Billing
Fixed-price subscriptions with Elements

Create fixed-price subscriptions

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

This guide walks you through how to create fixed-price subscriptions for a photo hosting service. It shows you how to use Stripe Elements to create a custom payment form you embed in your application. You can find code for an example implementation with Elements on GitHub.

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

  • Metered billing, where customers pay depending on how much they use of a service such as data storage, or calls to an API.
  • Per-seat subscriptions, where customers pay depending on how many units (seats) of a product or service they purchase. This example shows how to work with tiers.

What you’ll build

This guide shows you how to:

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

Example application

How to model it on Stripe

API object relationships

API object definitions

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

1 Install Stripe libraries and tools

Install the Stripe client of your choice:

# Available as a gem 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
# Make sure your project is using Go Modules go mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71
// Then import the package import ( "github.com/stripe/stripe-go/v71" )
# Install via dotnet dotnet add package Stripe.net dotnet restore
# Or install via NuGet PM> Install-Package Stripe.net

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. 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 Test as you go Server

You can test different payment scenarios with Stripe’s test cards, but testing also includes checking events to monitor the status of subscriptions. If you’re building a subscription integration for the first time, monitoring events in the Dashboard might be enough to help monitor your work while you follow the steps.

A production-ready integration should monitor events automatically, though. You can call the Stripe API and check specific values in the response (polling), or you can set up a webhook endpoint and let Stripe push events to your integration. Webhook monitoring is simpler and more efficient, especially for asynchronous integrations like subscriptions. The Stripe CLI provides a listen command for testing event monitoring during development.

Set up webhook monitoring

You can set up a webhook endpoint in the Dashboard, or with the Webhook Endpoints API.

Here’s how to set up your webhook handler and verify the signature of the event. Note that the example specifies the major event types to monitor for subscriptions, but you can add more as needed.

# 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 62 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 == 'customer.subscription.deleted' # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. # 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 48 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 == 'customer.subscription.deleted': # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. 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 59 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 'customer.subscription.deleted': // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. $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");
See all 57 lines Event event = null; 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 "customer.subscription.deleted": // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. 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 59 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 '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; default: // Unexpected event type } res.sendStatus(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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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 == "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. return } 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. return } if event.Type == "customer.subscription.deleted" { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. { return } }
[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.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 == "customer.subscription.deleted") { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. } return Ok(); }

Event types to monitor are indicated at the steps in this guide where they’re applicable. For more events you can monitor, see Subscription events.

3 Create the business model Stripe CLI or Dashboard

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

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

Create the product object for the premium service:

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

The response looks like:

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

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

stripe prices create \ -d product=prod_H94k5odtwJXMtQ \ -d unit_amount=1500 \ -d currency=usd \ -d "recurring[interval]"=month

Create the product object for the basic service:

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

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

stripe prices create \ -d product=prod_HGd6W1VUqqXGvr \ -d unit_amount=500 \ -d currency=usd \ -d "recurring[interval]"=month

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

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

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

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

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

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

The Copy to live mode button at the top right of the page lets you clone your product from test mode to live mode when you’re ready.

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

<!DOCTYPE html> <html lang="en"> <body> <form id="signup-form"> <div> <input id="email" type="text" placeholder="Email address" required /> </div> ​ <button id="email-submit" type="submit"> <span id="button-text">Sign up</span> </button> </form> <script> 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; }); } ​ let signupForm = document.getElementById('signup-form'); ​ signupForm.addEventListener('submit', function (evt) { evt.preventDefault(); // Create Stripe customer createCustomer().then((result) => { customer = result.customer; }); }); </script> </body> </html>
import React, { useState } from 'react'; ​ function CreateCustomerForm(props) { const [email, setEmail] = useState(''); const [customer, setCustomer] = useState(null); ​ const handleSubmit = (evt) => { evt.preventDefault(); return fetch('/create-customer', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: email, }), }) .then((response) => { return response.json(); }) .then((result) => { setCustomer(result.customer); }); }; ​ return ( <form id="signup-form" onSubmit={handleSubmit}> <div> <input id="email" type="text" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email address" required /> </div> ​ <button id="email-submit" type="submit"> <span id="button-text">Sign up</span> </button> </form> ); } ​ export default CreateCustomerForm;

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" } }
You should receive a `customer.created` event.

5 Collect payment information Client

Let your customer choose a plan and provide payment information. In the sample, 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 included with 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 do not use 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.

6 Save payment details and create the subscription Client and Server

On the frontend, save the payment details you just collected to a payment method. The following code handles the cases for a new subscription and for updating an existing payment method.

var form = document.getElementById('subscription-form'); ​ form.addEventListener('submit', function (ev) { ev.preventDefault(); ​ // If a previous payment was attempted, get the latest invoice const latestInvoicePaymentIntentStatus = localStorage.getItem( 'latestInvoicePaymentIntentStatus' ); ​ if (latestInvoicePaymentIntentStatus === 'requires_payment_method') { const invoiceId = localStorage.getItem('latestInvoiceId'); const isPaymentRetry = true; // create new payment method & retry payment on invoice with new payment method createPaymentMethod({ card, isPaymentRetry, invoiceId, }); } else { // create new payment method & create subscription createPaymentMethod({ card }); } }); ​ function createPaymentMethod({ card, isPaymentRetry, invoiceId }) { // Set up payment method for recurring usage let billingName = document.querySelector('#name').value; ​ stripe .createPaymentMethod({ type: 'card', card: card, billing_details: { name: billingName, }, }) .then((result) => { if (result.error) { displayError(result); } else { if (isPaymentRetry) { // Update the payment method and retry invoice payment retryInvoiceWithNewPaymentMethod({ customerId: customerId, paymentMethodId: result.paymentMethod.id, invoiceId: invoiceId, priceId: priceId, }); } else { // Create the subscription createSubscription({ customerId: customerId, paymentMethodId: result.paymentMethod.id, priceId: priceId, }); } } }); }
import React from 'react'; import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js'; ​ import CardSection from './CardSection'; ​ export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); ​ const handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); ​ if (!stripe || !elements) { // Stripe.js has not yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } ​ // Get a reference to a mounted CardElement. Elements knows how // to find your CardElement because there can only ever be one of // each type of element. const cardElement = elements.getElement(CardElement); ​ // If a previous payment was attempted, get the latest invoice const latestInvoicePaymentIntentStatus = localStorage.getItem( 'latestInvoicePaymentIntentStatus' ); ​ const { error, paymentMethod } = await stripe.createPaymentMethod({ type: 'card', card: cardElement, }); ​ if (error) { console.log('[createPaymentMethod error]', error); } else { console.log('[PaymentMethod]', paymentMethod); const paymentMethodId = paymentMethod.id; if (latestInvoicePaymentIntentStatus === 'requires_payment_method') { // Update the payment method and retry invoice payment const invoiceId = localStorage.getItem('latestInvoiceId'); retryInvoiceWithNewPaymentMethod({ customerId, paymentMethodId, invoiceId, priceId, }); } else { // Create the subscription createSubscription({ customerId, paymentMethodId, priceId }); } } }; ​ return ( <form onSubmit={handleSubmit}> <CardSection /> <button disabled={!stripe}>Confirm order</button> </form> ); }

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 additional 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 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_HGd7M3DV3IMXkC' }], expand: %w[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_HGd7M3DV3IMXkC' } ], 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' => 'price_HGd7M3DV3IMXkC', ], ], '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("price_HGd7M3DV3IMXkC") .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_HGd7M3DV3IMXkC' }], 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 }

The example verifies initial payment status with the API response by checking the value of subscription.latest_invoice.payment_intent.status. The invoice tracks payment status for the subscription; the payment intent tracks the status of the provided payment method. To retrieve subscription.latest_invoice.payment_intent.status, you expand the latest_invoice child object of the response.

You should also receive an invoice.paid event. You can disregard this event for initial payment, but monitor it for subsequent payments. The invoice.paid event type corresponds to the payment_intent.status of succeeded, so payment is complete, and the subscription status is active.

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

8 Manage payment authentication Client and Server

If you support payment methods that require customer authentication with 3D Secure, the value of 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", ... }, ... } } }

In production, you’d monitor the invoice.payment_action_required event type.

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 handlePaymentThatRequiresCustomerAction({ 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 later – in a production scenario, if a customer’s card was stolen or canceled after the subscription was set up, for example. The example relies on the API response object so that you can test error handling. In production you’d monitor the invoice.payment_failed webhook event for payments after initial payment success.

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

function handleRequiresPaymentMethod({ 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 additional 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: %w[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. }); }
import React, { useState } from 'react'; function CancelSubscription(props) { const handleClick = (evt) => { evt.preventDefault(); 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. }); }; return ( <button handleClick={handleClick} type="submit"> Cancel subscription </button> ); } export default CancelSubscription;

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; }

You should receive a customer.subscription.deleted event.

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.
4000002500003155 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 when it initially attaches to Customer object, but fails on the first payment of a subscription with the payment_intent value of requires_payment_method. See the manage subscription payment failure step.

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

Optional Let customers change their plans Client and Server

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

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

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

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/update-subscription' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve(data['subscriptionId']) updated_subscription = Stripe::Subscription.update( data['subscriptionId'], cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: 'price_H1NlVtpo6ubk0m' } ] ) updated_subscription.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/update-subscription', methods=['POST']) def updateSubscription(): data = json.loads(request.data) try: subscription = stripe.Subscription.retrieve(data['subscriptionId']) updatedSubscription = stripe.Subscription.modify( data['subscriptionId'], cancel_at_period_end=False, items=[{ 'id': subscription['items']['data'][0].id, 'price': 'price_H1NlVtpo6ubk0m', }] ) return jsonify(updatedSubscription) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/update-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $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("price_H1NlVtpo6ubk0m") .build() ) .setCancelAtPeriodEnd(false) .build(); subscription.update(params); return subscription.toJson(); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/update-subscription', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const updatedSubscription = await stripe.subscriptions.update( req.body.subscriptionId, { cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: "price_H1NlVtpo6ubk0m", }, ], } ); res.send(updatedSubscription); });
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("price_H1NlVtpo6ubk0m"), }}, } 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 = "price_H1NlVtpo6ubk0m", } } }; var updatedSubscription = service.Update(req.Subscription, options); return updatedSubscription; }

You should receive a customer.subscription.updated event.

Optional Preview a price change Client and Server

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

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

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

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

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retrieve-upcoming-invoice' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve(data['subscriptionId']) invoice = Stripe::Invoice.upcoming( customer: data['customerId'], subscription: data['subscriptionId'], subscription_items: [ { id: subscription.items.data[0].id, deleted: true }, { price: ENV[data['newPriceId']], deleted: false } ] ) invoice.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retrieve-upcoming-invoice', methods=['POST']) def retrieveUpcomingInvoice(): data = json.loads(request.data) try: # Retrieve the subscription subscription = stripe.Subscription.retrieve(data['subscriptionId']) # Retrieve the invoice invoice = stripe.Invoice.upcoming( customer=data['customerId'], subscription=data['subscriptionId'], subscription_items=[ { 'id': subscription['items']['data'][0].id, 'deleted': True, }, { 'price': 'price_H1NlVtpo6ubk0m', 'deleted': False, } ], ) return jsonify(invoice) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retrieve-upcoming-invoice', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve( $body->subscriptionId ); $invoice = $stripe->invoices->upcoming([ "customer" => $body->customerId, "subscription_prorate" => true, "subscription" => $body->subscriptionId, "subscription_items" => [ [ 'id' => $subscription->items->data[0]->id, 'deleted' => true ], [ 'price' => getenv($body->newPriceId), '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() ); InvoiceUpcomingParams invoiceParams = InvoiceUpcomingParams .builder() .setCustomer(postBody.getCustomerId()) .setSubscription(postBody.getSubscriptionId()) .addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setId(subscription.getItems().getData().get(0).getId()) .setDeleted(true) .build() ) .addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setPrice(dotenv.get(postBody.getNewPriceId().toUpperCase())) .build() ) .build(); Invoice invoice = Invoice.upcoming(invoiceParams); return invoice.toJson(); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/retrieve-upcoming-invoice', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const invoice = await stripe.invoices.retrieveUpcoming({ subscription_prorate: true, customer: req.body.customerId, subscription: req.body.subscriptionId, subscription_items: [ { id: subscription.items.data[0].id, deleted: true, }, { // This price ID is the price you want to change the subscription to. price: 'price_H1NlVtpo6ubk0m', deleted: false, }, ], }); res.send(invoice); });
func handleRetrieveUpcomingInvoice(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"` CustomerID string `json:"customerId"` 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.InvoiceParams{ Customer: stripe.String(req.CustomerID), Subscription: stripe.String(req.SubscriptionID), SubscriptionItems: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Deleted: stripe.Bool(true), }, { Price: stripe.String(os.Getenv(req.NewPriceID)), Deleted: stripe.Bool(false), }}, } in, err := invoice.GetNext(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("invoice.GetNext: %v", err) return } writeJSON(w, in) }
using Newtonsoft.Json; public class RetrieveUpcomingInvoiceRequest { [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonProperty("newPriceId")] public string NewPrice { get; set; } }
[HttpPost("retrieve-upcoming-invoice")] public ActionResult<Invoice> RetrieveUpcomingInvoice([FromBody] RetrieveUpcomingInvoiceRequest req) { var service = new SubscriptionService(); var subscription = service.Get(req.Subscription); var invoiceService = new InvoiceService(); var options = new UpcomingInvoiceOptions { Customer = req.Customer, Subscription = req.Subscription, SubscriptionItems = new List<InvoiceSubscriptionItemOptions> { new InvoiceSubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Deleted = true, }, new InvoiceSubscriptionItemOptions { // TODO: This should be Price, but isn't in Stripe.net yet. Plan = Environment.GetEnvironmentVariable(req.NewPrice), Deleted = false, }, } }; Invoice upcoming = invoiceService.Upcoming(options); return upcoming; }

Optional Display the customer payment method Client and Server

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

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

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

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

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retrieve-customer-payment-method' do content_type 'application/json' data = JSON.parse request.body.read payment_method = Stripe::PaymentMethod.retrieve(data['paymentMethodId']) payment_method.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retrieve-customer-payment-method', methods=['POST']) def retrieveCustomerPaymentMethod(): data = json.loads(request.data) try: paymentMethod = stripe.PaymentMethod.retrieve( data['paymentMethodId'], ) return jsonify(paymentMethod) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retrieve-customer-payment-method', function ( Request $request, Response $response, array $args ) { $body = json_decode($request->getBody()); $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.