# Build a subscriptions solution for an AI startup with a usage-based pricing model Create a customized payments integration to handle billing for usage-based pricing models. This guide describes how SaaS startups can build a [usage-based pricing](https://docs.stripe.com/billing/subscriptions/usage-based.md) model with a customized UI and payment flow with Stripe. As an example, the guide uses a fictional AI company called Hypernian, that’s integrating with Stripe to provide a [fixed fee and overages](https://docs.stripe.com/subscriptions/pricing-models/usage-based-pricing.md#fixed-fee-overage) pricing model for their Hypernian Chat model. Hypernian charges customers a flat rate per month for a base package, and bills any usage beyond that separately. ## Create a Stripe account Before integrating with Stripe, you must create a Stripe account. 1. [Create an account](https://dashboard.stripe.com/register) by entering your email address, full name, country, and creating a password. 1. Fill out your business profile. 1. In the Dashboard, click **Verify your email**. Stripe sends a verification email to your email address. 1. Verify your email address. ## Set up testing environment Before processing live transactions, test your integration with the Stripe testing tools, including [sandboxes](https://docs.stripe.com/sandboxes.md) and [test clocks](https://docs.stripe.com/billing/testing/test-clocks.md). Set these up early so you can test your integration before going live. ### Create a sandbox First, create a [sandbox](https://docs.stripe.com/sandboxes/dashboard/manage.md#create-a-sandbox). Configure the sandbox to match your live mode configuration, so you can test and stage changes on an ongoing basis. ### Create a simulation Next, create a simulation. As you change time, resources such as [Subscriptions](https://docs.stripe.com/api/subscriptions.md) change state and trigger *webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) events. 1. In the Stripe Dashboard, go to [Simulations](https://dashboard.stripe.com/test/test-clocks) in the Dashboard. 1. Click **+ New simulation**. 1. In the **Create new simulation** modal, enter a name for the simulation. You can use this to describe the simulation you’re testing, such as `Annual renewal` or `Free trial`. 1. Set the frozen time of the clock. This is the starting point for your simulation. You can set this to a time in the future or in the past, but you can only move it forward in time after you set it. To simulate subscription scenarios, click the **Add** dropdown and select **Customer**. You only need to enter a name for the customer but for some scenarios, such as tax collection and calculation, you need to add other information, such as billing and shipping addresses. Next, click the **Add** dropdown and select **Subscription**. Configure the subscription to suit your scenario. You can add additional customers and subscriptions to follow the rest of this guide. ## Set up a pricing model In this example, Hypernian charges customers for access to their LLM services by using a [fixed fee and overages](https://docs.stripe.com/subscriptions/pricing-models/usage-based-pricing.md#fixed-fee-overage) pricing model, with the following rates: | License | Fee | | -------- | ------- | | Per user | 100 USD | | Usage | Fee | | ------ | -------- | | 0-1000 | 0 USD | | 1000+ | 0.04 USD | To implement this model, you create a meter to record the usage, products and prices to represent your service, a customer, and a customer subscription. ### Create a meter Meters specify how to aggregate meter events over a billing period. Meter events represent all actions that customers take in your system (for example, API requests). Meters attach to prices and form the basis of what’s billed. For the Hypernian example, meter events are the number of tokens a customer uses in a query. The meter is the sum of tokens over a month. You can use the Stripe Dashboard or API to configure a meter. To use the API with the Stripe CLI to create a meter, [get started with the Stripe CLI](https://docs.stripe.com/stripe-cli.md). #### Dashboard 1. On the [Meters](https://dashboard.stripe.com/test/meters) page, click **Create meter**. 1. In the meter editor: - For **Meter name**, enter the name of the meter to display and for organization purposes. For the Hypernian example, enter “Hypernian tokens.” - For **Event name**, enter the name to display in meter events when reporting usage to Stripe. For the Hypernian example, enter “hypernian_tokens.” - Set the **Aggregation method** in the dropdown: - For the Hypernian example, select **Sum**. This will *sum the values* reported (in this example, number of tokens a customer uses) to determine the usage to bill for. - Choose **Count** to bill based on the *number* of events reported. - Choose **Last** to bill based on the *last value* reported. - Use the preview pane to set example usage events and verify the aggregation method. - Click **Create meter**. - (Optional) Under **Advanced settings**, specify the **Dimensions** that you want to tag your usage data with. To generate granular segment specific alerts, or to granularly price usage based on a combination of attributes, submit your usage data with dimensions that are populated for analytics and reporting. Some example dimensions are LLM model, token type, region, and event type. #### API ```curl curl https://api.stripe.com/v1/billing/meters \ -u "<>:" \ -d display_name="Hypernian tokens" \ -d event_name=hypernian_tokens \ -d "default_aggregation[formula]"=sum \ -d "customer_mapping[event_payload_key]"=stripe_customer_id \ -d "customer_mapping[type]"=by_id \ -d "value_settings[event_payload_key]"=value ``` ### Create the pricing model Use the Stripe Dashboard or API to create a [pricing model](https://docs.stripe.com/products-prices/pricing-models.md) that includes your [Products](https://docs.stripe.com/api/products.md) and their pricing options. [Prices](https://docs.stripe.com/api/prices.md) define the unit cost, currency, and billing period. For the Hypernian example, you create a product with a metered price of 0.04 USD per hundred units, billed at a monthly interval. Use the meter that you created in the previous step. #### Dashboard 1. On the [Product catalog](https://dashboard.stripe.com/products?active=true) page, click **Create product**. 1. On the **Add a product** page, do the following: 1. For **Name**, enter the name of your product. For the Hypernian example, enter `Hypernian usage`. 1. (Optional) For **Description**, add a description that appears in [Checkout](https://docs.stripe.com/payments/checkout.md), in the [customer portal](https://docs.stripe.com/customer-management.md) and in [quotes](https://docs.stripe.com/quotes.md). 1. Select **Recurring**. 1. Under **Billing period**, select **More pricing options**. 1. On the **Add price** page, do the following: 1. Under **Choose your pricing model**, select **Usage-based**. 1. Choose your pricing structure: - For the Hypernian example, select **Per Tier and Graduated**. - In the first row of the grid, set **First unit** to 0, **Last unit** to 1,000, **Per unit** to 0 USD, and **Flat fee** to 0 USD. - In the second row of the grid, set **First unit** to 1,001, **Last unit** to ∞, **Per unit** to 0.04 USD, and **Flat fee** to 0 USD. - After you create the first product, create another product to charge customers the 100 USD fee at the beginning of the month. A customer’s second invoice contains their usage fees from the previous month and their license fee for the upcoming month. - (Optional) To bill customers the initial license fee at the end of the month, update the first row of the grid to set the **Flat fee** to 100 USD. 1. Under **Meter**, select the meter you created previously. For the Hypernian example, select **Hypernian tokens** from the dropdown. 1. Select the **Billing period**. For the Hypernian example, select **Monthly**. 1. Click **Next**. 1. Click **Add Product**. #### API Locate your meter ID on the meter details page. ```curl curl https://api.stripe.com/v1/prices \ -u "<>:" \ -d currency=usd \ -d unit_amount=4 \ -d billing_scheme=per_unit \ -d "transform_quantity[divide_by]"=100 \ -d "transform_quantity[round]"=up \ -d "recurring[usage_type]"=metered \ -d "recurring[interval]"=month \ -d "recurring[meter]"={{METER_ID}} \ -d "product_data[name]"="Hypernian tokens" ``` Next, create the 100 USD monthly fee. #### Dashboard 1. On the [Product catalog](https://dashboard.stripe.com/products?active=true) page, click **Create product**. 1. On the **Add a product** page, do the following: - For **Name**, enter the name of your product. For the Hypernian example, enter `Hypernian license fee`. - (Optional) For **Description**, add a description that appears in [Checkout](https://docs.stripe.com/payments/checkout.md), in the [customer portal](https://docs.stripe.com/customer-management.md) and in [quotes](https://docs.stripe.com/quotes.md). - Select **Recurring**. - Under **Billing period**, select **More pricing options**. 1. On the **Add price** page, do the following: - Select **Recurring*. - For **Amount**, enter *100 USD*. - For **Currency**, select **USD**. - For **Billing Period**, select **Monthly**. - Click **Next**. - Click **Add Product**. #### API Create the products and prices for tiered pricing. ```curl curl https://api.stripe.com/v1/products \ -u "<>:" \ -d description="Usage fees for Hypernian" \ -d name="Hypernian Usage" ``` ```curl curl https://api.stripe.com/v1/prices \ -u "<>:" \ -d product="{{PRODUCT_ID}}" \ -d currency=usd \ -d billing_scheme=tiered \ -d "recurring[interval]"=month \ -d "recurring[interval_count]"=1 \ -d "recurring[usage_type]"=metered \ -d "recurring[meter]"="{{METER_ID}}" \ -d "tiers[0][flat_amount_decimal]"=0 \ -d "tiers[0][unit_amount_decimal]"=0 \ -d "tiers[0][up_to]"=1000 \ -d "tiers[1][flat_amount_decimal]"=0 \ -d "tiers[1][unit_amount_decimal]"=4 \ -d "tiers[1][up_to]"=inf \ -d tiers_mode=graduated ``` Next, create a product and price for the license fee. ```curl curl https://api.stripe.com/v1/products \ -u "<>:" \ -d description="License fee for Hypernian" \ -d name="Hypernian License fee" ``` ```curl curl https://api.stripe.com/v1/prices \ -u "<>:" \ -d billing_scheme=per_unit \ -d currency=usd \ -d product="{{PRODUCT_ID}}" \ -d "recurring[interval]"=month \ -d "recurring[interval_count]"=1 \ -d "recurring[usage_type]"=licensed \ -d unit_amount_decimal=10000 ``` ## Set up promotion codes To set up a promotion code, you create a coupon and promotion code, then apply the promotion code to a subscription. ### Create a coupon Create coupons in the Dashboard or with the [API](https://docs.stripe.com/api/coupons/create.md): #### Dashboard 1. In the Dashboard, open the [Products](https://dashboard.stripe.com/test/products?active=true) page. 1. Click **Coupons**. 1. Click **+New**. 1. In the **Create a coupon** dialog, enter the coupon’s parameters. 1. Click **Create coupon**. The following are all the settings for coupons. The name is the only setting you can edit after you create the coupon. | Setting | Description | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Name** | The name of the coupon that appears on receipts and invoices. | | **ID** (optional) | A unique identifier for the coupon in the API. If you leave this field blank, Stripe generates an ID for you. | | **Type** | Determines whether a coupon discounts a subscription by a fixed amount or by a percentage. | | **Percentage off** or **Discount amount** | Indicates how much the coupon actually discounts. If you sell in multiple currencies, a single coupon can define different discount amounts for different currencies. Multi-currency coupons follow the same rules as [multi-currency prices](https://docs.stripe.com/products-prices/how-products-and-prices-work.md#multiple-currencies). | | **Apply to specific products** (optional) | Limits the type of items that the coupon can apply to. | | **Duration** | Indicates how long the coupon is valid for. | | **Redemption limits** (optional) | Allows you to limit when a customer can redeem the coupon and the number of times a coupon can be redeemed. | | **Codes** (optional) | Allows you to create [promotion codes](https://docs.stripe.com/get-started/use-cases/usage-based-billing.md#promotion-codes--promotion-codes) for the coupon. | #### API ```curl curl https://api.stripe.com/v1/coupons \ -u "<>:" \ -d duration=once \ -d id=free-period \ -d percent_off=100 ``` The following table contains coupon parameters. | Setting | Description | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | A unique identifier for the coupon. | | `percent_off` or `amount_off` | The amount that is taken off the subtotal for the duration of the coupon. | | `currency` (if `amount_off` is set) | The three-letter ISO code for the currency of the amount to take off. | | `currency_options` (if `amount_off` is set) (optional) | If you sell in multiple currencies, amounts to take off the subtotal for different currencies. Multi-currency coupons follow the same rules as [multi-currency prices](https://docs.stripe.com/products-prices/how-products-and-prices-work.md#multiple-currencies). | | `duration` | Indicates how long the coupon is valid for. Values include **once**, **forever**, or **repeating**. | | `max_redemptions` (optional) | Maximum number of times a coupon can be redeemed across all customers. | | `redeem_by` (optional) | The latest date at which you can apply this coupon to customers. | | `applies_to` (optional) | Limits the items in an invoice that the coupon can apply to. | ### Set eligible products #### Dashboard To set the products that are eligible for discounts, add the relevant product in the **Apply to specific product** field. Any promotion codes that are associated with the coupon are also restricted to this list of eligible products. If you configure a coupon to apply to specific products and a subscription doesn’t have any applicable products, no discount is applied when you add the coupon to the subscription. #### API To set the products eligible for discounts, add the relevant product IDs to the `applies_to` hash in the coupon. This list of eligible products also applies to promotion codes associated with the coupon. If you configure a coupon to apply to specific products and a subscription doesn’t have any applicable products, no discount is applied when you add the coupon to the subscription. When you [make changes](https://docs.stripe.com/billing/subscriptions/change.md) to a subscription, Stripe calculates the proration and applies any existing discounts. You can’t discount proration line items further on the invoice that’s generated. ### Apply coupons to subscriptions After you’ve created coupons, create a discount by applying them to a subscription. You can apply the coupon when you create the subscription or by [updating a customer’s existing subscription](https://docs.stripe.com/api.md#update_subscription). #### Dashboard 1. In the Dashboard, open the [Subscriptions](https://dashboard.stripe.com/test/subscriptions?status=active) page. 1. Click the relevant subscription. 1. Click **Actions**. 1. Click **Update subscription**. 1. Click **Add coupon**. 1. Select one or more coupons from the dropdown menus and click **Submit**. #### API ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d customer="{{CUSTOMER_ID}}" \ -d "items[0][price]"="{{PRICE_ID}}" \ -d "discounts[0][coupon]"=free-period ``` You can still create a subscription when a customer doesn’t have a stored payment method if [no immediate payment](https://docs.stripe.com/billing/subscriptions/deferred-payment.md) is required after you apply coupons to it. ### Apply coupons to Checkout Apply coupons to subscriptions in a Checkout Session by setting the `discounts` parameter in the [API](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-discounts). To create a session with an applied discount, pass the coupon ID in the `coupon` parameter of the `discounts` array. #### curl ```bash curl https://api.stripe.com/v1/checkout/sessions \ -u <>: \ -d "payment_method_types[]"=card \ -d "line_items[][price]"="{{PRICE_ID}}" \ -d "line_items[][quantity]"=1 \ -d mode=subscription \-d "discounts[][coupon]"="{{COUPON_ID}}" \ -d success_url="https://example.com/success" ``` ## Build a checkout page and subscribe your customer Use [Stripe Elements](https://docs.stripe.com/payments/elements.md) and the [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions.md) to build a checkout page as part of [a fully customized subscriptions integration](https://docs.stripe.com/billing/subscriptions/build-subscriptions.md?payment-ui=elements&api-integration=checkout). To set up a subscription you need to create a *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments). You can collect address and payment details with the [Express Checkout Element](https://docs.stripe.com/get-started/use-cases/usage-based-billing.md#express-checkout-element). Then, you can use that information to create the subscription with the [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions.md). > To test this in a sandbox, you can attach the customer you created to the test clock you created while [setting up the testing environment](https://docs.stripe.com/get-started/use-cases/usage-based-billing.md#set-up-testing). ### Set up Stripe Install the Stripe client of your choice: #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # 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](https://docs.stripe.com/get-started/use-cases/usage-based-billing.md#pricing-model). For additional install options, see [Get started with the Stripe CLI](https://docs.stripe.com/stripe-cli.md). ### Create a Customer If your Connect platform uses [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer), use our [guide](https://docs.stripe.com/connect/use-accounts-as-customers.md) to replace `Customer` and event references in your code with the equivalent Accounts v2 API references. Stripe needs a *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 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](https://docs.stripe.com/elements/address-element.md) page. ```html
``` ```javascript 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. > Make sure you store the [Customer ID](https://docs.stripe.com/api/customers/object.md#customer_object-id) to use in the Checkout Session ```curl curl https://api.stripe.com/v1/customers \ -u "<>:" \ -d email={{CUSTOMER_EMAIL}} \ -d name={{CUSTOMER_NAME}} \ -d "shipping[address][city]"=Brothers \ -d "shipping[address][country]"=US \ -d "shipping[address][line1]"="27 Fredrick Ave" \ -d "shipping[address][postal_code]"=97712 \ -d "shipping[address][state]"=CA \ -d "shipping[name]"={{CUSTOMER_NAME}} \ -d "address[city]"=Brothers \ -d "address[country]"=US \ -d "address[line1]"="27 Fredrick Ave" \ -d "address[postal_code]"=97712 \ -d "address[state]"=CA ``` ### Enable payment methods By default, Stripe uses your [payment methods settings](https://dashboard.stripe.com/settings/payment_methods) to determine which payment methods are enabled in the Express Checkout Element. You can also configure specific payment methods on your Checkout Session using the [payment_method_types](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-payment_method_types) attribute. ### Supported payment methods Apple Pay and Google Pay are automatically enabled when using the `card` payment method type. When using Link, you must also pass the `card` payment method type. | Payment method | Payment method type | | -------------- | ------------------- | | Amazon Pay | `amazon_pay` | | Apple Pay | `card` | | Google Pay | `card` | | Klarna | `klarna` | | Link | `link`, `card` | | PayPal | `paypal` | ### Create the Checkout Session On the back end of your application, define an endpoint that [creates the session](https://docs.stripe.com/api/checkout/sessions/create.md) for your front end to call. You need the price ID of the subscription the customer is signing up for—your front end passes this value. ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d customer={{CUSTOMER_ID}} \ -d ui_mode=custom \ -d "line_items[0][price]"={{PRICE_ID}} \ -d "line_items[0][quantity]"=1 \ -d return_url={{RETURN_URL}} \ -d mode=subscription ``` ### Initialize Checkout #### HTML + JS Call [initCheckout](https://docs.stripe.com/js/custom_checkout/init), passing in `clientSecret`. `initCheckout` returns a [Checkout](https://docs.stripe.com/js/custom_checkout) object that contains data from the Checkout Session and methods to update it. Read the `total` and `lineItems` from [actions.getSession()](https://docs.stripe.com/js/custom_checkout/session), and display them in your UI. This lets you turn on new features with minimal code changes. For example, adding [manual currency prices](https://docs.stripe.com/payments/custom/localize-prices/manual-currency-prices.md) requires no UI changes if you display the `total`. ```html
``` ```javascript const clientSecret = fetch('/create-checkout-session', {method: 'POST'}) .then((response) => response.json()) .then((json) => json.client_secret); const checkout = stripe.initCheckout({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}`); } ``` #### React Wrap your application with the [CheckoutProvider](https://docs.stripe.com/js/react_stripe_js/checkout/checkout_provider) component, passing in `clientSecret` and the `stripe` instance. ```jsx import React from 'react'; import {CheckoutProvider} from '@stripe/react-stripe-js/checkout'; import CheckoutForm from './CheckoutForm'; const clientSecret = fetch('/create-checkout-session', {method: 'POST'}) .then((response) => response.json()) .then((json) => json.client_secret); const App = () => { return ( ); }; export default App; ``` Access the [Checkout](https://docs.stripe.com/js/custom_checkout) object in your checkout form component by using the `useCheckout()` hook. The `Checkout` object contains data from the Checkout Session and methods to update it. Read the `total` and `lineItems` from the `Checkout` object, and display them in your UI. This lets you enable features with minimal code changes. For example, adding [manual currency prices](https://docs.stripe.com/payments/custom/localize-prices/manual-currency-prices.md) requires no UI changes if you display the `total`. ```jsx import React from 'react'; import {useCheckout} from '@stripe/react-stripe-js/checkout'; const CheckoutForm = () => {const checkoutState = useCheckout(); if (checkoutState.type === 'loading') { return (
Loading...
); } if (checkoutState.type === 'error') { return (
Error: {checkoutState.error.message}
); } return (
{JSON.stringify(checkoutState.checkout.lineItems, null, 2)} {/* A formatted total amount */} Total: {checkoutState.checkout.total.total.amount}
); }; ``` ### Set up a trial period To offer a free 7-day trial period of your service, pass in [trial_period_days](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-subscription_data-trial_period_days) when you create the Checkout Session. ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d customer={{CUSTOMER_ID}} \ -d ui_mode=custom \ -d "line_items[0][price]"={{PRICE_ID}} \ -d "line_items[0][quantity]"=1 \ -d return_url={{RETURN_URL}} \ -d mode=subscription \ -d "subscription_data[trial_period_days]"=7 ``` ### Set up Stripe Elements #### HTML + JS The Express Checkout 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. ```html Checkout ``` Retrieve the client secret from your server: ```javascript // 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('<>'); ``` Create a `fetchClientSecret` function to retrieve the client secret from your server: ```javascript const clientSecret = fetch('/create-checkout-session', {method: 'POST'}) .then((response) => response.json()) .then((json) => json.client_secret); ``` Create the Checkout instance: ```javascript const checkout = stripe.initCheckout({ clientSecret }); ``` #### React The Express Checkout Element is automatically available as a feature of Stripe.js. Install [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) and the [Stripe.js loader](https://www.npmjs.com/package/@stripe/stripe-js) from the npm public registry. ```bash npm install --save @stripe/react-stripe-js @stripe/stripe-js ``` Create `clientSecret` as a `Promise | string` containing the client secret returned by your server. Wrap your application with the [CheckoutProvider](https://docs.stripe.com/js/react_stripe_js/checkout/checkout_provider) component, passing in `clientSecret` and the `stripe` instance. ```jsx import React from 'react'; import {CheckoutProvider} from '@stripe/react-stripe-js/checkout'; import {loadStripe} from '@stripe/stripe-js'; import CheckoutForm from './CheckoutForm'; // Make sure to call `loadStripe` outside of a component's render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe('<>'); const clientSecret = fetch('/create-checkout-session', {method: 'POST'}) .then((response) => response.json()) .then((json) => json.client_secret); const App = () => { return ( ); }; export default App; ``` ### Create and mount the Express Checkout Element The Express Checkout Element contains an iframe that securely sends the payment information to Stripe over an HTTPS connection. The checkout page address must also start with `https://`, rather than `http://`, for your integration to work. #### HTML + JS The Express Checkout Element needs a place to live on your payment page. Create an empty DOM node (container) with a unique ID in your payment form. ```html
``` When the form has loaded, create an instance of the Express Checkout Element and mount it to the container DOM node: ```javascript // Create and mount the Express Checkout Element const expressCheckoutElement = checkout.createExpressCheckoutElement(); expressCheckoutElement.mount('#express-checkout-element'); ``` #### React Add the `ExpressCheckoutElement` component to your payment page: ```jsx import React from 'react'; import {useCheckout, ExpressCheckoutElement} from '@stripe/react-stripe-js/checkout'; const CheckoutForm = () => { const checkoutState = useCheckout(); if (checkoutState.type === 'error') { return
Error: {checkoutState.error.message}
; } return (
); }; ``` ### Collect customer details and display line items The Checkout Session you created on the server automatically determines the line items, total amount, and available payment methods. The Express Checkout Element uses this information to display the appropriate interface. #### HTML + JS ### Handle payment confirmation Listen to the [confirm event](https://docs.stripe.com/js/elements_object/express_checkout_element_confirm_event) when your customer finalizes their payment: ```javascript const loadActionsResult = await checkout.loadActions(); if (loadActionsResult.type === 'success') { expressCheckoutElement.on('confirm', (event) => { loadActionsResult.actions.confirm({expressCheckoutConfirmEvent: event}); }); } ``` ### Handle dynamic updates If you need to update the Checkout Session based on customer selections (such as shipping address or shipping rate changes), you can listen to events and update the session: ```javascript expressCheckoutElement.on('shippingaddresschange', async (event) => { const response = await fetch('/update-session-shipping', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: checkout.session.id, shippingAddress: event.address }) }); const result = await response.json(); if (result.error) { event.reject(); } else { event.resolve(); } }); expressCheckoutElement.on('shippingratechange', async (event) => { const response = await fetch('/update-session-shipping-rate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: checkout.session.id, shippingRateId: event.shippingRate.id }) }); const result = await response.json(); if (result.error) { event.reject(); } else { event.resolve(); } }); ``` #### React ### Handle payment confirmation Handle the `confirm` event when your customer finalizes their payment: ```jsx import {useCheckout, ExpressCheckoutElement} from '@stripe/react-stripe-js/checkout'; const CheckoutForm = () => { const checkoutState = useCheckout(); if (checkoutState.type === 'loading') { return
Loading...
; } else if (checkoutState.type === 'error') { return
Error: {checkoutState.error.message}
; } const handleConfirmExpressCheckout = (event) => { if (checkoutState.type === 'success') { checkoutState.checkout.confirm({expressCheckoutConfirmEvent: event}); } }; return (
{checkoutState.type === 'success' && (
          {JSON.stringify(checkoutState.checkout.lineItems, null, 2)}
          Total: {checkoutState.checkout.total?.amount}
        
)}
); }; ``` ### Handle dynamic updates If you need to update the Checkout Session based on customer selections (such as shipping address or shipping rate changes), you can listen to events and update the session: ```jsx const onShippingAddressChange = async ({resolve, reject, address}) => { const response = await fetch('/update-session-shipping', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: checkout.session.id, shippingAddress: address }) }); const result = await response.json(); if (result.error) { reject(); } else { resolve(); } }; const onShippingRateChange = async ({resolve, reject, shippingRate}) => { const response = await fetch('/update-session-shipping-rate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: checkout.session.id, shippingRateId: shippingRate.id }) }); const result = await response.json(); if (result.error) { reject(); } else { resolve(); } }; ``` ### Submit the payment #### HTML + JS Render a **Pay** button that calls [confirm](https://docs.stripe.com/js/custom_checkout/confirm) from the `Checkout` instance to submit the payment. ```html
``` ```js const checkout = stripe.initCheckout({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; } }); }); } ``` #### React Render a **Pay** button that calls [confirm](https://docs.stripe.com/js/custom_checkout/confirm) from [useCheckout](https://docs.stripe.com/js/react_stripe_js/checkout/use_checkout) to submit the payment. ```jsx import React from 'react'; import {useCheckout} from '@stripe/react-stripe-js/checkout'; const PayButton = () => { const checkoutState = useCheckout(); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); if (checkoutState.type !== "success") { return null; } const handleClick = () => { setLoading(true);checkoutState.checkout.confirm().then((result) => { if (result.type === 'error') { setError(result.error) } setLoading(false); }) }; return (
{error &&
{error.message}
}
) }; export default PayButton; ``` ## Listen for webhook events To set up your back-end logic, you need to set up an endpoint that can listen for *webhook events* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) 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. During development, use the [Events](https://dashboard.stripe.com/events) tab in Workbench to monitor and [filter events](https://docs.stripe.com/workbench/guides.md#filter-events). For production, set up a webhook endpoint URL in the Dashboard, or use the [Webhook Endpoints API](https://docs.stripe.com/api/webhook_endpoints.md). See [Subscription events](https://docs.stripe.com/billing/subscriptions/webhooks.md#events) for more details about subscription-specific webhooks. ## Provision access Grant customers access to your service upon successful payment. In most cases, you can provision access to your service when: - You receive the [invoice.paid](https://docs.stripe.com/api/events/types.md#event_types-invoice.paid) event. - The subscription status is `active`. You can check the status in the [Subscriptions page](https://dashboard.stripe.com/subscriptions) in the Dashboard or by using the API to [retrieve the subscription](https://docs.stripe.com/api/subscriptions/retrieve.md) and checking the [status](https://docs.stripe.com/api/subscriptions/object.md#subscription_object-status) field. For more details about subscription-specific webhooks, [Subscription events](https://docs.stripe.com/billing/subscriptions/webhooks.md#events). ## Test your integration Test your integration before going live. | Scenario | Tests | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Successful subscription flows | - Use [test card number](https://docs.stripe.com/testing.md?testing-method=card-numbers#test-code) `4242424242424242` for successful payments - Verify subscription creation and customer provisioning | | Failure scenarios | - Use [test card number](https://docs.stripe.com/testing.md#declined-payments) `4000000000000002` to test [generic card declines](https://docs.stripe.com/declines/codes.md#generic_decline) - Verify error handling and customer communication - Test [insufficient funds](https://docs.stripe.com/declines/codes.md#insufficient_funds) scenarios with `4000000000009995` | | Time-based events | - Use test clocks to simulate subscription renewals - Test [trial period expiration](https://docs.stripe.com/billing/testing.md#trials) - Verify [webhook event handling](https://docs.stripe.com/billing/testing.md#webhooks) | | Promotion codes | - Apply promotion codes during checkout - Verify correct discount application - Test code expiration and usage limits | | Proration | - Test proration calculations for scenarios such as mid-cycle upgrades by using test clocks - Verify proration invoice handling by [previewing invoices](https://docs.stripe.com/billing/subscriptions/prorations.md#preview-proration) | | Cancellations | - Test cancellation logic during trial periods - Verify cancellations initiated from the [Customer Portal](https://docs.stripe.com/customer-management.md) | ## Go live 1. In the Dashboard, open your [Account settings](https://dashboard.stripe.com/account/onboarding). 1. Enter your business type, tax details, business details, personal verification information, and customer-facing information (for example, a statement descriptor). 1. Add bank details to confirm where to pay out your money. 1. Set up two-step authentication to secure your account. 1. You can optionally add automatic tax collection or revenue-based climate donations. 1. Review the information you entered, and click **Agree and submit**. 1. After you activate your profile, Stripe updates you from sandbox mode to live mode. Learn more about [activating your Stripe account](https://docs.stripe.com/get-started/account/activate.md). ## Next steps After setting up your integration, we recommend that you implement the following features: - Set up the [customer portal](https://docs.stripe.com/customer-management.md) to let your customers manage their subscriptions and payment information. - Set up [Smart Retries](https://docs.stripe.com/billing/revenue-recovery/smart-retries.md#smart-retries) to let Stripe’s AI choose the best times to retry failed payment attempts to increase the chance of successfully paying an invoice. - Set up [billing automations](https://docs.stripe.com/billing/automations.md) to build custom, automated workflows to streamline your business processes, enhance customer communication, and improve revenue recovery efforts.