Create fixed-price subscriptions with Elements

    Learn how to offer multiple pricing options to your customers and charge them a fixed amount each month.

    This guide walks you through how to create fixed-price subscriptions for a photo hosting service. It uses Stripe Elements to create a custom payment form you embed in your application. You can find the complete code on GitHub.

    What you’ll build

    This guide shows you how to:

    • Model your subscriptions with Products and Prices
    • Create a signup flow
    • Collect payment information
    • Create Subscriptions and charge customers
    • Handle payment errors
    • Let customers change their plan or cancel the subscription

    Example application

    How to model it on Stripe

    API object relationships

    API object definitions

    Object Definition
    Products and Prices A product represents the item your customer subscribes to. The price represents how much and how often to charge for the product. In this guide, two products are created, each with a single price.
    Customer A customer is associated with payment-related objects and with subscriptions. In this guide, payment and subscription information is added to the customer object.
    PaymentIntent A payment intent tracks the status of a specified payment. In this guide, payment intents are associated with invoices, which represent the overall payment status of the subscription.
    PaymentMethod Payment methods represent your customer's payment instruments. In this guide, only credit cards are accepted.
    Subscription A subscription lets you charge a customer's payment method for a specified product on a recurring basis.
    Invoice An invoice is a statement of amounts owed by a customer, generated automatically from a subscription. You track subscription provisioning with products, and payment with invoices.

    1 Install Stripe libraries and tools

    Install the Stripe client of your choice:

    # Available as a gem sudo gem install stripe
    # If you use bundler, you can add this line to your Gemfile gem 'stripe'
    # Install through pip pip install --upgrade stripe
    # Or find the Stripe package on http://pypi.python.org/pypi/stripe/
    # Install the PHP library via Composer composer require stripe/stripe-php
    # Or download the source directly: https://github.com/stripe/stripe-php/releases
    /* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
    <!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
    # For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
    # Install via npm npm install --save stripe

    And install the Stripe CLI. The CLI provides the webhook testing you’ll need, and you can run it to create your products and prices.

    To install the Stripe CLI with homebrew, run:

    brew install stripe/stripe-cli/stripe

    To install the Stripe CLI on Debian and Ubuntu-based distributions:

    1. Add Bintray’s GPG key to the apt sources keyring:
    sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 379CE192D401AB61
    1. Add stripe-cli’s apt repository to the apt sources list:
    echo "deb https://dl.bintray.com/stripe/stripe-cli-deb stable main" | sudo tee -a /etc/apt/sources.list
    1. Update the package list:
    sudo apt-get update
    1. Install the CLI:
    sudo apt-get install stripe

    To install the Stripe CLI on RedHat and CentOS-based distributions:

    1. Add stripe-cli’s yum repository to the yum sources list:
    wget https://bintray.com/stripe/stripe-cli-rpm/rpm -O bintray-stripe-stripe-cli-rpm.repo && sudo mv bintray-stripe-stripe-cli-rpm.repo /etc/yum.repos.d/
    1. Update the package list:
    sudo yum update
    1. Install the CLI:
    sudo yum install stripe

    To install the Stripe CLI with Scoop, run:

    scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
    scoop install stripe

    To install the Stripe CLI on macOS without homebrew:

    1. Download the latest mac-os tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
    2. Unzip the file: tar -xvf stripe_X.X.X_mac-os_x86_64.tar.gz

    Optionally, install the binary in a location where you can execute it globally (e.g., ~/usr/local/bin).

    To install the Stripe CLI on Linux without a package manager:

    1. Download the latest linux tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
    2. Unzip the file: tar -xvf stripe_X.X.X_linux_x86_64.tar.gz
    3. Run the executable: ./stripe

    To install the Stripe CLI on Windows without Scoop, run:

    1. Download the latest windows tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
    2. Unzip the stripe_X.X.X_windows_x86_64.zip file
    3. Run the unzipped .exe file

    The Stripe CLI is also available as a Docker image. To install the latest version, run:

    docker run --rm -it stripe/stripe-cli latest

    To run the Stripe CLI, you must also pair it with your Stripe account. Run stripe login and follow the prompts. For more information, see the Stripe CLI documentation page.

    2 Create the business model Stripe CLI or Dashboard

    You create your products and their pricing options with the Stripe CLI or in the Dashboard. A service with two different options needs a product and a price for each option.

    In this sample, each product bills at monthly intervals. The price for one product is 5 USD, and the other is 15 USD.

    Create the product object for the premium service:

    stripe products create \ --name="Billing Guide: Premium Service" \ --type=service \ --description="Premium service with extra features"

    The response looks like:

    { "id": "prod_H94k5odtwJXMtQ",
    See all 21 lines "object": "product", "active": true, "attributes": [ ], "created": 1587577341, "description": "Premium service with extra features", "images": [ ], "livemode": false, "metadata": { }, "name": "Billing Guide: Premium Service", "statement_descriptor": null, "type": "service", "unit_label": null, "updated": 1587577341 }

    Create the price for the premium product, passing the product ID from the response:

    stripe prices create \ -d product={{ PROD_ID_FOR_PREMIUM_SERVICE }} \ -d unit_amount=1500 \ -d currency=usd \ -d recurring[interval]=month

    Create the product object for the basic service:

    stripe products create \ --name="Billing Guide: Basic Service" \ --type=service \ --description="Basic service with minimum features"

    Create the price object for the basic product, passing the product ID from the response:

    stripe prices create \ -d product={{ PROD_ID_FOR_BASIC_SERVICE }} \ -d unit_amount=500 \ -d currency=usd \ -d recurring[interval]=month

    The response to the calls to the Prices API look like:

    { "id": "price_HGd7M3DV3IMXkC", "object": "price", "product": "prod_HGd6W1VUqqXGvr", "type": "recurring", "currency": "usd", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed" }, "active": true,
    See all 27 lines "billing_scheme": "per_unit", "created": 1589319695, "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "unit_amount": 1500, "unit_amount_decimal": "1500", "tiers": null, "tiers_mode": null, "transform_quantity": null }

    Record the price IDs from each call to the Prices API so they can be used in subsequent steps.

    Navigate to the Create a product page, and create two products. Add one price for each product, each with a monthly billing interval:

    • Basic product
      • Price: 5.00 USD
    • Premium product
      • Price: 15.00 USD

    After you create the prices, record the price IDs so they can be used in subsequent steps. Each ID is displayed on the pricing price page and should look similar to this: price_G0FvDp6vZvdwRZ.

    3 Create the Stripe customer Client and Server

    In this sample, you create a customer from the provided email.

    On your application frontend, pass the customer email to a backend endpoint.

    function createCustomer() { let billingEmail = document.querySelector('#email').value; return fetch('/create-customer', { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: billingEmail }) }) .then(response => { return response.json(); }) .then(result => { // result.customer.id is used to map back to the customer object // result.setupIntent.client_secret is used to create the payment method return result; }); }

    On the backend, define the endpoint to create the customer object.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/create-customer' do content_type 'application/json' data = JSON.parse request.body.read # Create a new customer object customer = Stripe::Customer.create( email: data['email'] ) # Recommendation: save the customer.id in your database. { 'customer': customer }.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/create-customer', methods=['POST']) def create_customer(): # Reads application/json and returns a response data = json.loads(request.data) try: # Create a new customer object customer = stripe.Customer.create( email=data['email'] ) # Recommendation: save the customer.id in your database. return jsonify( customer=customer ) except Exception as e: return jsonify(error=str(e)), 403
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/create-customer', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); // Create a new customer object $customer = \Stripe\Customer::create([ 'email' => $body->email ]); // Recommendation: save the customer.id in your database. return $response->withJson(['customer' => $customer]); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/create-customer", (request, response) -> { response.type("application/json"); CreateCustomerBody postBody = gson.fromJson(request.body(), CreateCustomerBody.class); // Create a new customer object Map<String, Object> customerParams = new HashMap<String, Object>(); customerParams.put("email", postBody.getEmail()); Customer customer = Customer.create(customerParams); // Recommendation: save the customer.id in your database. Map<String, Object> responseData = new HashMap<>(); responseData.put("customer", customer); return gson.toJson(responseData); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/create-customer', async (req, res) => { // Create a new customer object const customer = await stripe.customers.create({ email: req.body.email, }); // Recommendation: save the customer.id in your database. res.send({ customer }); });

    Example customer response:

    { "id": "cus_HAwLCi6nxPYcsl", "object": "customer", "address": null, "balance": 0, "created": 1588007418, "currency": "usd", "default_source": null, "delinquent": false, "description": "My First Test Customer",
    See all 45 lines "discount": null, "email": null, "invoice_prefix": "2D409C0", "invoice_settings": { "custom_fields": null, "default_payment_method": null, "footer": null }, "livemode": false, "metadata": {}, "name": null, "next_invoice_sequence": 1, "phone": null, "preferred_locales": [], "shipping": null, "sources": { "object": "list", "data": [], "has_more": false, "url": "/v1/customers/cus_HAwLCi6nxPYcsl/sources" }, "subscriptions": { "object": "list", "data": [], "has_more": false, "url": "/v1/customers/cus_HAwLCi6nxPYcsl/subscriptions" }, "tax_exempt": "none", "tax_ids": { "object": "list", "data": [], "has_more": false, "url": "/v1/customers/cus_HAwLCi6nxPYcsl/tax_ids" } }

    4 Collect payment information Client

    Let your new customer choose a plan and provide payment information. In this example, the customer chooses between Basic and Premium.

    Then use Stripe Elements to collect card information, and customize Elements to match the look-and-feel of the application.

    Selecting a plan and collecting payment details

    Set up Stripe Elements

    Stripe Elements 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. Do not 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> <link rel="stylesheet" href="StripeElements.css"> </head>

    Create an instance of Elements 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/account/apikeys var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); var elements = stripe.elements();

    Add Elements to your page

    Elements needs a place to live in your payment form. Create empty DOM nodes (containers) with unique IDs in your payment form and then pass those IDs to Elements.

    <!-- Use the CSS tab above to style your Element's container. --> <body> <form id="subscription-form"> <div id="card-element" class="MyCardElement"> <!-- Elements will create input elements here --> </div> <!-- We'll put the error messages in this element --> <div id="card-errors" role="alert"></div> <button type="submit">Subscribe</button> </form> </body>
    /** * Shows how you can use CSS to style your Element's container. */ .MyCardElement { height: 40px; padding: 10px 12px; width: 100%; color: #32325d; background-color: white; border: 1px solid transparent; border-radius: 4px; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } .MyCardElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .MyCardElement--invalid { border-color: #fa755a; } .MyCardElement--webkit-autofill { background-color: #fefde5 !important; }

    After the form loads, create an instance of an Element and mount it to the Element container.

    // Set up Stripe.js and Elements to use in checkout form var style = { base: { color: "#32325d", fontFamily: '"Helvetica Neue", Helvetica, sans-serif', fontSmoothing: "antialiased", fontSize: "16px", "::placeholder": { color: "#aab7c4" } }, invalid: { color: "#fa755a", iconColor: "#fa755a" } }; var cardElement = elements.create("card", { style: style }); cardElement.mount("#card-element");

    The card Element simplifies the form and minimizes the number of fields required by inserting a single, flexible input field that securely collects all necessary card details. For a full list of supported Element types, refer to our Stripe.js reference documentation.

    Use the test card number 4242 4242 4242 4242, any three-digit CVC number, any expiration date in the future, and any five-digit ZIP code.

    Elements validates user input as it is typed. To help your customers catch mistakes, listen to change events on the card Element and display any errors.

    cardElement.on('change', showCardError) function showCardError(event) { let displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } }
    cardElement.on('change', (event) => { showCardError(event); }); function showCardError(event) { let displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } }

    ZIP code validation depends on your customer’s billing country. Use our international test cards to experiment with other postal code formats.

    Install React Stripe.js and the Stripe.js loader from the npm public registry.

    npm install --save @stripe/react-stripe-js @stripe/stripe-js

    We also provide a UMD build for sites that are not using npm or modules.

    Include the Stripe.js script which exports a global Stripe function and the UMD build of React Stripe.js which exports a global ReactStripe object. Always load the Stripe.js script directly from js.stripe.com to remain PCI compliant. Do not include the script in a bundle or host a copy of it yourself.

    <!-- Stripe.js --> <script src="https://js.stripe.com/v3/"></script> <!-- React Stripe.js development build --> <script src="https://unpkg.com/@stripe/react-stripe-js@latest/dist/react-stripe.umd.js"></script> <!-- When you are ready to deploy your site to production, remove the above development script, and include the following production build. --> <script src="https://unpkg.com/@stripe/react-stripe-js@latest/dist/react-stripe.umd.min.js"></script>

    Add Stripe.js and Elements to your page

    To use Element components, wrap the root of your React app in an Elements provider. Call loadStripe with your publishable key and pass the returned Promise to the Elements provider.

    import React from 'react'; import ReactDOM from 'react-dom'; import {Elements} from '@stripe/react-stripe-js'; 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("pk_test_TYooMQauvdEDq54NiTphI7jx"); function App() { return ( <Elements stripe={stripePromise}> <CheckoutForm /> </Elements> ); }; ReactDOM.render(<App />, document.getElementById('root'));

    Add and configure a CardElement component

    Use individual Element components, such as CardElement, to build your form.

    /** * Use the CSS tab above to style your Element's container. */ import React from 'react'; import {CardElement} from '@stripe/react-stripe-js'; import './CardSectionStyles.css' const CARD_ELEMENT_OPTIONS = { style: { base: { color: "#32325d", fontFamily: '"Helvetica Neue", Helvetica, sans-serif', fontSmoothing: "antialiased", fontSize: "16px", "::placeholder": { color: "#aab7c4", }, }, invalid: { color: "#fa755a", iconColor: "#fa755a", }, }, }; function CardSection() { return ( <label> Card details <CardElement options={CARD_ELEMENT_OPTIONS} /> </label> ); }; export default CardSection;
    /** * Shows how you can use CSS to style your Element's container. * These classes are added to your Stripe Element by default. * You can override these classNames by using the options passed * to the CardElement component. * https://stripe.com/docs/js/elements_object/create_element?type=card#elements_create-options-classes */ .StripeElement { height: 40px; padding: 10px 12px; width: 100%; color: #32325d; background-color: white; border: 1px solid transparent; border-radius: 4px; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } .StripeElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .StripeElement--invalid { border-color: #fa755a; } .StripeElement--webkit-autofill { background-color: #fefde5 !important; }

    Elements are completely customizable. You can style Elements to match the look and feel of your site, providing a seamless checkout experience for your customers. It’s also possible to style various input states, for example when the Element has focus.

    The CardElement simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card and billing details. Otherwise, combine CardNumberElement, CardExpiryElement, and CardCvcElement elements for a flexible, multi-input card form.

    5 Save payment details and create the subscription Client and Server

    On the frontend, save the payment details you just collected to a payment method.

    function createPaymentMethod(cardElement, customerId, priceId) { return stripe .createPaymentMethod({ type: 'card', card: cardElement, }) .then((result) => { if (result.error) { displayError(error); } else { createSubscription({ customerId: customerId, paymentMethodId: result.paymentMethod.id, priceId: priceId, }); } }); }

    Define the createSubscription function you just called, passing the customer, payment method, and price IDs to a backend endpoint.

    function createSubscription(customerId, paymentMethodId, priceId) { return ( fetch('/create-subscription', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, paymentMethodId: paymentMethodId, priceId: priceId, }), }) .then((response) => { return response.json(); }) // If the card is declined, display an error to the user. .then((result) => { if (result.error) { // The card had an error when trying to attach it to a customer. throw result; } return result; }) // Normalize the result to contain the object returned by Stripe. // Add the addional details we need. .then((result) => { return { paymentMethodId: paymentMethodId, priceId: priceId, subscription: result, }; }) // Some payment methods require a customer to be on session // to complete the payment process. Check the status of the // payment intent to handle these actions. .then(handlePaymentThatRequiresCustomerAction) // If attaching this card to a Customer object succeeds, // but attempts to charge the customer fail, you // get a requires_payment_method error. .then(handleRequiresPaymentMethod) // No more actions required. Provision your service for the user. .then(onSubscriptionComplete) .catch((error) => { // An error has happened. Display the failure to the user here. // We utilize the HTML element we created. showCardError(error); }) ); }

    On the backend, define the endpoint that creates the subscription for the frontend to call. The code updates the customer with the payment method, and then passes the customer ID to the subscription. The payment method is also assigned as the default payment method for the subscription invoices.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/create-subscription' do content_type 'application/json' data = JSON.parse request.body.read ​ # Attach the payment method to the customer begin Stripe::PaymentMethod.attach( data['paymentMethodId'], { customer: data['customerId'] } ) rescue Stripe::CardError => e halt 200, { 'Content-Type' => 'application/json' }, { 'error': { message: e.error.message } }.to_json end ​ # Set the default payment method on the customer Stripe::Customer.update( data['customerId'], invoice_settings: { default_payment_method: data['paymentMethodId'] } ) ​ # Create the subscription subscription = Stripe::Subscription.create( customer: data['customerId'], items: [ { price: 'price_H1NlVtpo6ubk0m' } ], expand: ['latest_invoice.payment_intent'] ) ​ subscription.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/create-subscription', methods=['POST']) def createSubscription(): data = json.loads(request.data) try: # Attach the payment method to the customer stripe.PaymentMethod.attach( data['paymentMethodId'], customer=data['customerId'], ) # Set the default payment method on the customer stripe.Customer.modify( data['customerId'], invoice_settings={ 'default_payment_method': data['paymentMethodId'], }, ) ​ # Create the subscription subscription = stripe.Subscription.create( customer=data['customerId'], items=[ { 'price': 'price_H1NlVtpo6ubk0m' } ], expand=['latest_invoice.payment_intent'], ) return jsonify(subscription) except Exception as e: return jsonify(error={'message': str(e)}), 200
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/create-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); ​ // Attach the payment method to the customer. try { $payment_method = \Stripe\PaymentMethod::retrieve( $body->paymentMethodId ); $payment_method->attach([ 'customer' => $body->customerId, ]); } catch (Exception $e) { return $response->withJson($e->jsonBody); } // Set the default payment method on the customer \Stripe\Customer::update($body->customerId, [ 'invoice_settings' => [ 'default_payment_method' => $body->paymentMethodId ] ]); // Create the subscription $subscription = \Stripe\Subscription::create([ 'customer' => $body->customerId, 'items' => [ [ 'price' => 'price_H1NlVtpo6ubk0m', ], ], 'expand' => ['latest_invoice.payment_intent'], ]); ​ return $response->withJson($subscription); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/create-subscription", (request, response) -> { response.type("application/json"); ​ CreateSubscriptionBody postBody = gson.fromJson(request.body(), CreateSubscriptionBody.class); Customer customer = Customer.retrieve(postBody.getCustomerId()); ​ try { // Attach the payment method to the customer PaymentMethod pm = PaymentMethod.retrieve(postBody.getPaymentMethodId()); pm.attach(PaymentMethodAttachParams.builder().setCustomer(customer.getId()).build()); } catch (CardException e) { // Since it's a decline, CardException will be caught Map<String, String> responseErrorMessage = new HashMap<>(); responseErrorMessage.put("message", e.getLocalizedMessage()); Map<String, Object> responseError = new HashMap<>(); responseError.put("error", responseErrorMessage); ​ return gson.toJson(responseError); } ​ // Change the default invoice settings on the customer to the new payment method Map<String, Object> customerParams = new HashMap<String, Object>(); Map<String, String> invoiceSettings = new HashMap<String, String>(); invoiceSettings.put("default_payment_method", postBody.getPaymentMethodId()); customerParams.put("invoice_settings", invoiceSettings); customer.update(customerParams); ​ // Create the subscription Map<String, Object> item = new HashMap<>(); item.put("price", "price_H1NlVtpo6ubk0m"); Map<String, Object> items = new HashMap<>(); items.put("0", item); Map<String, Object> params = new HashMap<>(); params.put("customer", postBody.getCustomerId()); params.put("items", items); ​ List<String> expandList = new ArrayList<>(); expandList.add("latest_invoice.payment_intent"); params.put("expand", expandList); ​ Subscription subscription = Subscription.create(params); ​ return subscription.toJson(); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/create-subscription', async (req, res) => { // Attach the payment method to the customer try { await stripe.paymentMethods.attach(req.body.paymentMethodId, { customer: req.body.customerId, }); } catch (error) { return res.status('402').send({ error: { message: error.message } }); } ​ // Change the default invoice settings on the customer to the new payment method await stripe.customers.update( req.body.customerId, { invoice_settings: { default_payment_method: req.body.paymentMethodId, }, } ); ​ // Create the subscription const subscription = await stripe.subscriptions.create({ customer: req.body.customerId, items: [{ price: 'price_H1NlVtpo6ubk0m' }], expand: ['latest_invoice.payment_intent'], }); ​ res.send(subscription); }); ​

    Here’s an example response. The minimum fields to store are highlighted, but you should store whatever your application will frequently access.

    { "id": "sub_HAwfLuEoLetEJ3", "object": "subscription", "application_fee_percent": null, "billing_cycle_anchor": 1588008574, "billing_thresholds": null, "cancel_at": null, "cancel_at_period_end": false, "canceled_at": null, "collection_method": "charge_automatically",
    See all 79 lines "created": 1588008574, "current_period_end": 1590600574, "current_period_start": 1588008574, "customer": "cus_HAwfE7FhKgxbJ6", "days_until_due": null, "default_payment_method": null, "default_source": null, "default_tax_rates": [], "discount": null, "ended_at": null, "items": { "object": "list", "data": [ { "id": "si_HAwfKDKNOmRu6Q", "object": "subscription_item", "billing_thresholds": null, "created": 1588008574, "metadata": {}, "price": { "id": "price_H1NlVtpo6ubk0m", "object": "price", "active": true, "billing_scheme": "per_unit", "created": 1585803008, "currency": "usd", "livemode": false, "lookup_key": null, "metadata": {}, "nickname": "Basic", "product": "prod_H1Nl2JMpwPur6w", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": 30, "usage_type": "licensed" }, "tiers": null, "tiers_mode": null, "transform_quantity": null, "type": "recurring", "unit_amount": 500, "unit_amount_decimal": "500" }, "quantity": 1, "subscription": "sub_HAwfLuEoLetEJ3", "tax_rates": [] } ], "has_more": false, "url": "/v1/subscription_items?subscription=sub_HAwfLuEoLetEJ3" }, "latest_invoice": null, "livemode": false, "metadata": {}, "next_pending_invoice_item_invoice": null, "pause_collection": null, "pending_invoice_item_interval": null, "pending_setup_intent": null, "pending_update": null, "quantity": 1, "schedule": null, "start_date": 1588008574, "status": "active", "tax_percent": null, "trial_end": null, "trial_start": null }

    To verify payment status, check the value of subscription.latest_invoice.payment_intent.status. This field describes the status of the payment intent for the latest subscription invoice. The invoice tracks overall payment status for the subscription; the payment intent tracks the status of an individual payment. To get this value, you must expand the child objects of the reponse.

    If the value of subscription.latest_invoice.payment_intent.status is succeeded, payment is completed, and the subscription status should be active.

    6 Provision access to your service Client and Server

    To give the customer access to your service:

    1. Verify the subscription status is active.
    2. 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.
    3. Store the product.id and subscription.id in your database along with the customer.id you already saved.

    On the frontend, you can implement these steps in the success callback after the subscription is created.

    function onSubscriptionComplete(result) { // Payment was successful. if (result.subscription.status === 'active') { // Change your UI to show a success message to your customer. // Call your backend to grant access to your service based on // `result.subscription.items.data[0].price.product` the customer subscribed to. } }

    It’s possible for a user to leave your application after payment is made and before this function is called. Make sure to monitor the invoice.payment_succeeded event on your webhook endpoint to verify that the payment succeeded and that you should provision the subscription.

    This is also good practice because during the lifecycle of the subscription, you need to keep provisioning in sync with subscription status. Otherwise, customers might be able to access your service even if their payments fail.

    7 Manage payment authentication Client and Server

    If you support payment methods that require customer authentication with 3D Secure, the value of subscription.latest_invoice.payment_intent.status is initially requires_action. The response from the createSubscription call looks like this:

    { "id": "sub_1ELI8bClCIKljWvsvK36TXlC", "object": "subscription", "status": "incomplete", ... "latest_invoice": { "id": "in_EmGqfJMYy3Nt9M", "status": "open", ... "payment_intent": { "status": "requires_action", "client_secret": "pi_91_secret_W9", "next_action": { "type": "use_stripe_sdk", ... }, ... } } }

    To handle this scenario, on the frontend notify the customer that authentication is required to complete payment and start the subscription. Retrieve the client secret for the payment intent, and pass it in a call to stripe.confirmCardPayment.

    function handleCustomerActionRequired({ subscription, invoice, priceId, paymentMethodId, isRetry, }) { if (subscription && subscription.status === 'active') { // Subscription is active, no customer actions required. return { subscription, priceId, paymentMethodId }; } // If it's a first payment attempt, the payment intent is on the subscription latest invoice. // If it's a retry, the payment intent will be on the invoice itself. let paymentIntent = invoice ? invoice.payment_intent : subscription.latest_invoice.payment_intent; if ( paymentIntent.status === 'requires_action' || (isRetry === true && paymentIntent.status === 'requires_payment_method') ) { return stripe .confirmCardPayment(paymentIntent.client_secret, { payment_method: paymentMethodId, }) .then((result) => { if (result.error) { // Start code flow to handle updating the payment details. // Display error message in your UI. // The card was declined (i.e. insufficient funds, card has expired, etc). throw result; } else { if (result.paymentIntent.status === 'succeeded') { // Show a success message to your customer. // There's a risk of the customer closing the window before the callback. // We recommend setting up webhook endpoints later in this guide. return { priceId: priceId, subscription: subscription, invoice: invoice, paymentMethodId: paymentMethodId, }; } } }) .catch((error) => { displayError(error); }); } else { // No customer action needed. return { subscription, priceId, paymentMethodId }; } }

    This displays an authentication modal to your customers, attempts payment, then closes the modal and returns context to your application.

    Make sure to monitor the invoice.payment_succeeded event on your webhook endpoint to verify that the payment succeeded. It’s possible for users to leave your application before confirmCardPayment() finishes, so verifying whether the payment succeeded allows you to correctly provision your product.

    8 Manage subscription payment failure Client and Server

    If the value of subscription.latest_invoice.payment_intent.status is requires_payment_method, the card was processed when the customer first provided card details, but payment then failed after the payment method was attached to the customer object. Here’s how to handle the situation.

    Catch the error, let your customer know their card was declined, and return them to the payment form to try a different card.

    function handlePaymentMethodRequired({ subscription, paymentMethodId, priceId, }) { if (subscription.status === 'active') { // subscription is active, no customer actions required. return { subscription, priceId, paymentMethodId }; } else if ( subscription.latest_invoice.payment_intent.status === 'requires_payment_method' ) { // Using localStorage to manage the state of the retry here, // feel free to replace with what you prefer. // Store the latest invoice ID and status. localStorage.setItem('latestInvoiceId', subscription.latest_invoice.id); localStorage.setItem( 'latestInvoicePaymentIntentStatus', subscription.latest_invoice.payment_intent.status ); throw { error: { message: 'Your card was declined.' } }; } else { return { subscription, priceId, paymentMethodId }; } }

    On the frontend, define the function to attach the new card to the customer and update the invoice settings. Pass the customer, new payment method, invoice, and price IDs to a backend endpoint.

    function retryInvoiceWithNewPaymentMethod( customerId, paymentMethodId, invoiceId, priceId ) { return ( fetch('/retry-invoice', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, paymentMethodId: paymentMethodId, invoiceId: invoiceId, }), }) .then((response) => { return response.json(); }) // If the card is declined, display an error to the user. .then((result) => { if (result.error) { // The card had an error when trying to attach it to a customer. throw result; } return result; }) // Normalize the result to contain the object returned by Stripe. // Add the addional details we need. .then((result) => { return { // Use the Stripe 'object' property on the // returned result to understand what object is returned. invoice: result, paymentMethodId: paymentMethodId, priceId: priceId, isRetry: true, }; }) // Some payment methods require a customer to be on session // to complete the payment process. Check the status of the // payment intent to handle these actions. .then(handlePaymentThatRequiresCustomerAction) // No more actions required. Provision your service for the user. .then(onSubscriptionComplete) .catch((error) => { // An error has happened. Display the failure to the user here. // We utilize the HTML element we created. displayError(error); }) ); }

    On the backend, define the endpoint for your frontend to call. The code updates the customer with the new payment method, and assigns it as the new default payment method for subscription invoices.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retry-invoice' do content_type 'application/json' data = JSON.parse request.body.read ​ begin Stripe::PaymentMethod.attach( data['paymentMethodId'], { customer: data['customerId'] } ) rescue Stripe::CardError => e halt 200, { 'Content-Type' => 'application/json' }, { 'error': { message: e.error.message } }.to_json end ​ # Set the default payment method on the customer Stripe::Customer.update( data['customerId'], invoice_settings: { default_payment_method: data['paymentMethodId'] } ) ​ invoice = Stripe::Invoice.retrieve({ id: data['invoiceId'], expand: ['payment_intent'] }) ​ invoice.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retry-invoice', methods=['POST']) def retrySubscription(): data = json.loads(request.data) try: ​ stripe.PaymentMethod.attach( data['paymentMethodId'], customer=data['customerId'], ) # Set the default payment method on the customer stripe.Customer.modify( data['customerId'], invoice_settings={ 'default_payment_method': data['paymentMethodId'], }, ) ​ invoice = stripe.Invoice.retrieve( data['invoiceId'], expand=['payment_intent'], ) return jsonify(invoice) except Exception as e: return jsonify(error={'message': str(e)}), 200
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retry-invoice', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); ​ try { $payment_method = \Stripe\PaymentMethod::retrieve( $body->paymentMethodId ); $payment_method->attach([ 'customer' => $body->customerId, ]); } catch (Exception $e) { return $response->withJson($e->jsonBody); } // Set the default payment method on the customer \Stripe\Customer::update($body->customerId, [ 'invoice_settings' => [ 'default_payment_method' => $body->paymentMethodId ] ]); ​ $invoice = \Stripe\Invoice::retrieve([ 'id' => $body->invoiceId, 'expand' => ['payment_intent'], ]); ​ return $response->withJson($invoice); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/retry-invoice", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer RetryInvoiceBody postBody = gson.fromJson(request.body(), RetryInvoiceBody.class); Customer customer = Customer.retrieve(postBody.getCustomerId()); ​ try { // Set the default payment method on the customer PaymentMethod pm = PaymentMethod.retrieve(postBody.getPaymentMethodId()); pm.attach(PaymentMethodAttachParams.builder().setCustomer(customer.getId()).build()); } catch (CardException e) { // Since it's a decline, CardException will be caught Map<String, String> responseErrorMessage = new HashMap<>(); responseErrorMessage.put("message", e.getLocalizedMessage()); Map<String, Object> responseError = new HashMap<>(); responseError.put("error", responseErrorMessage); return gson.toJson(responseError); } ​ Map<String, Object> customerParams = new HashMap<String, Object>(); Map<String, String> invoiceSettings = new HashMap<String, String>(); invoiceSettings.put("default_payment_method", postBody.getPaymentMethodId()); customerParams.put("invoice_settings", invoiceSettings); customer.update(customerParams); ​ List<String> expandList = new ArrayList<>(); expandList.add("payment_intent"); ​ Map<String, Object> params = new HashMap<>(); params.put("expand", expandList); ​ Invoice invoice = Invoice.retrieve(postBody.getInvoiceId(), params, null); ​ return invoice.toJson(); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/retry-invoice', async (req, res) => { // Set the default payment method on the customer ​ try { await stripe.paymentMethods.attach(req.body.paymentMethodId, { customer: req.body.customerId, }); await stripe.customers.update(req.body.customerId, { invoice_settings: { default_payment_method: req.body.paymentMethodId, }, }); } catch (error) { // in case card_decline error return res .status('402') .send({ result: { error: { message: error.message } } }); } ​ const invoice = await stripe.invoices.retrieve(req.body.invoiceId, { expand: ['payment_intent'], }); res.send(invoice); });

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

    The example collects the subscription ID on the frontend, but you will most likely get this information from your database for your logged in user.

    Account settings with the ability to cancel the subscription

    function cancelSubscription() { 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 cancelled. }); }

    On the backend, define the endpoint for your frontend to call.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/cancel-subscription' do content_type 'application/json' data = JSON.parse request.body.read ​ deleted_subscription = Stripe::Subscription.delete(data['subscriptionId']) ​ deleted_subscription.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/cancel-subscription', methods=['POST']) def cancelSubscription(): data = json.loads(request.data) try: # Cancel the subscription by deleting it deletedSubscription = stripe.Subscription.delete(data['subscriptionId']) return jsonify(deletedSubscription) except Exception as e: return jsonify(error=str(e)), 403
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/cancel-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); ​ $subscription = \Stripe\Subscription::retrieve( $body->subscriptionId ); $subscription->delete(); ​ return $response->withJson($subscription); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/cancel-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer CancelPostBody postBody = gson.fromJson(request.body(), CancelPostBody.class); ​ Subscription subscription = Subscription.retrieve(postBody.getSubscriptionId()); ​ Subscription deletedSubscription = subscription.cancel(); return deletedSubscription.toJson(); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/cancel-subscription', async (req, res) => { // Delete the subscription const deletedSubscription = await stripe.subscriptions.del( req.body.subscriptionId ); res.send(deletedSubscription); });

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

    10 Test your integration

    To make sure your integration is ready for production, you can work with the following test cards. Use them with any CVC, postal code, and future expiration date.

    Card number What it does
    4242424242424242 Succeeds and immediately creates an active subscription.
    4000002760003184 Requires authentication. confirmCardPayment() will trigger a modal asking for the customer to authenticate. Once the user confirms, the subscription will become active. See manage payment authentication.
    4000008260003178 Always fails with a decline code of insufficient_funds. See create subscription step on how to handle this server side.
    4000000000000341 Succeeds on initial attaching to Customer object, but fails with on the first payment of a subscription with the payment_intent value of requires_payment_method. See manage subscription payment failure step.

    Check out the documentation for testing Billing for more information and ways to test your integration.

    11 Set up webhooks Server

    Because subscriptions are asynchronous, we recommend setting up webhooks to receive events from Stripe instead of polling for status updates. You can set up a webhook endpoint in the Dashboard, or with the Webhook Endpoints API.

    The Stripe CLI provides a listen command for testing event monitoring during development.

    Get started with these events:

    Webhook Why you need it
    invoice.payment_failed If the payment fails or the customer does not have a valid payment method, an invoice.payment_failed event is sent, and the subscription status becomes past_due. Use this event to notify your user that their payment has failed and request new card details.
    invoice.payment_succeeded Use this event to provision services. The status of the invoice will show up as paid. Store the status in your database to reference when a user accesses your service to avoid hitting rate limits. This event is also important for monitoring the status of payments that require customer authentication with 3D Secure.

    Here’s how to set up your webhook handler and verify the signature of the event.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/stripe-webhook' do # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = ENV['STRIPE_WEBHOOK_SECRET'] payload = request.body.read if !webhook_secret.empty?
    See all 75 lines # 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 = nil ​ begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = 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 == 'invoice.payment_succeeded' # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. # puts data_object end ​ if event_type == 'invoice.payment_failed' # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. # puts data_object end ​ if event_type == 'invoice.finalized' # If you want to manually send out invoices to your customers # or store them locally to reference to avoid hitting Stripe rate limits. # puts data_object end ​ if event_type == 'customer.subscription.deleted' # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. # puts data_object end ​ if event_type == 'customer.subscription.trial_will_end' # Send notification to your user that the trial will end # puts data_object end ​ content_type 'application/json' { status: 'success' }.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/stripe-webhook', methods=['POST']) def webhook_received(): # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET') request_data = json.loads(request.data)
    See all 57 lines ​ if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] ​ data_object = data['object'] ​ if event_type == 'invoice.payment_succeeded': # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. print(data) ​ if event_type == 'invoice.payment_failed': # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. print(data) if event_type == 'invoice.finalized': # If you want to manually send out invoices to your customers # or store them locally to reference to avoid hitting Stripe rate limits. print(data) ​ if event_type == 'customer.subscription.deleted': # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. print(data) if event_type == 'customer.subscription.trial_will_end': # Send notification to your user that the trial will end print(data) ​ return jsonify({'status': 'success'})
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/stripe-webhook', function(Request $request, Response $response) { $logger = $this->get('logger'); $event = $request->getParsedBody(); // Parse the message body (and check the signature if possible) $webhookSecret = getenv('STRIPE_WEBHOOK_SECRET'); if ($webhookSecret) {
    See all 66 lines try { $event = \Stripe\Webhook::constructEvent( $request->getBody(), $request->getHeaderLine('stripe-signature'), $webhookSecret ); } catch (\Exception $e) { return $response->withJson([ 'error' => $e->getMessage() ])->withStatus(403); } } else { $event = $request->getParsedBody(); } $type = $event['type']; $object = $event['data']['object']; ​ // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch ($type) { case 'invoice.payment_succeeded': // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. $logger->info('🔔 Webhook received! ' . $object); break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. $logger->info('🔔 Webhook received! ' . $object); break; case 'invoice.finalized': // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. $logger->info('🔔 Webhook received! ' . $object); break; case 'customer.subscription.deleted': // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. $logger->info('🔔 Webhook received! ' . $object); break; case 'customer.subscription.trial_will_end': // Send notification to your user that the trial will end $logger->info('🔔 Webhook received! ' . $object); break; // ... handle other event types default: // Unhandled event type } ​ return $response->withJson([ 'status' => 'success' ])->withStatus(200); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/stripe-webhook", (request, response) -> { String payload = request.body(); String sigHeader = request.headers("Stripe-Signature"); String endpointSecret = dotenv.get("STRIPE_WEBHOOK_SECRET"); Event event = null; ​
    See all 61 lines try { event = Webhook.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { // Invalid signature response.status(400); return ""; } ​ // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } ​ switch (event.getType()) { case "invoice.payment_succeeded": // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. break; case "invoice.payment_failed": // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case "invoice.finalized": // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. break; case "customer.subscription.deleted": // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. break; case "customer.subscription.trial_will_end": // Send notification to your user that the trial will end break; default: // Unhandled event type } ​ response.status(200); return ""; });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post( '/stripe-webhook', bodyParser.raw({ type: 'application/json' }), async (req, res) => { // Retrieve the event by verifying the signature using the raw body and secret. let event;
    See all 72 lines ​ try { event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(err); console.log(`⚠️ Webhook signature verification failed.`); console.log( `⚠️ Check the env file and enter the correct webhook secret.` ); return res.sendStatus(400); } // Extract the object from the event. const dataObject = event.data.object; ​ // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch (event.type) { case 'invoice.payment_succeeded': // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate limits. break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case 'invoice.finalized': // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. break; case 'customer.subscription.deleted': if (event.request != null) { // handle a subscription cancelled by your request // from above. } else { // handle subscription cancelled automatically based // upon your subscription settings. } break; case 'customer.subscription.trial_will_end': if (event.request != null) { // handle a subscription cancelled by your request // from above. } else { // handle subscription cancelled automatically based // upon your subscription settings. } break; default: // Unexpected event type } res.sendStatus(200); } );

    Learn more about other events your webhook can listen for.

    Optional Let customers change their plans Client and Server

    To let your customers change their subscription, collect the price ID of the option they want to change to. Then send the new price ID from the frontend to a backend endpoint. This example also passes the subscription ID, but you can retrieve it from your database for your logged in user.

    function updateSubscription(priceId, subscriptionId) { return fetch('/update-subscription', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId, newPriceId: priceId, }), }) .then(response => { return response.json(); }) .then(response => { return response; }); }

    On the backend, define the endpoint for your frontend to call, passing the subscription ID and the new price ID. The subscription is now Premium, at 15 USD per month, instead of Basic at 5 USD per month.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/update-subscription' do content_type 'application/json' data = JSON.parse request.body.read ​ subscription = Stripe::Subscription.retrieve(data['subscriptionId']) ​ updated_subscription = Stripe::Subscription.update( data['subscriptionId'], cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: "price_H1NlVtpo6ubk0m" } ] ) ​ updated_subscription.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/update-subscription', methods=['POST']) def updateSubscription(): data = json.loads(request.data) try: subscription = stripe.Subscription.retrieve(data['subscriptionId']) ​ updatedSubscription = stripe.Subscription.modify( data['subscriptionId'], cancel_at_period_end=False, items=[{ 'id': subscription['items']['data'][0].id, 'price': "price_H1NlVtpo6ubk0m", }] ) return jsonify(updatedSubscription) except Exception as e: return jsonify(error=str(e)), 403
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/update-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); ​ $subscription = \Stripe\Subscription::retrieve($body->subscriptionId); $updatedSubscription = \Stripe\Subscription::update($body->subscriptionId, [ 'cancel_at_period_end' => false, 'items' => [ [ 'id' => $subscription->items->data[0]->id, 'price' => "price_H1NlVtpo6ubk0m", ], ], ]); ​ return $response->withJson($updatedSubscription); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/update-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer UpdatePostBody postBody = gson.fromJson(request.body(), UpdatePostBody.class); ​ Subscription subscription = Subscription.retrieve(postBody.getSubscriptionId()); ​ SubscriptionUpdateParams params = SubscriptionUpdateParams.builder() .addItem(SubscriptionUpdateParams.Item.builder() .setId(subscription.getItems().getData().get(0).getId()) .setPrice("price_H1NlVtpo6ubk0m".build()) .setCancelAtPeriodEnd(false).build(); ​ subscription.update(params); return subscription.toJson(); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/update-subscription', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const updatedSubscription = await stripe.subscriptions.update( req.body.subscriptionId, { cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: "price_H1NlVtpo6ubk0m", }, ], } ); ​ res.send(updatedSubscription); });

    Optional Preview a price change Client and Server

    When your customer changes their subscription, there’s often an adjustment to the amount they owe known as a proration. You can use the upcoming invoice endpoint to display the adjusted amount to your customers.

    On the frontend, pass the upcoming invoice details to a backend endpoint.

    function retrieveUpcomingInvoice( customerId, subscriptionId, newPriceId, trialEndDate ) { return fetch('/retrieve-upcoming-invoice', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, subscriptionId: subscriptionId, newPriceId: newPriceId, }), }) .then(response => { return response.json(); }) .then(invoice { return invoice; }); }

    On the backend, define the endpoint for your frontend to call.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retrieve-upcoming-invoice' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve( data['subscriptionId'] ) invoice = Stripe::Invoice.upcoming( customer: data['customerId'], subscription: data['subscriptionId'], subscription_items: [ { id: subscription.items.data[0].id, deleted: true }, { price: 'price_H1NlVtpo6ubk0m', deleted: false } ] ) invoice.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retrieve-upcoming-invoice', methods=['POST']) def retrieveUpcomingInvoice(): data = json.loads(request.data) try: # Retrieve the subscription subscription = stripe.Subscription.retrieve(data['subscriptionId']) # Retrieve the invoice invoice = stripe.Invoice.upcoming( customer=data['customerId'], subscription=data['subscriptionId'], subscription_items=[ { 'id': subscription['items']['data'][0].id, 'deleted': True }, { 'price': 'price_H1NlVtpo6ubk0m', 'deleted': False } ], ) return jsonify(invoice) except Exception as e: return jsonify(error=str(e)), 403
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retrieve-upcoming-invoice', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $subscription = \Stripe\Subscription::retrieve( $body->subscriptionId ); $invoice = \Stripe\Invoice::upcoming([ "customer" => $body->customerId, "subscription_prorate" => TRUE, "subscription" => $body->subscriptionId, "subscription_items" => [ [ 'id' => $subscription->items->data[0]->id, 'deleted' => TRUE ], [ 'price' => 'price_H1NlVtpo6ubk0m', 'deleted' => FALSE ], ] ]); return $response->withJson($invoice); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/retrieve-upcoming-invoice", (request, response) -> { response.type("application/json"); UpcomingInvoicePostBody postBody = gson.fromJson(request.body(), UpcomingInvoicePostBody.class); Subscription subscription = Subscription.retrieve(postBody.getSubscriptionId()); Map<String, Object> invoiceParams = new HashMap<>(); invoiceParams.put("customer", postBody.getCustomerId()); invoiceParams.put("subscription", postBody.getSubscriptionId()); invoiceParams.put("subscription_prorate", true); Map<String, Object> item = new HashMap<>(); item.put("id", subscription.getItems().getData().get(0).getId()); item.put("deleted", true); Map<String, Object> items = new HashMap<>(); items.put("0", item); Map<String, Object> item2 = new HashMap<>(); item2.put("price", "price_H1NlVtpo6ubk0m"); item2.put("deleted", false); items.put("1", item2); invoiceParams.put("subscription_items", items); Invoice invoice = Invoice.upcoming(invoiceParams); return invoice.toJson(); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/retrieve-upcoming-invoice', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const invoice = await stripe.invoices.retrieveUpcoming({ subscription_prorate: true, customer: req.body.customerId, subscription: req.body.subscriptionId, subscription_items: [ { id: subscription.items.data[0].id, deleted: true, }, { // This price ID is the price you want to change the subscription to. price: 'price_H1NlVtpo6ubk0m', deleted: false, }, ], }); res.send(invoice); });

    Optional Display the customer payment method Client and Server

    Displaying the brand and last 4 digits of your customer’s card can help them know which card is being charged or if they need to update their payment method.

    On the frontend, send the payment method ID to a backend endpoint that retrieves the payment method details.

    function retrieveCustomerPaymentMethod(paymentMethodId) { return fetch('/retrieve-customer-payment-method', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ paymentMethodId: paymentMethodId, }), }) .then((response) => { return response.json(); }) .then((response) => { return response; }); }

    On the backend, define the endpoint for your frontend to call.

    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retrieve-customer-payment-method' do content_type 'application/json' data = JSON.parse request.body.read ​ payment_method = Stripe::PaymentMethod.retrieve( data['paymentMethodId'] ) ​ payment_method.to_json end
    # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retrieve-customer-payment-method', methods=['POST']) def retrieveCustomerPaymentMethod(): data = json.loads(request.data) try: paymentMethod = stripe.PaymentMethod.retrieve( data['paymentMethodId'], ) return jsonify(paymentMethod) except Exception as e: return jsonify(error=str(e)), 403
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retrieve-customer-payment-method', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); ​ $paymentMethod = \Stripe\PaymentMethod::retrieve( $body->paymentMethodId ); ​ return $response->withJson($paymentMethod); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/retrieve-customer-payment-method", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer PaymentMethodBody paymentMethodBody = gson.fromJson(request.body(), PaymentMethodBody.class); ​ PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodBody.getPaymentMethodId()); return paymentMethod.toJson(); });
    // Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/retrieve-customer-payment-method', async (req, res) => { const paymentMethod = await stripe.paymentMethods.retrieve( req.body.paymentMethodId ); ​ res.send(paymentMethod); });

    Example response:

    { "id": "pm_1GcbHY2eZvKYlo2CoqlVxo42", "object": "payment_method", "billing_details": { "address": { "city": null, "country": null, "line1": null, "line2": null, "postal_code": null,
    See all 41 lines "state": null }, "email": null, "name": null, "phone": null }, "card": { "brand": "visa", "checks": { "address_line1_check": null, "address_postal_code_check": null, "cvc_check": "pass" }, "country": "US", "exp_month": 8, "exp_year": 2021, "fingerprint": "Xt5EWLLDS7FJjR1c", "funding": "credit", "generated_from": null, "last4": "4242", "three_d_secure_usage": { "supported": true }, "wallet": null }, "created": 1588010536, "customer": "cus_HAxB7dVQxhoKLh", "livemode": false, "metadata": {}, "type": "card" }

    Was this page helpful?

    Feedback about this page?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page