Metered billing with Elements

Learn how to charge customers based on how much they use your product or service. For example, charging customers based on the number of API calls they make or the amount of data they use.

This guide walks you through how to use metered billing subscriptions for an email service that offers two levels of service, each with discounted pricing depending on how many emails are sent. It uses Stripe Elements to create a custom payment form you embed in your application. You can find the complete code on GitHub

Other guides that explain how to build subscriptions with different business models:

This guide is different in that you report usage. Like the per-seat guide, it also shows how to use tiers, in this case to offer discounted pricing for higher levels of usage.

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
  • Report usage
  • 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.
Usage Record A usage record represents how much a customer has used your product or service at a specific point in time. These records are used to calculate how much to bill a customer.
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/
# Find the version you want to pin: # https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md # Specify that version in your requirements.txt file stripe>=2.48.0,<3.0
# 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. This guide uses an email API platform as an example. It has two products with one pricing option for each:

  • Basic option
    • Tier one: 15 USD per month for 2,000 emails
    • Tier two: An additional .00100 USD for each email after 2,000
  • Premium option
    • Tier one: 75 USD per month for 10,000 emails
    • Tier two: An additional .00075 USD for each email after 10,000

To achieve this kind of pricing, you charge a flat fee and an additional amount based on how much customers use. Graduated tiers are used so that customers initially pay the flat fee for the first 2,000 or 10,000 emails. If they upload more than that, they reach tier two and start paying for each additional email. You could also charge solely based on usage without the flat fee.

During each billing period, you create usage records for each customer and then Stripe adds them up to determine how much to bill for. This process is explained in a subsequent step but understanding the default behavior might impact how you create prices.

By default, Stripe sums up the quantity of all usage records for a customer in a billing period and then multiplies the total by the unit_amount of the price. If you want to change this behavior, you need to set recurring[aggregate_usage] when you create the price. You can read more about this in our metered billing documentation but you can choose between using:

  • The last usage record sent during a billing period
  • The last usage record sent regardless of billing period
  • The usage record with max (highest) quantity during a billing period

The default behavior is used in this guide so recurring[aggregate_usage] isn’t set manually.

Create the product objects for the service.

stripe products create \ --name="Basic" \ --type=service \ --description="Basic option for email API platform"
stripe products create \ --name="Premium" \ --type=service \ --description="Premium option for email API platform"

The response to the Products API looks like:

{ "id": "prod_H94k5odtwJXMtQ",
See all 21 lines "object": "product", "active": true, "attributes": [ ], "created": 1587577341, "description": "Basic option for email API platform", "images": [ ], "livemode": false, "metadata": { }, "name": "Basic", "statement_descriptor": null, "type": "service", "unit_label": null, "updated": 1587577341 }

Create the price for the basic product, passing the product ID from the response.

stripe prices create \ -d product=prod_HMuXBqb2LQXH1C \ -d currency=usd \ -d "tiers[0][flat_amount]"=1500 \ -d "tiers[0][unit_amount_decimal]"=0 \ -d "tiers[0][up_to]"=2000 \ -d "tiers[1][unit_amount_decimal]"=0.100 \ -d "tiers[1][up_to]"=inf \ -d tiers_mode=graduated \ -d billing_scheme=tiered \ -d "recurring[usage_type]"=metered \ -d "recurring[interval]"=month \ -d "expand[]"=tiers

Create the premium price using the same product ID.

stripe prices create \ -d product=prod_HMuXBqb2LBZi2CV \ -d currency=usd \ -d "tiers[0][flat_amount]"=7500 \ -d "tiers[0][unit_amount_decimal]"=0 \ -d "tiers[0][up_to]"=10000 \ -d "tiers[1][unit_amount_decimal]"=0.07500 \ -d "tiers[1][up_to]"=inf \ -d tiers_mode=graduated \ -d billing_scheme=tiered \ -d "recurring[usage_type]"=metered \ -d "recurring[interval]"=month \ -d "expand[]"=tiers

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

{ "id": "price_HGd7M3DV3IMXkC",
See all 42 lines "object": "price", "active": true, "billing_scheme": "tiered", "created": 1590775759, "currency": "usd", "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "product": "prod_HMuXBqb2LBZi2CV", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "metered" }, "tiers": [ { "flat_amount": 7500, "flat_amount_decimal": "7500", "unit_amount": 0, "unit_amount_decimal": "0", "up_to": 10000 }, { "flat_amount": null, "flat_amount_decimal": null, "unit_amount": null, "unit_amount_decimal": "0.075", "up_to": null } ], "tiers_mode": "graduated", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": 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 named Basic and Premium. Add a price for each product:

  • Basic
    • Pricing model: Graduated
    • First tier: 0-2,000 units, at 0 USD per unit, with a 15 USD flat fee
    • Second tier: 2,001+ units at .00100 USD per unit
    • Billing period: monthly
    • Usage is metered checkbox selected
    • Charge for metered usage by is set to Sum of usage values during period
  • Premium
    • Pricing model: Graduated
    • First tier: 0-10,000 units, at 0 USD per unit, with a 75 USD flat fee
    • Second tier: 10,0001+ units at .00075 USD per unit
    • Billing period: monthly
    • Usage is metered checkbox selected
    • Charge for metered usage by is set to Sum of usage values during period

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'] ) # At this point, associate the ID of the Customer object with your # own internal representation of a customer, if you have one. 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()); $stripe = $this->stripe; $customer = $stripe->customers->create([ 'email' => $body->email ]); 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); CustomerCreateParams customerParams = CustomerCreateParams.builder() .setEmail(postBody.getEmail()) .build(); // Create a new customer object Customer customer = Customer.create(customerParams); Map<String, Object> responseData = new HashMap<>(); responseData.put("customer", customer); //we use StripeObject.PRETTY_PRINT_GSON.toJson() so that we get the JSON our client is expecting on the polymorphic //parameters that can either be object ids or the object themselves. If we tried to generate the JSON without call this, //for example, by calling gson.toJson(responseData) we will see something like "customer":{"id":"cus_XXX"} instead of //"customer":"cus_XXX". //If you only need to return 1 object, you can use the built in serializers, i.e. Subscription.retrieve("sub_XXX").toJson() return StripeObject.PRETTY_PRINT_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, }); // save the customer.id as stripeCustomerId // in your database. res.send({ customer }); });
func handleCreateCustomer(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.CustomerParams{ Email: stripe.String(req.Email), } c, err := customer.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("customer.New: %v", err) return } writeJSON(w, struct { Customer *stripe.Customer `json:"customer"` }{ Customer: c, }) }
using Newtonsoft.Json; public class CreateCustomerRequest { [JsonProperty("email")] public string Email { get; set; } }
using Newtonsoft.Json; using Stripe; public class CreateCustomerResponse { [JsonProperty("customer")] public Customer Customer { get; set; } }
[HttpPost("create-customer")] public ActionResult<CreateCustomerResponse> CreateCustomer([FromBody] CreateCustomerRequest req) { var options = new CustomerCreateOptions { Email = req.Email, }; var service = new CustomerService(); var customer = service.Create(options); return new CreateCustomerResponse { Customer = 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. Don’t include the script in a bundle or host a copy of it yourself.

<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> <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()); $stripe = $this->stripe; try { $payment_method = $stripe->paymentMethods->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->customers->update($body->customerId, [ 'invoice_settings' => [ 'default_payment_method' => $body->paymentMethodId ] ]); // Create the subscription $subscription = $stripe->subscriptions->create([ 'customer' => $body->customerId, 'items' => [ [ 'price' => getenv($body->priceId), ], ], '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"); // Set the default payment method on the customer CreateSubscriptionBody postBody = gson.fromJson(request.body(), CreateSubscriptionBody.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); } CustomerUpdateParams customerUpdateParams = CustomerUpdateParams.builder() .setInvoiceSettings(CustomerUpdateParams.InvoiceSettings.builder() .setDefaultPaymentMethod(postBody.getPaymentMethodId()) .build()) .build(); customer.update(customerUpdateParams); // Create the subscription SubscriptionCreateParams subCreateParams = SubscriptionCreateParams.builder() .addItem( SubscriptionCreateParams.Item.builder() .setPrice(dotenv.get(postBody.getPriceId().toUpperCase())) .build() ) .setCustomer(customer.getId()) .addAllExpand(Arrays.asList( "latest_invoice.payment_intent")) .build(); Subscription subscription = Subscription.create(subCreateParams); 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); });
func handleCreateSubscription(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentMethodID string `json:"paymentMethodId"` CustomerID string `json:"customerId"` PriceID string `json:"priceId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // Attach PaymentMethod params := &stripe.PaymentMethodAttachParams{ Customer: stripe.String(req.CustomerID), } pm, err := paymentmethod.Attach( req.PaymentMethodID, params, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("paymentmethod.Attach: %v %s", err, pm.ID) return } // Update invoice settings default customerParams := &stripe.CustomerParams{ InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{ DefaultPaymentMethod: stripe.String(pm.ID), }, } c, err := customer.Update( req.CustomerID, customerParams, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("customer.Update: %v %s", err, c.ID) return } // Create subscription subscriptionParams := &stripe.SubscriptionParams{ Customer: stripe.String(req.CustomerID), Items: []*stripe.SubscriptionItemsParams{ { Plan: stripe.String(os.Getenv(req.PriceID)), }, }, } subscriptionParams.AddExpand("latest_invoice.payment_intent") s, err := sub.New(subscriptionParams) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.New: %v", err) return } writeJSON(w, s) }
using Newtonsoft.Json; public class CreateSubscriptionRequest { [JsonProperty("paymentMethodId")] public string PaymentMethod { get; set; } [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("priceId")] public string Price { get; set; } }
[HttpPost("create-subscription")] public ActionResult<Subscription> CreateSubscription([FromBody] CreateSubscriptionRequest req) { // Attach payment method var options = new PaymentMethodAttachOptions { Customer = req.Customer, }; var service = new PaymentMethodService(); var paymentMethod = service.Attach(req.PaymentMethod, options); // Update customer's default invoice payment method var customerOptions = new CustomerUpdateOptions { InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = paymentMethod.Id, }, }; var customerService = new CustomerService(); customerService.Update(req.Customer, customerOptions); // Create subscription var subscriptionOptions = new SubscriptionCreateOptions { Customer = req.Customer, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Price = Environment.GetEnvironmentVariable(req.Price), }, }, }; subscriptionOptions.AddExpand("latest_invoice.payment_intent"); var subscriptionService = new SubscriptionService(); try { Subscription subscription = subscriptionService.Create(subscriptionOptions); return subscription; } catch (StripeException e) { Console.WriteLine($"Failed to create subscription.{e}"); return BadRequest(); } }

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.paid 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 Report usage Server

Throughout each billing period, you need to report usage to Stripe so that customers are billed the correct amounts. You do this by creating usage records with a subscription item, quantity used, and a timestamp. How often you report usage is up to you, but this example uses a daily schedule. It’s best to send usage records in batches to reduce the number of API calls you need to make.

You also have the option of calculating usage yourself or having Stripe do it. In this example, Stripe does the calculations so the action is set. When reporting usage, use idempotency keys to ensure usage isn’t reported more than once in case of latency or other issues.

# 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' # This code can be run on an interval (e.g., every 24 hours) for each active # metered subscription. # You need to write some of your own business logic before creating the # usage record. Pull a record of a customer from your database # and extract the customer's Stripe Subscription Item ID and usage for # the day. If you aren't storing subscription item IDs, # you can retrieve the subscription and check for subscription items # https://stripe.com/docs/api/subscriptions/object#subscription_object-items. subscription_item_id = '' # The usage number you've been keeping track of in your database for # the last 24 hours. usage_quantity = 100 timestamp = Time.now.to_i # The idempotency key allows you to retry this usage record call if it fails. idempotency_key = SecureRandom.uuid begin Stripe::SubscriptionItem.create_usage_record( subscription_item_id, { quantity: usage_quantity, timestamp: timestamp, action: 'set' }, { idempotency_key: idempotency_key } ) rescue Stripe::StripeError => e puts "Usage report failed for item #{subscription_item_id}:" puts "#{e.error.message} (idempotency key: #{idempotency_key})" 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' # This code can be run on an interval (e.g., every 24 hours) for each active # metered subscription. # You need to write some of your own business logic before creating the # usage record. Pull a record of a customer from your database # and extract the customer's Stripe Subscription Item ID and usage # for the day. If you aren't storing subscription item IDs, # you can retrieve the subscription and check for subscription items # https://stripe.com/docs/api/subscriptions/object#subscription_object-items. subscription_item_id = '' # The usage number you've been keeping track of in your database for # the last 24 hours. usage_quantity = 100 timestamp = int(time.time()) # The idempotency key allows you to retry this usage record call if it fails. idempotency_key = uuid.uuid4() try: stripe.SubscriptionItem.create_usage_record( subscription_item_id, quantity=usage_quantity, timestamp=timestamp, action='set', idempotency_key=idempotency_key ) pass except stripe.error.StripeError as e: print('Usage report failed for item ID %s with idempotency key %s: %s' % (subscription_item_id, idempotency_key, e.error.message)) pass
// 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'); // This code can be run on an interval (e.g., every 24 hours) for each active // metered subscription. // You need to write some of your own business logic before creating the // usage record. Pull a record of a customer from your database // and extract the customer's Stripe Subscription Item ID and // usage for the day. If you aren't storing subscription item IDs, // you can retrieve the subscription and check for subscription items // https://stripe.com/docs/api/subscriptions/object#subscription_object-items. $subscription_item_id = ''; // The usage number you've been keeping track of in your database for // the last 24 hours. $usage_quantity = 100; $date = date_create(); $timestamp = date_timestamp_get($date); // The idempotency key allows you to retry this usage record call if it fails. $idempotency_key = Uuid::uuid4()->toString(); try { StripeSubscriptionItem::createUsageRecord( $subscription_item_id, [ 'quantity' => $usage_quantity, 'timestamp' => $timestamp, 'action' => 'set', ], [ 'idempotency_key' => $idempotency_key, ] ); } catch (StripeExceptionApiErrorException $e) { echo "Usage report failed for item ID $subscription_item_id with idempotency key $idempotency_key: $error.toString()" }
// 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"; // This code can be run on an interval (e.g., every 24 hours) for each active // metered subscription. // You need to write some of your own business logic before creating the // usage record. Pull a record of a customer from your database // and extract the customer's Stripe Subscription Item ID and // usage for the day. If you aren't storing subscription item IDs, // you can retrieve the subscription and check for subscription items // https://stripe.com/docs/api/subscriptions/object#subscription_object-items. String subscriptionItemID = ""; // The usage number you've been keeping track of in your database for the last 24 hours. long usageQuantity = 100; long timestamp = Instant.now().getEpochSecond(); // The idempotency key allows you to retry this usage record call if it fails. String idempotencyKey = UUID.randomUUID().toString(); try { UsageRecordCreateOnSubscriptionItemParams params = UsageRecordCreateOnSubscriptionItemParams.builder() .setQuantity(usageQuantity) .setTimestamp(timestamp) .setAction(UsageRecordCreateOnSubscriptionItemParams.Action.SET) .build(); RequestOptions options = RequestOptions .builder() .setIdempotencyKey(idempotencyKey) .build(); UsageRecord.createOnSubscriptionItem(subscriptionItemID, params, options); } catch (StripeException e) { System.out.println("Usage report failed for item ID " + subscriptionItemID + " with idempotency key " + idempotencyKey + ": " + e.getMessage()); }
// 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'); // This code can be run on an interval (e.g., every 24 hours) for each active // metered subscription. // You need to write some of your own business logic before creating the // usage record. Pull a record of a customer from your database // and extract the customer's Stripe Subscription Item ID and // usage for the day. If you aren't storing subscription item IDs, // you can retrieve the subscription and check for subscription items // https://stripe.com/docs/api/subscriptions/object#subscription_object-items. const subscriptionItemID = ''; // The usage number you've been keeping track of in your database for the last 24 hours. const usageQuantity = 100; // The idempotency key allows you to retry this usage record call if it fails. const idempotencyKey = uuid(); const timestamp = parseInt(Date.now() / 1000); try { await stripe.subscriptionItems.createUsageRecord( subscriptionItemID, { quantity: usageQuantity, timestamp: timestamp, action: 'set', }, { idempotencyKey, } ); } catch (error) { console.error(`Usage report failed for item ID ${subscriptionItemID} with idempotency key ${idempotencyKey}: ${error.toString()}`); }

When you report usage, the timestamp has to be within the current billing period, otherwise the call fails. If aggregate_usage is set to sum on the price, there’s an additional five minutes after the end of a billing period when you can report usage (this is to accommodate for clock drift). For all other aggregate_usage values, the timestamp has to be within the billing period.

If you need to see the usage for a customer during a current period, you can retrieve the upcoming invoice and check the quantity for each subscription_item.

8 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.paid 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.

9 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()); $stripe = $this->stripe; try { $payment_method = $stripe->paymentMethods->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->customers->update($body->customerId, [ 'invoice_settings' => [ 'default_payment_method' => $body->paymentMethodId ] ]); $invoice = $stripe->invoices->retrieve($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); } CustomerUpdateParams customerUpdateParams = CustomerUpdateParams.builder() .setInvoiceSettings(CustomerUpdateParams.InvoiceSettings.builder() .setDefaultPaymentMethod(postBody.getPaymentMethodId()) .build()) .build(); customer.update(customerUpdateParams); InvoiceRetrieveParams params = InvoiceRetrieveParams.builder() .addAllExpand(Arrays.asList( "payment_intent")) .build(); 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); });
func handleRetryInvoice(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { CustomerID string `json:"customerId"` PaymentMethodID string `json:"paymentMethodId"` InvoiceID string `json:"invoiceId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // Attach PaymentMethod params := &stripe.PaymentMethodAttachParams{ Customer: stripe.String(req.CustomerID), } pm, err := paymentmethod.Attach( req.PaymentMethodID, params, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("paymentmethod.Attach: %v %s", err, pm.ID) return } // Update invoice settings default customerParams := &stripe.CustomerParams{ InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{ DefaultPaymentMethod: stripe.String(pm.ID), }, } c, err := customer.Update( req.CustomerID, customerParams, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("customer.Update: %v %s", err, c.ID) return } // Retrieve Invoice invoiceParams := &stripe.InvoiceParams{} invoiceParams.AddExpand("payment_intent") in, err := invoice.Get( req.InvoiceID, invoiceParams, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("invoice.Get: %v", err) return } writeJSON(w, in) }
using Newtonsoft.Json; public class RetryInvoiceRequest { [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("paymentMethodId")] public string PaymentMethod { get; set; } [JsonProperty("invoiceId")] public string Invoice { get; set; } }
[HttpPost("retry-invoice")] public ActionResult<Invoice> RetryInvoice([FromBody] RetryInvoiceRequest req) { // Attach payment method var options = new PaymentMethodAttachOptions { Customer = req.Customer, }; var service = new PaymentMethodService(); var paymentMethod = service.Attach(req.PaymentMethod, options); // Update customer's default invoice payment method var customerOptions = new CustomerUpdateOptions { InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = paymentMethod.Id, }, }; var customerService = new CustomerService(); customerService.Update(req.Customer, customerOptions); var invoiceOptions = new InvoiceGetOptions(); invoiceOptions.AddExpand("payment_intent"); var invoiceService = new InvoiceService(); Invoice invoice = invoiceService.Get(req.Invoice, invoiceOptions); return invoice; }

10 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()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->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); });
func handleCancelSubscription(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { SubscriptionID string `json:"subscriptionId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Cancel(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Cancel: %v", err) return } writeJSON(w, s) }
using Newtonsoft.Json; public class CancelSubscriptionRequest { [JsonProperty("subscriptionId")] public string Subscription { get; set; } }
[HttpPost("cancel-subscription")] public ActionResult<Subscription> CancelSubscription([FromBody] CancelSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Cancel(req.Subscription, null); return subscription; }

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.

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

12 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.paid 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.paid' # 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.paid': # 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(); $stripe = $this->stripe; // Parse the message body (and check the signature if possible)
See all 68 lines $webhookSecret = getenv('STRIPE_WEBHOOK_SECRET'); if ($webhookSecret) { try { $event = $stripe->webhooks->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.paid': // 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.paid": // 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.paid': // 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); } );
func handleWebhook(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } b, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("ioutil.ReadAll: %v", err) return } event, err := webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), os.Getenv("STRIPE_WEBHOOK_SECRET")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("webhook.ConstructEvent: %v", err) return } if event.Type != "checkout.session.completed" { return } cust, err := customer.Get(event.GetObjectValue("customer"), nil) if err != nil { log.Printf("customer.Get: %v", err) return } if event.GetObjectValue("display_items", "0", "custom") != "" && event.GetObjectValue("display_items", "0", "custom", "name") == "Pasha e-book" { log.Printf("🔔 Customer is subscribed and bought an e-book! Send the e-book to %s", cust.Email) } else { log.Printf("🔔 Customer is subscribed but did not buy an e-book.") } }
[HttpPost("stripe-webhook")] public async Task<IActionResult> Webhook() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent; try { stripeEvent = EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], this.options.Value.WebhookSecret ); Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}"); } catch (Exception e) { Console.WriteLine($"Something failed {e}"); return BadRequest(); } if (stripeEvent.Type == "invoice.paid") { // 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. } if (stripeEvent.Type == "invoice.paid") { // 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. } if (stripeEvent.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. } if (stripeEvent.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. } if (stripeEvent.Type == "customer.subscription.deleted") { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. } if (stripeEvent.Type == "customer.subscription.trial_will_end") { // Send notification to your user that the trial will end } return Ok(); }

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 on the Premium option, instead of the Basic option.

# 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()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve($body->subscriptionId); $updatedSubscription = $stripe->subscriptions->update($body->subscriptionId, [ '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(dotenv.get(postBody.getNewPriceId().toUpperCase())).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); });
func handleUpdateSubscription(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { SubscriptionID string `json:"subscriptionId"` NewPriceID string `json:"newPriceId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Get(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Get: %v", err) return } params := &stripe.SubscriptionParams{ CancelAtPeriodEnd: stripe.Bool(false), Items: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Price: stripe.String(os.Getenv(req.NewPriceID)), }}, } updatedSubscription, err := sub.Update(req.SubscriptionID, params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Update: %v", err) return } writeJSON(w, updatedSubscription) }
using Newtonsoft.Json; public class UpdateSubscriptionRequest { [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonProperty("newPriceId")] public string NewPrice { get; set; } }
[HttpPost("update-subscription")] public ActionResult<Subscription> UpdateSubscription([FromBody] UpdateSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Get(req.Subscription); var options = new SubscriptionUpdateOptions { CancelAtPeriodEnd = false, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Price = Environment.GetEnvironmentVariable(req.NewPrice), } } }; var updatedSubscription = service.Update(req.Subscription, options); return 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. Retrieve the upcoming invoice and pass in the changes you want to preview. For this example, you need to delete the subscription item for the old price ID, clear the usage, and then add the new price ID. These changes aren’t actually applied, they just define what the preview looks like.

# 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, clear_usage: 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, 'clear_usage': 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, 'clear_usage' => 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); item.put("clear_usage", 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, clear_usage: 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, create the endpoint called above to retrieve the payment method details.

# 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()); $stripe = $this->stripe; $paymentMethod = $stripe->paymentMethods->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); });
func handleRetrieveCustomerPaymentMethod(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentMethodID string `json:"paymentMethodId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } pm, err := paymentmethod.Get(req.PaymentMethodID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("paymentmethod.Get: %v", err) return } writeJSON(w, pm) }
using Newtonsoft.Json; public class RetrieveCustomerPaymentMethodRequest { [JsonProperty("paymentMethodId")] public string PaymentMethod { get; set; } }
[HttpPost("retrieve-customer-payment-method")] public ActionResult<PaymentMethod> RetrieveCustomerPaymentMethod([FromBody] RetrieveCustomerPaymentMethodRequest req) { var service = new PaymentMethodService(); var paymentMethod = service.Get(req.PaymentMethod); return 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?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.