Prebuilt subscription page with Stripe Checkout
Incorporate your own test mode data into our sample app to run a full, working subscription integration using Stripe Billing and Stripe Checkout.
The sample app demonstrates redirecting your customers from your site to a prebuilt payment page hosted on Stripe. The Stripe Billing APIs create and manage subscriptions, invoices, and recurring payments, while Checkout provides the prebuilt, secure, Stripe-hosted UI for collecting payment details.
Click each step to see the corresponding sample code. As you interact with the steps, such as adding pricing data, the builder updates the sample code.
Download and customize the sample app locally to test your integration.
Add features to your product
Create features, such as an annual birthday gift, and associate them with your subscription to entitle new subscribers to them. Listen to the active entitlements summary events for your event destination, and use the list active entitlements API for a given customer to fulfill your customer’s entitlements.
Note
Enable payment methods
Use your Dashboard to enable supported payment methods that you want to accept in addition to cards. Checkout dynamically displays your enabled payment methods in order of relevance, based on the customer’s location and other characteristics.
Add a pricing preview page
Add a page to your site that displays your product and allows your customers to subscribe to it. Clicking Checkout, redirects them to a Stripe-hosted Checkout page, which finalizes the order and prevents further modification.
Consider embedding a pricing table to dynamically display your pricing information through the Dashboard. Clicking a pricing option redirects your customer to the checkout page.
Add a success page
Create a success page to display order confirmation messaging or order details to your customer. Associate this page with the Checkout Session success_
, which Stripe redirects to after the customer successfully completes the checkout.
Add a cancel page
Add a page to associate with the Checkout Session cancel_
, which Stripe redirects to when the customer clicks the back button in Checkout.
Install the Stripe Ruby library
Install the Stripe ruby gem and require it in your code. Alternatively, if you’re starting from scratch and need a Gemfile, download the project files using the link in the code editor.
Create a Checkout Session
The Checkout Session controls what your customer sees in the Stripe-hosted payment page such as line items, the order amount and currency, and acceptable payment methods.
Get the price from lookup key
Pass the lookup key you defined for your product in the Price endpoint to apply its price to the order.
Define the line items
Always keep sensitive information about your product inventory, such as price and availability, on your server to prevent customer manipulation from the client. Pass in the predefined price ID retrieved above.
Supply success and cancel URLs
Specify publicly accessible URLs that Stripe can redirect customers after success or cancellation. You can provide the same URL for both properties. Add the session_
query parameter at the end of your URL so you can retrieve the customer later and so Stripe can generate the customer’s hosted Dashboard.
Redirect from Checkout
After creating the session, redirect your customer to the URL returned in the response (either the success or cancel URL).
Create a customer portal session
Initiate a secure, Stripe-hosted customer portal session that lets your customers manage their subscriptions and billing details.
Redirect to customer portal
After creating the portal session, redirect your customer to the URL returned in the response.
Fulfill the subscription
Create a /webhook
endpoint and obtain your webhook secret key in the Webhooks tab in Workbench to listen for events related to subscription activity. After a successful payment and redirect to the success page, verify that the subscription status is active
and grant your customer access to the products and features they subscribed to.
Try it out
Click the checkout button. In the Stripe Checkout page, use any of these test cards to simulate a payment.
Add customization features
If you successfully subscribed to your product in your test, you have a working, basic subscriptions checkout integration. Use the toggles below to see how to customize this sample with additional features.
Attach a trial period to a Checkout session.
Specify a billing cycle anchor when creating a Checkout session.
Calculate and collect the right amount of tax on your Stripe transactions. Learn more about Stripe Tax and how to add it to Checkout. Activate Stripe Tax in the Dashboard before integrating.
Next steps
Update subscription prices
Update subscriptions to handle customers upgrading or downgrading their subscription plan.
Apply prorations
Learn how to adjust a customer’s invoice to accurately reflect mid-cycle pricing changes.
Offer upsells
Incentivize customers with discounts for committing to longer billing intervals.
More features
Review the features to further customize your integration to offer discounts, pause payment collection, and more.
<!DOCTYPE html><html><head><title>Subscribe to a cool new product</title><link rel="stylesheet" href="style.css"><script src="https://js.stripe.com/v3/"></script></head><body><section><div class="product"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="16px" viewBox="0 0 14 16" version="1.1"><defs/><g id="Flow" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="0-Default" transform="translate(-121.000000, -40.000000)" fill="#E184DF"><path d="M127,50 L126,50 C123.238576,50 121,47.7614237 121,45 C121,42.2385763 123.238576,40 126,40 L135,40 L135,56 L133,56 L133,42 L129,42 L129,56 L127,56 L127,50 Z M127,48 L127,42 L126,42 C124.343146,42 123,43.3431458 123,45 C123,46.6568542 124.343146,48 126,48 L127,48 Z" id="Pilcrow"/></g></g></svg><div class="description"><h3>Starter plan</h3><h5>$20.00 / month</h5></div></div><form action="/create-checkout-session" method="POST"><!-- Add a hidden field with the lookup_key of your Price --><input type="hidden" name="lookup_key" value="{{PRICE_LOOKUP_KEY}}" /><button id="checkout-and-portal-button" type="submit">Checkout</button></form></section></body></html>
<!DOCTYPE html><html><head><title>Thanks for your order!</title><link rel="stylesheet" href="style.css"><script src="client.js" defer></script></head><body><section><div class="product Box-root"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="16px" viewBox="0 0 14 16" version="1.1"><defs/><g id="Flow" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="0-Default" transform="translate(-121.000000, -40.000000)" fill="#E184DF"><path d="M127,50 L126,50 C123.238576,50 121,47.7614237 121,45 C121,42.2385763 123.238576,40 126,40 L135,40 L135,56 L133,56 L133,42 L129,42 L129,56 L127,56 L127,50 Z M127,48 L127,42 L126,42 C124.343146,42 123,43.3431458 123,45 C123,46.6568542 124.343146,48 126,48 L127,48 Z" id="Pilcrow"/></g></g></svg><div class="description Box-root"><h3>Subscription to Starter plan successful!</h3></div></div><form action="/create-portal-session" method="POST"><input type="hidden" id="session-id" name="session_id" value="" /><button id="checkout-and-portal-button" type="submit">Manage your billing information</button></form></section></body></html>
<!DOCTYPE html><html><head><title>Checkout canceled</title><link rel="stylesheet" href="style.css"></head><body><section><p>Picked the wrong subscription? Shop around then come back to pay!</p></section></body></html>
body {display: flex;flex-direction: column;justify-content: center;align-items: center;background: #242d60;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto','Helvetica Neue', 'Ubuntu', sans-serif;height: 100vh;margin: 0;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}section {background: #ffffff;display: flex;flex-direction: column;width: 400px;height: 112px;border-radius: 6px;justify-content: space-between;margin: 10px;}.product {display: flex;}.description {display: flex;flex-direction: column;justify-content: center;}p {font-style: normal;font-weight: 500;font-size: 14px;line-height: 20px;letter-spacing: -0.154px;color: #242d60;height: 100%;width: 100%;padding: 0 20px;display: flex;align-items: center;justify-content: center;box-sizing: border-box;}svg {border-radius: 6px;margin: 10px;width: 54px;height: 57px;}h3,h5 {font-style: normal;font-weight: 500;font-size: 14px;line-height: 20px;letter-spacing: -0.154px;color: #242d60;margin: 0;}h5 {opacity: 0.5;}a {text-decoration: none;color: white;}#checkout-and-portal-button {height: 36px;background: #556cd6;color: white;width: 100%;font-size: 14px;border: 0;font-weight: 500;cursor: pointer;letter-spacing: 0.6;border-radius: 0 0 6px 6px;transition: all 0.2s ease;box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);}#checkout-and-portal-button:hover {opacity: 0.8;}
// In production, this should check CSRF, and not pass the session ID.// The customer ID for the portal should be pulled from the// authenticated user on the server.document.addEventListener('DOMContentLoaded', async () => {let searchParams = new URLSearchParams(window.location.search);if (searchParams.has('session_id')) {const session_id = searchParams.get('session_id');document.getElementById('session-id').setAttribute('value', session_id);}});
require 'stripe'require 'sinatra'# This is a public sample test API key.# Don’t submit any personally identifiable information in requests made with this key.# Sign in to see your own test API key embedded in code samples.Stripe.api_key = 'sk_test_BQokikJOvBiI2HlWgH4olfQ2'set :static, trueset :port, 4242YOUR_DOMAIN = 'http://localhost:4242'post '/create-checkout-session' doprices = Stripe::Price.list(lookup_keys: [params['lookup_key']],expand: ['data.product'])beginsession = Stripe::Checkout::Session.create({mode: 'subscription',line_items: [{quantity: 1,price: prices.data[0].id}],success_url: YOUR_DOMAIN + '/success.html?session_id={CHECKOUT_SESSION_ID}',cancel_url: YOUR_DOMAIN + '/cancel.html',})rescue StandardError => ehalt 400,{ 'Content-Type' => 'application/json' },{ 'error': { message: e.error.message } }.to_jsonendredirect session.url, 303endpost '/create-portal-session' docontent_type 'application/json'# For demonstration purposes, we're using the Checkout session to retrieve the customer ID.# Typically this is stored alongside the authenticated user in your database.checkout_session_id = params['session_id']checkout_session = Stripe::Checkout::Session.retrieve(checkout_session_id)# This is the URL to which users will be redirected after they're done# managing their billing.return_url = YOUR_DOMAINsession = Stripe::BillingPortal::Session.create({customer: checkout_session.customer,return_url: return_url})redirect session.url, 303endpost '/webhook' do# Replace this endpoint secret with your endpoint's unique secret# If you are testing with the CLI, find the secret by running 'stripe listen'# If you are using an endpoint defined with the API or dashboard, look in your webhook settings# at https://dashboard.stripe.com/webhookswebhook_secret = 'whsec_12345'payload = request.body.readif !webhook_secret.empty?# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.sig_header = request.env['HTTP_STRIPE_SIGNATURE']event = nilbeginevent = Stripe::Webhook.construct_event(payload, sig_header, webhook_secret)rescue JSON::ParserError => e# Invalid payloadstatus 400returnrescue Stripe::SignatureVerificationError => e# Invalid signatureputs '⚠️ Webhook signature verification failed.'status 400returnendelsedata = JSON.parse(payload, symbolize_names: true)event = Stripe::Event.construct_from(data)end# Get the type of webhook event sent - used to check the status of PaymentIntents.event_type = event['type']data = event['data']data_object = data['object']if event.type == 'customer.subscription.deleted'# handle subscription canceled automatically based# upon your subscription settings. Or if the user cancels it.# puts data_objectputs "Subscription canceled: #{event.id}"endif event.type == 'customer.subscription.updated'# handle subscription updated# puts data_objectputs "Subscription updated: #{event.id}"endif event.type == 'customer.subscription.created'# handle subscription created# puts data_objectputs "Subscription created: #{event.id}"endif event.type == 'customer.subscription.trial_will_end'# handle subscription trial ending# puts data_objectputs "Subscription trial will end: #{event.id}"endif event.type == 'entitlements.active_entitlement_summary.updated'# handle active entitlement summary updated# puts data_objectputs "Active entitlement summary updated: #{event.id}"endcontent_type 'application/json'{status: 'success'}.to_jsonend
source 'https://rubygems.org/'gem 'sinatra'gem 'stripe'
# Prebuilt checkout page with subscriptionsExplore a full, working code sample of an integration with Stripe Checkout and the customer portal. The client- and server-side code redirects to a prebuilt payment page hosted on Stripe. Included are some basic build and run scripts you can use to start up the application.## Running the sample1. Build the server~~~bundle install~~~2. Run the server~~~ruby server.rb -o 0.0.0.0~~~3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html)