DEV Community

Cover image for Start a subscription to collect recurring payments with Stripe
CJ Avilla for Stripe

Posted on • Updated on

Start a subscription to collect recurring payments with Stripe

Subscriptions allow you to charge a customer on a recurring basis. Recall from the primer that a Subscription is related to a specific Customer and one or many recurring Prices which determine the billing interval. Each Subscription can be thought of as an “engine” that generates Invoices and collects payments on a recurring basis.

There are several ways to start Subscriptions with Stripe. In the Building a SaaS pricing page article, you learned how to construct a pricing page. As a refresher, if you use the embeddable pricing table or Payment Links, you need only drop in those references, and you’re off to the races. However, if you opt for Stripe Checkout or a custom form using the Payment Element, you’ll need to write a bit of code.

Here we’ll focus on using Stripe Checkout to start subscriptions using Ruby on Rails. Please refer to the full documentation to see how to integrate using the PaymentElement.

If you’re building along with the series then your application has an authentication system and a pricing page already. That pricing page passes the Price ID for a Stripe Price object to the server and we’re ready to create a Checkout Session and redirect your customer to the Stripe hosted checkout page.

The integration involves one API call to Stripe to create a Checkout Session that has a URL where we redirect the customer. Once the customer is done submitting their payment details, we redirect them back to our application.

Integration

For the simplest business model, a single flat rate monthly or yearly subscription, all we need from the user is the ID of the Price. By default Checkout will create a new Stripe Customer for us if we don’t pass a reference to an existing customer.

Again, we can have a debate about whether or not to create the Stripe Customer with the API before redirecting to Checkout.One on hand, we can explicitly create the customer and store its ID alongside the authenticated user and associate all future API calls with that specific Stripe Customer. On the other hand, if we want to leverage the embeddable pricing table or Payment Links at some later date, those will create Stripe Customers for us and there’s no way to pass in an existing customer. For pricing table, Payment Links, and Stripe Checkout, we can pass our authenticated user’s ID via the client_reference_id property to ensure we can associate the newly created Stripe Customer back to the right user in our database.

I know there are many, but I recommend reading through all of the arguments you can send when making an API call to create a Checkout Session. Your business model will dictate which ones you care about most. For instance, you might want to enable automatic tax calculation, or coupon code support.

Using the good-better-best model with a single price, this is the most basic call to create a SaaS Checkout Session:

session = Stripe::Checkout::Session.create(
  mode: 'subscription',
  line_items: [{
    price: params[:price_id], # The ID of the Price from the custom pricing page
    quantity: 1,
  }],
  success_url: 'http://localhost:3000/thanks',
  cancel_url: 'http://localhost:3000/pricing'
)
Enter fullscreen mode Exit fullscreen mode

The success URL (where the customer is redirected after subscribing) and cancel URL (if the customer bails by clicking on the back arrow to cancel) are hard coded so we're only passing one line item with a fixed quantity. Note that if you want to enable a per-seat pricing model, the quantity property is where you can specify the number of seats.

Let's look at another more realistic example for our SaaS company that:

  1. Associates the new Subscription with the authenticated user client_reference_id: current_user.id
  2. Dynamically derives the success and cancel URLs using Rails routing helper methods
  3. Passes metadata for the session and subscription (to simplify automations later). This way, all of the webhooks about Checkout Sessions and Subscriptions will include an ID to find the User object.
metadata: {
  user_id: current_user.id,
},
subscription_data: {
  metadata: {
    user_id: current_user.id,
  },
},
Enter fullscreen mode Exit fullscreen mode
  1. Enables promotion codes allow_promotion_codes: true,
  2. Collects taxes
automatic_tax: {
  enabled: true,
},
Enter fullscreen mode Exit fullscreen mode

Here's what the API call might look like (I know it's a lot, but this is a testament to the power and configurability of Checkout!)

session = Stripe::Checkout::Session.create(
  mode: 'subscription',
  line_items: [{
    price: params[:price_id],
    quantity: 1,
  }],
  client_reference_id: current_user.id,
  success_url: thanks_checkouts_url, # https://example.com/checkout/thanks
  cancel_url: pricing_url # https://example.com/pricing
  metadata: {
    user_id: current_user.id,
  },
  subscription_data: {
    metadata: {
      user_id: current_user.id,
    },
  },
  allow_promotion_codes: true,
  phone_number_collection: {
    enabled: true,
  },
  automatic_tax: {
    enabled: true,
  },
)
Enter fullscreen mode Exit fullscreen mode

In our Checkouts controller, we'll handle a POST request with the Price ID, use that to create a Checkout Session, and ultimately redirect.

class CheckoutsController < ApplicationController
  # Since we only want to collect payment from authenticated users
  # we ensure they are logged in:
  before_action :authenticate_user!
  def create
    session = Stripe::Checkout::Session.create(
      mode: 'subscription',
      client_reference_id: current_user.id,      
      line_items: [{
        price: params[:price_id],
        quantity: 1,
      }],
      success_url: thanks_checkouts_url, # https://example.com/checkout/thanks
      cancel_url: pricing_url # https://example.com/pricing
      # ...
    )
    redirect_to session.url, allow_other_hosts: true, status: :see_other
  end
end
Enter fullscreen mode Exit fullscreen mode

Consider each Checkout Session ephemeral. I don't store anything about the Checkout Session in the database, but if you wanted to keep some ledger of the attempts to start a Subscription you might create a CheckoutSession object and store the IDs of these objects in your database.

You may have noticed the subscription property on the Checkout Session object in the API reference and wondered whether you should store that ID in your database when you create your Checkout Session. That subscription property will be null until after the customer goes through the Checkout flow as the Subscription isn't actually created until they click on "Subscribe".

Next steps

Provisioning: how do we know whether the user is actually subscribed or not? In the next article in this series, you'll see how to handle the Checkout and Subscription webhook event types which is the only recommended path for fulfilling orders and giving users access.

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 🚲.

Oldest comments (0)