Build a subscriptions integration
Create and manage subscriptions to accept recurring payments.
Checkout
Elements
Mobile
Integration effort
Some code
Integration type
Combine UI components into a custom payment flow
UI customisation
CSS-level customisation with the Appearance API
Build a custom payment form using Stripe Elements and the Checkout Sessions API to sell fixed-price subscriptions. See how this integration compares to Stripe’s other integration types.
The Checkout Sessions API provides built-in support for tax calculation, discounts, shipping and currency conversion, reducing the amount of custom code you need to write. This is the recommended approach for most integrations. Learn more about when to use Checkout Sessions instead of PaymentIntents.
If you don’t want to build a custom payment form, you can integrate with the hosted version of 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 catalogue.
- 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.
API object definitions
Set up Stripe
Install the Stripe client of your choice:
Command Line
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# Available as a gem
sudo gem install stripe
Gemfile
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
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.
Install the Stripe CLI with npm:
Command Line
npm install -g @stripe/cli@latest
After installation, log in to your Stripe account:
Command Line
stripe login
After you install the CLI, you can also install agent tooling, or set up autocompletion.
Note
For more installation options for Windows, macOS, Linux and Docker, see the Stripe CLI readme on GitHub.
Create the pricing model Stripe CLI or Dashboard
Recurring pricing models represent the products or services you sell, how much they cost, what currency you accept for payments, and the service period for subscriptions. To build the pricing model, create products (what you sell) and prices (how much and how often to charge for your products).
This example uses flat-rate pricing with two different service-level options: Basic and Premium. For each service-level option, you need to create a product and a recurring price. To add a one-time charge for something like a setup fee, create a third product with a one-time price.
Each product bills at monthly intervals. The price for the Basic product is 5 USD. The price for the Premium product is 15 USD. See the flat rate pricing guide for an example with three tiers.
Go to the Add a product page and create two products. Add one price for each product, each with a monthly recurring billing period:
- Premium product: Premium service with extra features
- Price: Flat rate | 15 USD
- Basic product: Basic service with minimum features
- Price: Flat rate | 5 USD
After you create the prices, record the price IDs so you can use them in other steps. Price IDs look like this: price_G0FvDp6vZvdwRZ.
When you’re ready, use the Copy to live mode button at the top right of the page to clone your product from a sandbox to live mode.
Create the customer Client and Server
Stripe needs a customer for each subscription. In your application front end, collect any necessary information from your users and pass it to the back end.
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, see the Address Element page.
register.html
<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>
register.js
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 an object to represent the customer. This can be either a customer-configured Account object or a Customer object. Save the object’s ID to use in the Checkout Session.
Use the Accounts v2 API to represent customers
The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a preview version in your code.
To join the Accounts v2 preview, go to Account previews and features in your Dashboard and enable Reusable payment methods for Global Payouts.
For most use cases, we recommend modelling your customers as customer-configured Account objects instead of using Customer objects.
Command Line
Select a language
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
Create a Checkout Session Server
On the back end of your application, define an endpoint that creates the session for your front end to call. You’ll need the price ID of the subscription the customer is signing up for – your front end passes this value.
If you created a one-off price in step 2, pass that price ID also. After creating a Checkout Session, make sure you pass the client secret back to the client in the response.
Note
You can use lookup_keys to fetch prices rather than Price IDs. See the sample application for an example.
Select a language
Ruby
Python
.NET
PHP
Java
Node.js
Go
No results
From your Dashboard, enable the payment methods you want to accept from your customers. Checkout supports several payment methods.
Initialise Checkout Client
Call initCheckoutElementsSdk, passing in clientSecret.
initCheckoutElementsSdk returns a Checkout object that contains data from the Checkout Session and methods to update it.
Read the total and lineItems from actions.getSession(), and display them in your UI. This lets you turn on new features with minimal code changes. For example, adding manual currency prices requires no UI changes if you display the total.
checkout.html
<div id="checkout-container"></div>
checkout.js
const clientSecret = fetch('/create-checkout-session', {method: 'POST'})
.then((response) => response.json())
.then((json) => json.client_secret);
const checkout = stripe.initCheckoutElementsSdk({clientSecret});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
const session = loadActionsResult.actions.getSession();
const checkoutContainer = document.getElementById('checkout-container');
checkoutContainer.append(JSON.stringify(session.lineItems, null, 2));
checkoutContainer.append(document.createElement('br'));
checkoutContainer.append(`Total: ${session.total.total.amount}`);
}
Collect payment information Client
Collect payment details on the client with the Payment Element. The Payment Element is a pre-built UI component that simplifies collecting payment details for a variety of payment methods.
The Payment Element contains an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing the Payment Element within another iframe because some payment methods require redirecting to another page for payment confirmation.
If you choose to use an iframe and want to accept Apple Pay or Google Pay, the iframe must have the allow attribute set to equal "payment *".
The checkout page address must start with https:// rather than http:// for your integration to work. You can test your integration without using HTTPS, but remember to enable it when you’re ready to accept live payments.
First, create a container DOM element to mount the Payment Element. Then create an instance of the Payment Element using checkout.createPaymentElement and mount it by calling element.mount, providing either a CSS selector or the container DOM element.
checkout.html
<div id="payment-element"></div>
checkout.js
const paymentElement = checkout.createPaymentElement();
paymentElement.mount('#payment-element');
See the Stripe.js docs to view the supported options.
You can customise the appearance of all Elements by passing elementsOptions.appearance when initialising Checkout on the front end.
Submit the payment Client-side
Render a Pay button that calls confirm from the Checkout instance to submit the payment.
checkout.html
<button id="pay-button">Pay</button>
<div id="confirm-errors"></div>
checkout.js
const checkout = stripe.initCheckoutElementsSdk({clientSecret});
checkout.on('change', (session) => {
document.getElementById('pay-button').disabled = !session.canConfirm;
});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
const {actions} = loadActionsResult;
const button = document.getElementById('pay-button');
const errors = document.getElementById('confirm-errors');
button.addEventListener('click', () => {
// Clear any validation errors
errors.textContent = '';
actions.confirm().then((result) => {
if (result.type === 'error') {
errors.textContent = result.error.message;
}
});
});
}
Listen for webhooks Server
To complete the integration, you need to process webhooks sent by Stripe. These events are triggered whenever the status in 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:
server.rb
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
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:
Command Line
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 need to listen to a few events to complete the remaining steps in this guide. See Subscription events for more details about subscription-specific webhooks.
Provision access to your service Client and Server
Now that the subscription is active, give your user access to your service. To do this, listen to the customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted events. These events pass a subscription object that contains a status field indicating whether the subscription is active, past due or cancelled. 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 period.
- Store the product. id , subscription. id and subscription. status in your database along with either the customer _ account. id or the customer. id you already saved. Check this record when determining which features to enable for the user in your application.
The status of a subscription might change at any point during its lifetime, even if your application doesn’t directly make any calls to Stripe. For example, a renewal might fail because of an expired credit card, which puts the subscription into a past due status. 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 status in sync with Stripe.
Cancel the subscription Client 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
script.js
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 back end, define the endpoint for your front end to call.
server.rb
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new('sk_test_Ou1w6LVt3zmVipDVJsvMeQsc')
post '/cancel-subscription' do
content_type 'application/json'
data = JSON.parse request.body.read
deleted_subscription = client.v1.subscriptions.cancel(data['subscriptionId'])
deleted_subscription.to_json
end
Your application receives a customer.subscription.deleted event.
After the subscription is cancelled, update your database to remove the Stripe subscription ID you previously stored and limit access to your service.
When a subscription cancels, you can’t reactivate it. 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 |
|---|---|---|
| the related setting Direct Debit | Your customer successfully pays with the related setting Direct Debit. | Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
| the related setting Direct Debit | Your customer’s payment fails with an account_closed error code. | Fill out the form using the account number 111111113 and BSB 000000. |
| Credit card | The card payment succeeds and doesn’t 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_funds. | Fill out the credit card form using the credit card number 4000 0000 0000 9995 with any expiration, CVC, and postal code. |
| the related setting Direct Debit | Your customer successfully pays with the related setting Direct Debit. | Fill out the form using the account number the related setting. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
| the related setting Direct Debit | Your customer’s PaymentIntent status transitions from processing to requires_payment_method. | Fill out the form using the account number the related setting. |
Monitor events
Set up webhooks to listen to subscription change events, such as 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.
Optional Let customers change their plans Client and Server
Optional Preview a price change Client and Server
Optional Display the customer payment method Client and Server
Optional Set the billing cycle date Server
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.
