Subscription Payments #3: Update and Cancel plans via a Manage Billing Screen using Stripe

Subscription Payments are the bread and butter of a SaaS application. In this guide, we will build a Manage Billing screen to let customers update their subscription plans.

Subscription Payments #3: Update and Cancel plans via a Manage Billing Screen using Stripe

This is the Part 3 in the series of guides on adding, managing, and keeping track of subscription payments using Stripe and Mongo for your SaaS app.

Subscription Payments are the bread and butter of a SaaS application; it's how you start generating revenue. In a well-implemented system, users need to first be able to change or cancel their plans, and second, undergo a trial phase to see if they would even like to pay for premium features. Typically, it's a hassle to set these up. In this set of guides, we will go through all the steps required to build a complete Subscription Payments system for your SaaS app.

In Part 3, we will:

  1. Allow customers to switch between Basic and Pro plans
  2. Cancel a subscription plan

Let's start!

Configure Billing Portal Settings

One of the great things about Stripe is that it has Billing Portal features already built in. Things like, View Previous Invoices, Change Billing Information, Allow Promotion Codes etc. can be turned on in the Billing Portal Settings page here.

Let's enable a few options.

  1. Enable the Allow customers to cancel subscriptions
  2. Enable the Allow customers to switch to a different pricing plan
  3. Add our products so customers can see the other available plans when switching
  4. Add a Privacy Policy, Terms of Service, and a Redirect page. Hit Save.

Add our products so customers can see the other available plans when switching.

That's it! Stripe just did the bulk of our work for us. All we have to do now is to trigger the Manage Billing screen. Let's do that next.

Create a Manage Billing button

We can add a Manage Billing button in views/account.ejs that shows up only when the customer is on an active plan. This will give them the option to update or cancel their plan.

Let's start by editing views/account.ejs

<% } else{ %>
					// ...
              <p>
                Not happy with your current plan? Cancel or Upgrade by clicking the
                button below.
              </p>
              <button id="manage-billing-button" type="submit">
                Manage Billing
              </button>
<% }

When the button is clicked, we will send customer information along and trigger the Customer Billing Session on the /billing endpoint. Edit the public/js/account.js

// ..

const manageBillingButton = $('#manage-billing-button')

manageBillingButton.click(function () {
    const requestOptions = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
				customer: customer.billingID
      })
    }

    fetch('/billing', requestOptions)
      .then((response) => response.json())
      .then((result) => window.location.replace(result.url))
      .catch((error) => console.log('error', error))
  })

Let's create a server-side session for Billing in app.js

app.post('/billing', async (req, res) => {
  const { customer } = req.body

  const session = await Stripe.createBillingSession(customer)

  res.json({ url: session.url })
})

We need to edit the src/connect/stripe.js so we can add the createBillingSession method.

const createBillingSession = async (customer) => {
  const session = await Stripe.billingPortal.sessions.create({
    customer,
    return_url: 'https://localhost:4242'
  })
  return session
}

module.exports = {
  // ...
		createBillingSession 
}

We are ready to test it out. Login, buy a plan, and then try to change the plan.

The plan should be correctly changed on the Stripe Dashboard. Notice, however, the information in the database is now outdated.

Update Customer data when the plan is updated/cancelled

When the customer updates or cancels their purchased subscription plan via the Manage Billing screen, Stripe sends a customer.subscription.updated event to our webhook.

app.post("/webhook", async (req, res) => {
	  // ..
    case "customer.subscription.updated":
      //started trial
      const user = await UserService.getUserByBillingID(data.customer)

      if (data.plan.id == productToPriceMap.BASIC) {
        user.plan = "basic"
      }

      if (data.plan.id == productToPriceMap.PRO) {
        user.plan = "pro"
      }

      const isOnTrial = data.status === "trialing"

      if (isOnTrial) {
        user.hasTrial = true
        user.endDate = new Date(data.current_period_end * 1000)
      } else if (data.status === "active") {
        user.hasTrial = false
        user.endDate = new Date(data.current_period_end * 1000)
      }

      if (data.canceled_at) {
        // cancelled
        user.plan = "none"
        user.hasTrial = false
        user.endDate = null
      }

      await user.save()
      break
    default:
  }
	// ..
});

Test it out

Let's test out our application. Go through the login flow with your email address and purchase a plan. Then go back to the login page, log in again and try to cancel it via the Manage Billing button.

Perfect! Not only can a customer now buy a subscription plan, but they have the option to update/cancel it as well if they wish to do so.

Next Steps

In this guide we learned how to:

  1. Allow customers to switch between Basic and Pro plans
  2. Cancel a subscription plan

Our customers can now correctly buy/update/cancel subscription plans but they don't have access to exclusive content yet. Presumably they are buying a plan to consume some sort of premium content. We will look into how we can set up special pages that a customer can access based on the type of plan they are on.

Subscription Payments #4: Access premium content based on a subscription plan