Deciding on a Feature's Depth: The Example of a Subscription

Nicolas Hennick
5 February 2024
In web development, one of the most nuanced decisions is determining the depth of a feature's integration. How much is good enough? This decision is seldom black and white and often depends on the context and stage of the product. How do you strike the right balance between functionality and resource investment? Let's explore this through a real-world example: implementing a subscription system for a startup's new product.

The Context
This startup has developed an innovative product, aiming to monetize it through subscriptions. Users are offered a 14-day trial to experience the product, after which they can subscribe to continue its use. Operating with limited time and budget, our focus was on creating value efficiently. Therefore, we initially launched the product without a payment or subscription system. This approach was driven by pragmatism – why invest in a subscription system before confirming the product's market fit?

As it turned out, the app gained traction, and the end of the trial period for our early users was approaching. It was time to implement a subscription system. We decided to use Stripe for payment processing, known for its simplicity and robust API.

How much is good enough?
Here's where we needed to decide on the feature's depth. We had three options:
1. Manual MVP: Manually manage everything in Stripe and the app, sending payment links via email.
2. Proximity: Build a minimal connection between the app and Stripe, automating essential processes to eliminate human intervention while leveraging Stripe's interfaces.
3. Integration: Fully integrate Stripe for a seamless user experience, making it appear as though all payment and subscription management occurs within the app.

Given that our product is B-to-C, where user convenience is paramount, the Manual MVP option posed too high a risk of losing users due to friction. On the other hand, full integration, while ideal, demanded more resources than we could justify at this stage. Therefore, we settled on the Proximity option, striking a balance between automation and resource expenditure.

Technical Implementation
Our subscription model offers two distinct options, each providing a specific allocation of search credits and AI comment credits. To manage these limits effectively, we used Stripe's metadata feature, allowing us to set and track user-specific limits directly.


Additionally, we leveraged the Pay gem, a versatile payment engine that facilitated Stripe integration and subscription management, and provided access to the necessary metadata. We had to extend the `Pay::Subscription` model from the pay gem to include custom methods to retrieve the credit limits. Here's a snippet:

Rails.application.config.to_prepare do
  Pay::Subscription.include SubscriptionExtensions
end

module SubscriptionExtensions
  extend ActiveSupport::Concern

  included do
    # Custom methods to access subscription limits
    def ai_comment_limit
      plan_metadata['ai_comment_limit']
    end

    # ... other methods ...
  end

  private

  def plan_metadata
    data.dig('subscription_items', 0, 'price', 'metadata')
  end
end

@user.payment_processor.subscription.ai_comment_limit # => 120

This allowed us to efficiently track and manage user credit limits. All in all, it took just a few hours to build a fully functional and relatively complex subscription feature. While this approach has its dependencies, it was a perfect fit given the current needs and resources. 

Conclusion: The Art of Enough
Ultimately, the depth of our subscription feature mirrored the startup's immediate needs and constraints. This journey highlights a critical aspect of software development: it's not always about building the most comprehensive features, but rather about building what is sufficiently good for the moment.

Need help on this subject?