DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Managing the customer billing lifecycle using the customer portal
CJ Avilla for Stripe

Posted on • Updated on

Managing the customer billing lifecycle using the customer portal

Once you have the full customer onboarding flow in place and you're correctly giving access to paid services and features, you'll inevitably start to hear support requests where customers want to update payment methods on file, upgrade and downgrade between plans, view billing history, and more. We call this β€œcustomer lifecycle management.”

The two primary options for supporting billing management are to build it yourself or to use the Stripe customer portal. Similar to Stripe Checkout, the customer portal is a page hosted by Stripe where you redirect existing customers with active subscriptions. From the portal, they can do things like update their payment methods and upgrade, cancel, upgrade, or pause their subscriptions. The customer portal is configurable by the API or via settings in the Stripe Dashboard.

Screenshot of some of the settings for configuring the customer portal from the dashboard. https://dashboard.stripe.com/settings/billing/portal

I highly recommend implementing the customer portal instead of the significant investment of implementing all of the billing management features by hand. The customer portal is feature rich, localized in many languages, and will only continue to get better. You would need to invest development time for each of those changes if you decide to build all your billing management from scratch.

The fastest way to integrate is with a single API call to create a customer portal session which will return a URL that you can redirect customers to. The most basic session only requires that we pass the customer's ID (which we created during onboarding, remember?):

session = Stripe::BillingPortal::Session.create({
  customer: 'cus_F6sbKqBiA0Ms4g',
})
redirect_to session.url, allow_other_hosts: true, status: :see_other
Enter fullscreen mode Exit fullscreen mode

Handling changes with webhooks

When customers make changes to their subscriptions in the customer portal, it’s important that our application reflects those changes. In the previous article about provisioning access to a SaaS using webhooks, we discussed several webhook event types that are important to handle for giving access. You’ll also want to ensure that you’re handling the customer.subscription.deleted webhook event which represents the customer’s cancellation of a subscription. In this case, we find the Subscription in the database by ID and update its status to the one for the webhook event payload: canceled.

def handle_subscription_deleted(event)
  subscription = event.data.object
  sub = Subscription.find_by(stripe_id: subscription.id)
  sub.update!(
    status: subscription.status,
  )
end
Enter fullscreen mode Exit fullscreen mode

Configuring portal sessions

By default, any customer portal session you create will use your Stripe Dashboard’s customer portal settings. You can also create portal configurations to individualize the portal experience for different customers.

For instance, you may want to offer customers on legacy plans the option to upgrade to a higher tier of a legacy plan, but require that new customers use a default set of tiers with different prices. One solution would be to create a custom portal configuration for the legacy users that includes a list of legacy products and prices in the features.subscription_update.products array.

Given the ID of a customer portal configuration object, you can create new customer portal sessions by passing in the config to modify the features seen by the customer using that portal session.

config = Stripe::BillingPortal::Configuration.create({
  features: {
    customer_update: {
      allowed_updates: ['email', 'tax_id'],
      enabled: true,
    },
    invoice_history: {enabled: true},
    payment_method_update: {enabled: true},
    subscription_update: {
      enabled: true,
      default_allowed_updates: ['quantity', 'price'],
      products: [{
        product: 'prod_MBsdZlqD3StDZo', # Legacy product
        prices: [
          'price_1LTUryCZ6qsJgndJl0mhzex1', # Legacy monthly
          'price_1LXWGHCZ6qsJgndJBOEgbbxs', # Legacy annual
        ],
      }],
    },
  },
  business_profile: {
    privacy_policy_url: 'https://example.com/privacy',
    terms_of_service_url: 'https://example.com/terms',
  },
})

portal_session = Stripe::BillingPortal::Session.create({
  customer: 'cus_La7iFhdBoBP29t',
  configuration: config.id
})

redirect_to portal_session.url, allow_other_host: true, status: :see_other
Enter fullscreen mode Exit fullscreen mode

What customers see

Depending on the configuration, customers will see a two-panel UI showing their current plan, payment method, billing information and actions they can take.

Screenshot of the self-serve subscription management UI for the customer portal, showing the user’s current plan, payment method(s), and billing information.

Next steps

If you’ve made it this far in the series, nice work! You now have all the fundamental building blocks for launching a recurring SaaS application with Stripe. We’d love to hear your feedback about the integration patterns and best practices outlined in the series. Please drop me a line to let me know what you’re building: @cjav_dev on Twitter.

About the author

CJ Avilla

CJ Avilla (@cjav_dev) is a Developer Advocate at Stripe, a Ruby on Rails developer, and a YouTuber. He loves learning and teaching new programming languages and web frameworks. When he’s not at his computer, he’s spending time with his family or on a bike ride 🚲.

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.