Build a subscriptions integration
Create and manage subscriptions to accept recurring payments.
Customize with the Appearance API.
Interested in using Stripe Billing?
We’re developing a Payment Element integration that helps manage subscription features, including free trials, billing cycle anchors, and proration. Read the Build a checkout page guide to learn more.
Use this guide to learn how to sell fixed-price subscriptions. You’ll use the Payment Element to create a custom payment form that you embed in your application.
If you don’t want to build a custom payment form, you can integrate with Checkout. For an immersive version of that end-to-end integration guide, see the Billing quickstart.
If you aren’t ready to code an integration, you can set up basic subscriptions manually in the Dashboard. You can also use Payment Links to set up subscriptions without writing any code. Learn more about designing an integration to understand the decisions you need to make and the resources you need.
What you’ll build
This guide shows you how to:
- Model your business by building a product catalog.
- Build a registration process that creates a customer.
- Create subscriptions and collect payment information.
- Test and monitor payment and subscription status.
- Let customers change their plan or cancel the subscription.
How to model it on Stripe
Subscriptions simplify your billing by automatically creating Invoices and PaymentIntents for you. To create and activate a subscription, you need to first create a Product to model what is being sold, and a Price which determines the interval and amount to charge. You also need a Customer to store PaymentMethods used to make each recurring payment.
API object definitions
Set up Stripe
Install the Stripe client of your choice:
And then install the Stripe CLI. The CLI provides webhook testing and you can run it to make API calls to Stripe. This guide shows how to use the CLI to set up a pricing model in a later section.
For additional install options, see Get started with the Stripe CLI.
Create the pricing modelStripe CLI or Dashboard
Create your products and their prices in the Dashboard or with the Stripe CLI.
This example uses a fixed-price service with two different service-level options: Basic and Premium. For each service-level option, you need to create a product and a recurring price. (If you want to add a one-time charge for something like a setup fee, create a third product with a one-time price. To keep things simple, this example doesn’t include a one-time charge.)
In this example, each product bills at monthly intervals. The price for the Basic product is 5 USD. The price for the Premium product is 15 USD.
Create the customerClient and Server
Stripe needs a customer for each subscription. In your application frontend, collect any necessary information from your users and pass it to the backend.
If you need to collect address details, the Address Element enables you to collect a shipping or billing address for your customers. For more information on the Address Element, visit the Address Element page.
<form id="signup-form"> <label> Email <input id="email" type="email" placeholder="Email address" value="test@example.com" required /> </label> <button type="submit"> Register </button> </form>
const emailInput = document.querySelector('#email'); fetch('/create-customer', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: emailInput.value, }), }).then(r => r.json());
On the server, create the Stripe customer object.
Create the subscriptionClient and Server
Note
If you want to render the Payment Element without first creating a subscription, see Collect payment details before creating an Intent.
Let your new customer choose a plan and then create the subscription—in this guide, they choose between Basic and Premium.
On the frontend, pass the selected price ID and the ID of the customer record to the backend.
fetch('/create-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ priceId: priceId, customerId: customerId, }), })
On the backend, create the subscription with status incomplete
using payment_
. Then return the client_
from the subscription’s first payment intent to the frontend to complete payment.
Set save_default_payment_method to on_
to save the payment method as the default for a subscription when a payment succeeds. Saving a default payment method increases the success rate of future subscription payments.
Note
If you’re using a multi-currency Price, use the currency parameter to tell the Subscription which of the Price’s currencies to use. (If you omit the currency
parameter, then the Subscription uses the Price’s default currency.)
At this point the Subscription is inactive
and awaiting payment. Here’s an example response. The minimum fields to store are highlighted, but store whatever your application frequently accesses.
{ "id": "sub_JgRjFjhKbtD2qz", "object": "subscription", "application_fee_percent": null, "automatic_tax": { "enabled": false }, "billing": "charge_automatically", "billing_cycle_anchor": 1623873347, "billing_thresholds": null,
Collect payment informationClient
Use Stripe Elements to collect payment details and activate the subscription. You can customize Elements to match the look-and-feel of your application.
Note
If you’re building an integration with Stripe Elements, Link enables you to create frictionless payments for your customers. They can save, change, and manage all their payment details in Link without any impact to your integration. Meanwhile, as Stripe adds support for more payment methods to Link, your integration can automatically accept them, without requiring you to make changes to your Payment methods settings.
The Payment Element securely collects all necessary payment details for a wide variety of payments methods. The payment methods currently supported by both the Payment Element and Subscriptions are credit cards, Link, SEPA Direct Debit, and BECS Direct Debit.
Set up Stripe Elements
The Payment Element is automatically available as a feature of Stripe.js. Include the Stripe.js script on your checkout page by adding it to the head
of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head> <body> <!-- content here --> </body>
Create an instance of Stripe with the following JavaScript on your checkout page:
// Set your publishable key: remember to change this to your live publishable key in production // See your keys here: https://dashboard.stripe.com/apikeys const stripe = Stripe(
);'pk_test_TYooMQauvdEDq54NiTphI7jx'
Add the Payment Element to your page
The Payment Element needs a place to live on your payment page. Create an empty DOM node (container) with a unique ID in your payment form.
<form id="payment-form"> <div id="payment-element"> <!-- Elements will create form elements here --> </div> <button id="submit">Subscribe</button> <div id="error-message"> <!-- Display error message to your customers here --> </div> </form>
When the form above has loaded, create an instance of the Payment Element and mount it to the container DOM node. In the create the subscription step, you passed the client_
value to the frontend. Pass this value as an option when creating an instance of Elements.
const options = { clientSecret: '{{CLIENT_SECRET}}', // Fully customizable with appearance API. appearance: {/*...*/}, }; // Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in step 5 const elements = stripe.elements(options); const paymentElementOptions = { layout: "tabs", }; // Create and mount the Payment Element const paymentElement = elements.create('payment', paymentElementOptions); paymentElement.mount('#payment-element');
The Payment Element renders a dynamic form that allows your customer to select a payment method. The form automatically collects all necessary payments details for the payment method that they select.
Optional Payment Element configurations
- Customize the Payment Element to match the design of your site by passing the appearance object into
options
when creating an instance of Elements. - Configure the Apple Pay interface to return a merchant token to support recurring, auto reload, and deferred payments.
Complete payment
Use stripe.
to complete the payment using details from the Payment Element and activate the subscription. This creates a PaymentMethod and confirms the incomplete Subscription’s first PaymentIntent, causing a charge to be made. If Strong Customer Authentication (SCA) is required for the payment, the Payment Element handles the authentication process before confirming the PaymentIntent.
Provide a return_url to this function to indicate where Stripe redirects the user after they complete the payment. Your user might first be redirected to an intermediate site, like a bank authorization page, before being redirected to the return_
. Card payments immediately redirect to the return_
when a payment is successful.
const form = document.getElementById('payment-form'); form.addEventListener('submit', async (event) => { event.preventDefault(); const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, confirmParams: { return_url: "https://example.com/order/123/complete", } }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) const messageContainer = document.querySelector('#error-message'); messageContainer.textContent = error.message; } else { // Your customer will be redirected to your `return_url`. For some payment // methods like iDEAL, your customer will be redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } });
When your customer submits a payment, Stripe redirects them to the return_
and includes the following URL query parameters. The return page can use them to get the status of the PaymentIntent so it can display the payment status to the customer.
When you specify the return_
, you can also append your own query parameters for use on the return page.
Parameter | Description |
---|---|
payment_ | The unique identifier for the PaymentIntent . |
payment_ | The client secret of the PaymentIntent object. |
When the customer is redirected back to your site, you can use the payment_
to query for the PaymentIntent and display the transaction status to your customer.
Caution
If you have tooling that tracks the customer’s browser session, you might need to add the stripe.
domain to the referrer exclude list. Redirects cause some tools to create new sessions, which prevents you from tracking the complete session.
Use one of the query parameters to retrieve the PaymentIntent. Inspect the status of the PaymentIntent to decide what to show your customers. You can also append your own query parameters when providing the return_
, which persist through the redirect process.
// Initialize Stripe.js using your publishable key const stripe = Stripe(
); // Retrieve the "payment_intent_client_secret" query parameter appended to // your return_url by Stripe.js const clientSecret = new URLSearchParams(window.location.search).get( 'payment_intent_client_secret' ); // Retrieve the PaymentIntent stripe.retrievePaymentIntent(clientSecret).then(({paymentIntent}) => { const message = document.querySelector('#message') // Inspect the PaymentIntent `status` to indicate the status of the payment // to your customer. // // Some payment methods will [immediately succeed or fail][0] upon // confirmation, while others will first enter a `processing` state. // // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification switch (paymentIntent.status) { case 'succeeded': message.innerText = 'Success! Payment received.'; break; case 'processing': message.innerText = "Payment processing. We'll update you when payment is received."; break; case 'requires_payment_method': message.innerText = 'Payment failed. Please try another payment method.'; // Redirect your user back to your payment page to attempt collecting // payment again break; default: message.innerText = 'Something went wrong.'; break; } });'pk_test_TYooMQauvdEDq54NiTphI7jx'
Listen for webhooksServer
To complete the integration, you need to process webhooks sent by Stripe. These are events triggered whenever state inside of Stripe changes, such as subscriptions creating new invoices. In your application, set up an HTTP handler to accept a POST request containing the webhook event, and verify the signature of the event:
During development, use the Stripe CLI to observe webhooks and forward them to your application. Run the following in a new terminal while your development app is running:
stripe listen --forward-to localhost:4242/webhook
For production, set up a webhook endpoint URL in the Dashboard, or use the Webhook Endpoints API.
You’ll listen to a couple of events to complete the remaining steps in this guide. See Subscription events for more details about subscription-specific webhooks.
Provision access to your serviceClient and Server
Now that the subscription is active, give your user access to your service. To do this, listen to the customer.
, customer.
, and customer.
events. These events pass a subscription object which contains a status
field indicating whether the subscription is active, past due, or canceled. See the subscription lifecycle for a complete list of statuses.
In your webhook handler:
- Verify the subscription status. If it’s
active
then your user has paid for your product. - Check the product the customer subscribed to and grant access to your service. Checking the product instead of the price gives you more flexibility if you need to change the pricing or billing interval.
- Store the
product.
,id subscription.
andid subscription.
in your database along with thestatus customer.
you already saved. Check this record when determining which features to enable for the user in your application.id
The state of a subscription might change at any point during its lifetime, even if your application does not directly make any calls to Stripe. For example, a renewal might fail due to an expired credit card, which puts the subscription into a past due state. Or, if you implement the customer portal, a user might cancel their subscription without directly visiting your application. Implementing your handler correctly keeps your application state in sync with Stripe.
Cancel the subscriptionClient and Server
It’s common to allow customers to cancel their subscriptions. This example adds a cancellation option to the account settings page.
Account settings with the ability to cancel the subscription
function cancelSubscription(subscriptionId) { return fetch('/cancel-subscription', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId, }), }) .then(response => { return response.json(); }) .then(cancelSubscriptionResponse => { // Display to the user that the subscription has been canceled. }); }
On the backend, define the endpoint for your frontend to call.
Your application receives a customer.
event.
After the subscription is canceled, update your database to remove the Stripe subscription ID you previously stored, and limit access to your service.
When a subscription is canceled, it can’t be reactivated. Instead, collect updated billing information from your customer, update their default payment method, and create a new subscription with their existing customer record.
Test your integration
Test payment methods
Use the following table to test different payment methods and scenarios.
Payment method | Scenario | How to test |
---|---|---|
BECS Direct Debit | Your customer successfully pays with BECS Direct Debit. | Fill out the form using the account number 900123456 and BSB 000-000 . The confirmed PaymentIntent initially transitions to processing , then transitions to the succeeded status three minutes later. |
BECS Direct Debit | Your customer’s payment fails with an account_ error code. | Fill out the form using the account number 111111113 and BSB 000-000 . |
Credit card | The card payment succeeds and does not require authentication. | Fill out the credit card form using the credit card number 4242 4242 4242 4242 with any expiration, CVC, and postal code. |
Credit card | The card payment requires authentication. | Fill out the credit card form using the credit card number 4000 0025 0000 3155 with any expiration, CVC, and postal code. |
Credit card | The card is declined with a decline code like insufficient_ . | Fill out the credit card form using the credit card number 4000 0000 0000 9995 with any expiration, CVC, and postal code. |
SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit. | Fill out the form using the account number AT321904300235473204 . The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
SEPA Direct Debit | Your customer’s payment intent status transition from processing to requires_ . | Fill out the form using the account number AT861904300235473202 . |
Monitor events
Set up webhooks to listen to subscription change events like upgrades and cancellations. Learn more about subscription webhooks. You can view events in the Dashboard or with the Stripe CLI.
For more details, see testing your Billing integration.
Disclose Stripe to your customers
Stripe collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. You’re responsible for disclosing and obtaining all rights and consents necessary for Stripe to use data in these ways. For more information, visit our privacy center.