Billing
Per-seat billing with Elements

Create per-seat subscriptions with Elements

Learn how to charge customers based on how many units of your product or service they purchase. For example, a service could charge based on the number of seats purchased, and offer discounts for larger quantities.

This guide walks you through how to create per-seat subscriptions for a messaging service that offers a basic option with a single price per seat, and a premium option with discounted pricing based on the number of seats purchased. It uses Stripe Elements to create a custom payment form you embed in your application. Note that Checkout does not support per-seat pricing with tiers.

You can find code for an example implementation on GitHub.

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

  • Fixed-price subscriptions, where everyone pays the same amount for a single product or service
  • Metered billing, where customers pay depending on how much they use of a service such as data storage, or calls to an API

This guide is different in that you pass the quantity to the subscription object. Like the metered billing guide, it also shows how to use tiers, in this case to offer discounted pricing for larger quantities.

What you’ll build

This guide shows you how to:

  • Model your subscriptions with Products and Prices
  • Create a signup flow
  • Collect payment information
  • Create Subscriptions and charge customers
  • Handle payment errors
  • Preview the cost of adding or removing seats, or changing the plan
  • Let customers change their plan or cancel the subscription

Sample 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 god 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 Set up webhooks Server

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

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

Get started with these events:

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

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

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/stripe-webhook' do # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = ENV['STRIPE_WEBHOOK_SECRET'] payload = request.body.read if !webhook_secret.empty?
See all 73 lines # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = JSON.parse(payload, symbolize_names: true) event = Stripe::Event.construct_from(data) end # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] data = event['data'] data_object = data['object'] if event_type == 'invoice.paid' # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. # puts data_object end if event_type == 'invoice.payment_failed' # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. # puts data_object end if event_type == 'invoice.finalized' # If you want to manually send out invoices to your customers # or store them locally to reference to avoid hitting Stripe rate limits. # puts data_object end if event_type == 'customer.subscription.deleted' # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. # puts data_object end if event_type == 'customer.subscription.trial_will_end' # Send notification to your user that the trial will end # puts data_object end content_type 'application/json' { status: 'success' }.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/stripe-webhook', methods=['POST']) def webhook_received(): # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET') request_data = json.loads(request.data)
See all 57 lines if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] data_object = data['object'] if event_type == 'invoice.paid': # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. print(data) if event_type == 'invoice.payment_failed': # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. print(data) if event_type == 'invoice.finalized': # If you want to manually send out invoices to your customers # or store them locally to reference to avoid hitting Stripe rate limits. print(data) if event_type == 'customer.subscription.deleted': # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. print(data) if event_type == 'customer.subscription.trial_will_end': # Send notification to your user that the trial will end print(data) return jsonify({'status': 'success'})
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/stripe-webhook', function(Request $request, Response $response) { $logger = $this->get('logger'); $event = $request->getParsedBody(); $stripe = $this->stripe; // Parse the message body (and check the signature if possible)
See all 68 lines $webhookSecret = getenv('STRIPE_WEBHOOK_SECRET'); if ($webhookSecret) { try { $event = $stripe->webhooks->constructEvent( $request->getBody(), $request->getHeaderLine('stripe-signature'), $webhookSecret ); } catch (Exception $e) { return $response->withJson([ 'error' => $e->getMessage() ])->withStatus(403); } } else { $event = $request->getParsedBody(); } $type = $event['type']; $object = $event['data']['object']; // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch ($type) { case 'invoice.paid': // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. $logger->info('🔔 Webhook received! ' . $object); break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. $logger->info('🔔 Webhook received! ' . $object); break; case 'invoice.finalized': // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. $logger->info('🔔 Webhook received! ' . $object); break; case 'customer.subscription.deleted': // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. $logger->info('🔔 Webhook received! ' . $object); break; case 'customer.subscription.trial_will_end': // Send notification to your user that the trial will end $logger->info('🔔 Webhook received! ' . $object); break; // ... handle other event types default: // Unhandled event type } return $response->withJson([ 'status' => 'success' ])->withStatus(200); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/stripe-webhook", (request, response) -> { String payload = request.body(); String sigHeader = request.headers("Stripe-Signature"); String endpointSecret = dotenv.get("STRIPE_WEBHOOK_SECRET");
See all 61 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 "invoice.finalized": // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. break; case "customer.subscription.deleted": // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. break; default: // Unhandled event type } response.status(200); return ""; } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post( '/stripe-webhook', bodyParser.raw({ type: 'application/json' }), async (req, res) => { // Retrieve the event by verifying the signature using the raw body and secret. let event;
See all 72 lines try { event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(err); console.log(`⚠️ Webhook signature verification failed.`); console.log( `⚠️ Check the env file and enter the correct webhook secret.` ); return res.sendStatus(400); } // Extract the object from the event. const dataObject = event.data.object; // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch (event.type) { case 'invoice.paid': // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate limits. break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case 'invoice.finalized': // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. break; case 'customer.subscription.deleted': if (event.request != null) { // handle a subscription cancelled by your request // from above. } else { // handle subscription cancelled automatically based // upon your subscription settings. } break; case 'customer.subscription.trial_will_end': if (event.request != null) { // handle a subscription cancelled by your request // from above. } else { // handle subscription cancelled automatically based // upon your subscription settings. } break; default: // Unexpected event type } res.sendStatus(200); } );
// 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 != "checkout.session.completed" { return } cust, err := customer.Get(event.GetObjectValue("customer"), nil) if err != nil { log.Printf("customer.Get: %v", err) return } if event.GetObjectValue("display_items", "0", "custom") != "" && event.GetObjectValue("display_items", "0", "custom", "name") == "Pasha e-book" { log.Printf("🔔 Customer is subscribed and bought an e-book! Send the e-book to %s", cust.Email) } else { log.Printf("🔔 Customer is subscribed but did not buy an e-book.") } }
[HttpPost("stripe-webhook")] public async Task<IActionResult> Webhook() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent; try { stripeEvent = EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], this.options.Value.WebhookSecret ); Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}"); } catch (Exception e) { Console.WriteLine($"Something failed {e}"); return BadRequest(); } if (stripeEvent.Type == "invoice.paid") { // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. } if (stripeEvent.Type == "invoice.paid") { // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. } if (stripeEvent.Type == "invoice.payment_failed") { // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. } if (stripeEvent.Type == "invoice.finalized") { // If you want to manually send out invoices to your customers // or store them locally to reference to avoid hitting Stripe rate limits. } if (stripeEvent.Type == "customer.subscription.deleted") { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. } if (stripeEvent.Type == "customer.subscription.trial_will_end") { // Send notification to your user that the trial will end } return Ok(); }

Learn about other events your webhook can listen for.

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. For the premium service, the price includes tiers to represent the discount levels.

In this sample, each product bills at monthly intervals. The price for the basic product is 5 USD per seat, and the price for the premium product starts at 15 USD per seat and goes down as the customer purchases more seats.

Create the product object for the basic service:

stripe products create \ --name="Billing Guide: Basic Service" \ --description="Messaging service" \ -d unit_label="seat"

The response looks like:

{ "id": "prod_HMElQ1d5NR6Pvr",
See all 21 lines "object": "product", "active": true, "attributes": [ ], "created": 1590612924, "description": "Messaging service", "images": [ ], "livemode": false, "metadata": { }, "name": "Billing Guide: Basic Service", "statement_descriptor": null, "type": "service", "unit_label": "seat", "updated": 1590612924 }

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

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

The response looks like:

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

Create the product object for the premium service:

stripe products create \ --name="Billing Guide: Premium Service" \ --description="Messaging service" \ -d unit_label="seat"

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

stripe prices create \ -d product=prod_HZaLjvgB83z6Kv \ -d "tiers[0][unit_amount]"=1500 \ -d "tiers[0][up_to]"=5 \ -d "tiers[1][unit_amount]"=1000 \ -d "tiers[1][up_to]"=15 \ -d "tiers[2][unit_amount]"=500 \ -d "tiers[2][up_to]"=inf \ -d tiers_mode=volume \ -d billing_scheme=tiered \ -d currency=usd \ -d "recurring[interval]"=month \ -d "expand[]"=tiers

The response looks like:

{ "id": "price_1H0RCyDxzApwA5wcuKfVigQ1",
See all 49 lines "object": "price", "active": true, "billing_scheme": "tiered", "created": 1593691684, "currency": "usd", "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "product": "prod_HZaLjvgB83z6Kv", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed" }, "tiers": [ { "flat_amount": null, "flat_amount_decimal": null, "unit_amount": 1500, "unit_amount_decimal": "1500", "up_to": 5 }, { "flat_amount": null, "flat_amount_decimal": null, "unit_amount": 1000, "unit_amount_decimal": "1000", "up_to": 15 }, { "flat_amount": null, "flat_amount_decimal": null, "unit_amount": 500, "unit_amount_decimal": "500", "up_to": null } ], "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null }

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

Navigate to the Create a product page, and create two products, one for the basic service and one for the premium service.

Add a price to the basic product:

  • Pricing model: Standard
  • Price: 5 USD
  • Billing period: monthly

Add a price to the premium product. The price discounts are described by tiers:

  • Pricing model: Volume
  • First tier: 1-5 units at 15 USD per unit
  • Second tier: 6-15 units at 10 USD per unit
  • Third tier: 16+ units at 5 USD per unit
  • Billing period: monthly

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.

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

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

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/create-customer' do content_type 'application/json' data = JSON.parse request.body.read # Create a new customer object customer = Stripe::Customer.create(email: data['email']) # Recommendation: save the customer.id in your database. { 'customer': customer }.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/create-customer', methods=['POST']) def create_customer(): # Reads application/json and returns a response data = json.loads(request.data) try: # Create a new customer object customer = stripe.Customer.create( email=data['email'] ) # At this point, associate the ID of the Customer object with your # own internal representation of a customer, if you have one. return jsonify( customer=customer, ) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/create-customer', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $customer = $stripe->customers->create([ 'email' => $body->email ]); return $response->withJson(['customer' => $customer]); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/create-customer", (request, response) -> { response.type("application/json"); CreateCustomerBody postBody = gson.fromJson( request.body(), CreateCustomerBody.class ); CustomerCreateParams customerParams = CustomerCreateParams .builder() .setEmail(postBody.getEmail()) .build(); // Create a new customer object Customer customer = Customer.create(customerParams); Map<String, Object> responseData = new HashMap<>(); responseData.put("customer", customer); //we use StripeObject.PRETTY_PRINT_GSON.toJson() so that we get the JSON our client is expecting on the polymorphic //parameters that can either be object ids or the object themselves. If we tried to generate the JSON without call this, //for example, by calling gson.toJson(responseData) we will see something like "customer":{"id":"cus_XXX"} instead of //"customer":"cus_XXX". //If you only need to return 1 object, you can use the built in serializers, i.e. Subscription.retrieve("sub_XXX").toJson() return StripeObject.PRETTY_PRINT_GSON.toJson(responseData); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/create-customer', async (req, res) => { // Create a new customer object const customer = await stripe.customers.create({ email: req.body.email, }); // save the customer.id as stripeCustomerId // in your database. res.send({ customer }); });
func handleCreateCustomer(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.CustomerParams{ Email: stripe.String(req.Email), } c, err := customer.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("customer.New: %v", err) return } writeJSON(w, struct { Customer *stripe.Customer `json:"customer"` }{ Customer: c, }) }
using Newtonsoft.Json; public class CreateCustomerRequest { [JsonProperty("email")] public string Email { get; set; } }
using Newtonsoft.Json; using Stripe; public class CreateCustomerResponse { [JsonProperty("customer")] public Customer Customer { get; set; } }
[HttpPost("create-customer")] public ActionResult<CreateCustomerResponse> CreateCustomer([FromBody] CreateCustomerRequest req) { var options = new CustomerCreateOptions { Email = req.Email, }; var service = new CustomerService(); var customer = service.Create(options); return new CreateCustomerResponse { Customer = customer, }; }

Example customer response:

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

5 Collect payment information Client

Let your new customer choose a plan and provide payment information. In the sample, the customer chooses between Basic and Premium, and specifies the number of seats.

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.

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

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

function createSubscription({customerId, paymentMethodId, priceId}) { return ( fetch('/create-subscription', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, paymentMethodId: paymentMethodId, priceId: priceId, }), }) .then((response) => { return response.json(); }) // If the card is declined, display an error to the user. .then((result) => { if (result.error) { // The card had an error when trying to attach it to a customer. throw result; } return result; }) // Normalize the result to contain the object returned by Stripe. // Add the 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', quantity: data['quantity'] }], expand: %w[latest_invoice.payment_intent plan.product] ) 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: 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'), 'quantity': data['quantity'] } ], expand=['latest_invoice.payment_intent', 'plan.product'], ) 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', 'quantity' => $body->quantity, ], ], 'expand' => ['latest_invoice.payment_intent', 'plan.product'], ]); 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); SubscriptionCreateParams subCreateParams = SubscriptionCreateParams .builder() .addItem( SubscriptionCreateParams .Item.builder() .setPrice("price_HGd7M3DV3IMXkC") .setQuantity(postBody.getQuantity()) .build() ) .setCustomer(customer.getId()) .addAllExpand( Arrays.asList("latest_invoice.payment_intent", "plan.product") ) .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) => { // Set the default payment method on 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 } }); } let updateCustomerDefaultPaymentMethod = 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', quantity: req.body.quantity }, ], expand: ['latest_invoice.payment_intent', 'plan.product'], }); res.send(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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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"` Quantity int64 `json:"quantity"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONErrorMessage(w, err.Error(), 422) 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 { writeJSONErrorMessage(w, err.Error(), 422) 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 { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("customer.Update: %v %s", err, c.ID) return } // Create subscription subscriptionParams := &stripe.SubscriptionParams{ Customer: stripe.String(req.CustomerID), Items: []*stripe.SubscriptionItemsParams{ { Price: stripe.String("price_HGd7M3DV3IMXkC")), }, }, } subscriptionParams.AddExpand("latest_invoice.payment_intent") subscriptionParams.AddExpand("plan.product") s, err := sub.New(subscriptionParams) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("sub.New: %v", err) return } writeJSON(w, s) }
using Newtonsoft.Json; public class CreateSubscriptionRequest { [JsonRequired] [JsonProperty("paymentMethodId")] public string PaymentMethod { get; set; } [JsonRequired] [JsonProperty("customerId")] public string Customer { get; set; } [JsonRequired] [JsonProperty("priceId")] public string Price { get; set; } [JsonRequired] [JsonProperty("quantity")] public long Quantity { get; set; } }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("create-subscription")] public ActionResult<Subscription> CreateSubscription([FromBody] CreateSubscriptionRequest req) { // Attach payment method var options = new PaymentMethodAttachOptions { Customer = req.Customer, }; var service = new PaymentMethodService(); PaymentMethod paymentMethod; try { paymentMethod = service.Attach(req.PaymentMethod, options); } catch (Exception e) { return this.FailWithMessage($"Failed to attach payment method {e}"); } // Update customer's default invoice payment method var customerOptions = new CustomerUpdateOptions { InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = paymentMethod.Id, }, }; var customerService = new CustomerService(); try { customerService.Update(req.Customer, customerOptions); } catch (StripeException e) { return this.FailWithMessage($"Failed to attach payment method {e}"); } // Create subscription var subscriptionOptions = new SubscriptionCreateOptions { Customer = req.Customer, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Price = Environment.GetEnvironmentVariable(req.Price), Quantity = req.Quantity, }, }, }; subscriptionOptions.AddExpand("latest_invoice.payment_intent"); var subscriptionService = new SubscriptionService(); try { return subscriptionService.Create(subscriptionOptions); } catch (StripeException e) { return this.FailWithMessage($"Failed to attach payment method: {e}"); } }

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

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

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

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

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. Make sure to monitor the invoice.paid event on your webhook endpoint to verify that the payment succeeded and that you should provision the subscription.

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

8 Manage payment authentication Client and Server

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

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

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

function 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 after the payment method was attached to the customer object. Here’s how to handle the situation.

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

function 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. The sample adds a cancellation option to the account settings page.

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

Account settings with the ability to cancel the subscription

function cancelSubscription() { return fetch('/cancel-subscription', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId, }), }) .then(response => { return response.json(); }) .then(cancelSubscriptionResponse => { // Display to the user that the subscription has been cancelled. }); }

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

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/cancel-subscription' do content_type 'application/json' data = JSON.parse request.body.read deleted_subscription = Stripe::Subscription.delete(data['subscriptionId']) deleted_subscription.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/cancel-subscription', methods=['POST']) def cancelSubscription(): data = json.loads(request.data) try: # Cancel the subscription by deleting it deletedSubscription = stripe.Subscription.delete(data['subscriptionId']) return jsonify(deletedSubscription) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/cancel-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve( $body->subscriptionId ); $subscription->delete(); return $response->withJson($subscription); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/cancel-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer CancelPostBody postBody = gson.fromJson( request.body(), CancelPostBody.class ); Subscription subscription = Subscription.retrieve( postBody.getSubscriptionId() ); Subscription deletedSubscription = subscription.cancel(); return deletedSubscription.toJson(); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/cancel-subscription', async (req, res) => { // Delete the subscription const deletedSubscription = await stripe.subscriptions.del( req.body.subscriptionId ); res.send(deletedSubscription); });
func handleCancelSubscription(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { SubscriptionID string `json:"subscriptionId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Cancel(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Cancel: %v", err) return } writeJSON(w, s) }
using Newtonsoft.Json; public class CancelSubscriptionRequest { [JsonProperty("subscriptionId")] public string Subscription { get; set; } }
[HttpPost("cancel-subscription")] public ActionResult<Subscription> CancelSubscription([FromBody] CancelSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Cancel(req.Subscription, null); return subscription; }

After the subscription is canceled, update your database to remove the Stripe subscription ID you previously stored, and limit access to your service.

When a subscription is canceled, it cannot be reactivated. Instead, collect updated billing information from your customer, update their default payment method, and create a new subscription with their existing customer record.

11 Test your integration

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

Card number What it does
4242424242424242 Succeeds and immediately creates an active subscription.
4000002500003155 Requires authentication, with the payment_intent value of requires_action. The confirmCardPayment() triggers a modal that asks the customer to authenticate. When the user confirms, the subscription is active. See manage payment authentication.
4000008260003178 Always fails with a decline code of insufficient_funds. To handle this error on the backend, see create subscription.
4000000000000341 Succeeds when first attached to the customer object, but fails on first payment of the subscription, with the payment_intent value of requires_payment_method. See manage subscription payment failure.

See the documentation on testing Billing for more information and ways to test your integration.

Optional Let customers preview changes and change their plans Client and Server

The sample shows how your customers can change the plan they subscribe to from Basic to Premium, or change the number of seats in the subscription. To help them decide how to change plans, you can show them what different price change options look like in the UI, then ask them to confirm their selected change.

Sample UI for changing plans

This approach uses the upcoming invoice and update a subscription endpoints.

To get information about subscription changes, on the frontend, collect the new quantity or new price ID and pass them together with the customer ID and subscription ID to the backend:

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

On the backend, define the endpoint for your frontend to call. Note that the code must account differently for the case where the customer updates only the number of seats and for the case where they change their plan from Basic to Premium (or vice versa).

# 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 new_price = ENV[data['newPriceId'].upcase] quantity = data['quantity'] params = {} params[:customer] = data['customerId'] if !data['subscriptionId'].nil? subscription = Stripe::Subscription.retrieve(data['subscriptionId']) params[:subscription] = data['subscriptionId'] # compare the current price to the new price, and only create a new subscription if they are different # otherwise, just add seats to the existing subscription # subscription.plan.id would also work current_price = subscription.items.data[0].price.id params[:subscription_items] = if current_price == new_price [{ id: subscription.items.data[0].id, quantity: quantity }] else [ { id: subscription.items.data[0].id, deleted: true }, { price: new_price, quantity: quantity } ] end else params[:subscription_items] = [{ price: new_price, quantity: quantity }]
See all 67 lines end invoice = Stripe::Invoice.upcoming(params) response = {} # in the case where we are returning the upcoming invoice for a subscription change, calculate what the # invoice totals would be for the invoice we'll charge immediately when they confirm the change, and # also return the amount for the next period's invoice. if !subscription.nil? current_period_end = subscription.current_period_end immediate_total = 0 next_invoice_sum = 0 invoice.lines.data.each do |invoiceLineItem| if invoiceLineItem.period.end == current_period_end immediate_total += invoiceLineItem.amount else next_invoice_sum += invoiceLineItem.amount end end response = { immediate_total: immediate_total, next_invoice_sum: next_invoice_sum, invoice: invoice } else response = { invoice: invoice } end response.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: new_price = os.getenv(data['newPriceId'].upper()) quantity = data['quantity'] subscriptionId = data['subscriptionId'] params = dict( customer=data['customerId'] ) if subscriptionId != None: # Retrieve the subscription subscription = stripe.Subscription.retrieve(data['subscriptionId']) params["subscription"] = subscriptionId current_price = subscription['items']['data'][0].price.id if current_price == new_price: params["subscription_items"] = [ { "id": subscription['items']['data'][0].id, "quantity":quantity }] else:
See all 77 lines params["subscription_items"] = [ { "id": subscription['items']['data'][0].id, "deleted": True }, { "price": new_price, "quantity": quantity } ] else: params["subscription_items"] = [ { "price": new_price, "quantity": quantity } ] # Retrive the Invoice invoice = stripe.Invoice.upcoming(**params) response = {} if data['subscriptionId'] != None: current_period_end = subscription.current_period_end immediate_total = 0 next_invoice_sum = 0 for invoiceLineItem in invoice.lines.data: if invoiceLineItem.period.end == current_period_end: immediate_total += invoiceLineItem.amount else: next_invoice_sum += invoiceLineItem.amount response = { 'immediate_total': immediate_total, 'next_invoice_sum': next_invoice_sum, 'invoice': invoice } else: response = { 'invoice': invoice } return jsonify(response) 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; $new_price = getenv(strtoupper($body->newPriceId)); $params = []; $params['customer'] = $body->customerId; $subscriptionId = $body->subscriptionId; if ($subscriptionId != null) { $subscription = $stripe->subscriptions->retrieve($subscriptionId); $params['subscription'] = $subscriptionId; #compare the current price to the new price, and only create a new subscription if they are different #otherwise, just add seats to the existing subscription # subscription.plan.id would also work $current_price = $subscription->items->data[0]->price->id; if ($current_price == $new_price) { $params['subscription_items'] = [ [ 'id' => $subscription->items->data[0]->id, 'quantity' => $body->quantity ] ]; } else
See all 99 lines { $params['subscription_items'] = [ [ 'id' => $subscription->items->data[0]->id, 'deleted' => true ], [ 'price' => $new_price, 'quantity' => $body->quantity ] ]; } } else { $params['subscription_items'] = [ [ 'price' => $new_price, 'quantity' => $body->quantity ] ]; } $invoice = $stripe->invoices->upcoming($params); #in the case where we are returning the upcoming invoice for a subscription change, calculate what the #invoice totals would be for the invoice we'll charge immediately when they confirm the change, and #also return the amount for the next period's invoice. $responseParams = []; if ($subscription != null) { $current_period_end = $subscription->current_period_end; $immediate_total = 0; $next_invoice_sum = 0; foreach ($invoice->lines->data as $invoiceLineItem) { if ($invoiceLineItem->period->end == $current_period_end) { $immediate_total += $invoiceLineItem->amount; } else { $next_invoice_sum += $invoiceLineItem->amount; } } $responseParams = [ 'immediate_total' => $immediate_total, 'next_invoice_sum' => $next_invoice_sum, 'invoice' => $invoice ]; } else { $responseParams = [ 'invoice' => $invoice ]; } return $response->withJson($responseParams); });
// 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 ); String newPrice = dotenv.get(postBody.getNewPriceId().toUpperCase()); Long quantity = postBody.getQuantity(); InvoiceUpcomingParams.Builder invoiceParamsBuilder = new InvoiceUpcomingParams.Builder(); invoiceParamsBuilder.setCustomer(postBody.getCustomerId()); String subscriptionId = postBody.getSubscriptionId(); Subscription subscription = null; if (subscriptionId != null) { invoiceParamsBuilder.setSubscription(subscriptionId); subscription = Subscription.retrieve(subscriptionId); String currentPrice = subscription .getItems() .getData() .get(0) .getPrice() .getId(); if (currentPrice.equals(newPrice)) {
See all 96 lines invoiceParamsBuilder.addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setId(subscription.getItems().getData().get(0).getId()) .setQuantity(quantity) .build() ); } else { invoiceParamsBuilder.addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setId(subscription.getItems().getData().get(0).getId()) .setDeleted(true) .build() ); invoiceParamsBuilder.addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setPrice(newPrice) .setQuantity(quantity) .build() ); } } else { invoiceParamsBuilder.addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setPrice(newPrice) .setQuantity(quantity) .build() ); } Invoice invoice = Invoice.upcoming(invoiceParamsBuilder.build()); Map<String, Object> responseData = new HashMap<>(); /* * in the case where we are returning the upcoming invoice for a subscription * change, calculate what the invoice totals would be for the invoice we'll * charge immediately when they confirm the change, and also return the amount * for the next period's invoice. */ if (subscription != null) { Long currentPeriodEnd = subscription.getCurrentPeriodEnd(); Long immediateTotal = 0L; Long nextInvoiceSum = 0L; for (InvoiceLineItem invoiceLineItem : invoice .getLines() .autoPagingIterable()) { if ( invoiceLineItem.getPeriod().getEnd().equals(currentPeriodEnd) ) immediateTotal += invoiceLineItem.getAmount(); else nextInvoiceSum = invoiceLineItem.getAmount(); } responseData.put("immediate_total", immediateTotal); responseData.put("next_invoice_sum", nextInvoiceSum); } responseData.put("invoice", invoice); 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('/retrieve-upcoming-invoice', async (req, res) => { const new_price = process.env[req.body.newPriceId.toUpperCase()]; const quantity = req.body.quantity; const subscriptionId = req.body.subscriptionId; var params = {}; params['customer'] = req.body.customerId; var subscription; if (subscriptionId != null) { params['subscription'] = subscriptionId; subscription = await stripe.subscriptions.retrieve(subscriptionId); const current_price = subscription.items.data[0].price.id; if (current_price == new_price) { params['subscription_items'] = [ { id: subscription.items.data[0].id, quantity: quantity, }, ]; } else { params['subscription_items'] = [ { id: subscription.items.data[0].id, deleted: true, },
See all 78 lines { price: new_price, quantity: quantity, }, ]; } } else { params['subscription_items'] = [ { price: new_price, quantity: quantity, }, ]; } console.log('params are ' + JSON.stringify(params)); const invoice = await stripe.invoices.retrieveUpcoming(params); response = {}; if (subscriptionId != null) { const current_period_end = subscription.current_period_end; var immediate_total = 0; var next_invoice_sum = 0; invoice.lines.data.forEach((invoiceLineItem) => { if (invoiceLineItem.period.end == current_period_end) { immediate_total += invoiceLineItem.amount; } else { next_invoice_sum += invoiceLineItem.amount; } }); response = { immediate_total: immediate_total, next_invoice_sum: next_invoice_sum, invoice: invoice, }; } else { response = { invoice: invoice, }; } res.send(response); });
// 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 handleRetrieveUpcomingInvoice(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"` NewPriceID string `json:"newPriceId"` Quantity int64 `json:"quantity"` SubscriptionID string `json:"subscriptionId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("json.NewDecoder.Decode: %v", err) return } newPriceID := os.Getenv(req.NewPriceID) var items []*stripe.SubscriptionItemsParams var s stripe.Subscription if req.SubscriptionID != "" { s, err := sub.Get(req.SubscriptionID, nil) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("sub.Get: %v", err) return } currentPriceID := s.Items.Data[0].Price.ID if currentPriceID == newPriceID { items = []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Quantity: stripe.Int64(req.Quantity), }} } else { items = []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Deleted: stripe.Bool(true), }, { Price: stripe.String(newPriceID), Quantity: stripe.Int64(req.Quantity), }} } } else { items = []*stripe.SubscriptionItemsParams{{ Price: stripe.String(newPriceID), Quantity: stripe.Int64(req.Quantity), }} } var params *stripe.InvoiceParams if req.SubscriptionID != "" { params = &stripe.InvoiceParams{ Customer: stripe.String(req.CustomerID), Subscription: stripe.String(req.SubscriptionID), SubscriptionItems: items, } } else { params = &stripe.InvoiceParams{ Customer: stripe.String(req.CustomerID), SubscriptionItems: items, } } in, err := invoice.GetNext(params) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("invoice.GetNext: %v", err) return } // in the case where we are returning the upcoming invoice for a subscription change, calculate what the // invoice totals would be for the invoice we'll charge immediately when they confirm the change, and // also return the amount for the next period's invoice. if req.SubscriptionID == "" { writeJSON(w, struct { Invoice *stripe.Invoice `json:"invoice"` }{ Invoice: in, }) return } var immediateTotal int64 var nextInvoiceSum int64 for _, lineItem := range in.Lines.Data { if lineItem.Period.End == s.CurrentPeriodEnd { immediateTotal += lineItem.Amount } else { nextInvoiceSum += lineItem.Amount } } writeJSON(w, struct { ImmediateTotal int64 `json:"immediate_total"` NextInvoiceSum int64 `json:"next_invoice_sum"` Invoice *stripe.Invoice `json:"invoice"` }{ ImmediateTotal: immediateTotal, NextInvoiceSum: nextInvoiceSum, Invoice: in, }) }
using Newtonsoft.Json; public class RetrieveUpcomingInvoiceRequest { [JsonRequired] [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonRequired] [JsonProperty("newPriceId")] public string NewPrice { get; set; } [JsonRequired] [JsonProperty("quantity")] public long Quantity { get; set; } }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("retrieve-upcoming-invoice")] public ActionResult<RetrieveUpcomingInvoiceResponse> RetrieveUpcomingInvoice([FromBody] RetrieveUpcomingInvoiceRequest req) { if (!ModelState.IsValid) { return this.FailWithMessage("invalid params"); } var newPrice = Environment.GetEnvironmentVariable(req.NewPrice.ToUpper()); if (newPrice is null || newPrice == "") { return this.FailWithMessage($"No price with the new price ID ({req.NewPrice}) found in .env"); } List<InvoiceSubscriptionItemOptions> items; Subscription subscription = null; if (req.Subscription != "" && req.Subscription != null) { var subscriptionService = new SubscriptionService(); subscription = subscriptionService.Get(req.Subscription); var currentPrice = subscription.Items.Data[0].Price.Id; if (currentPrice == newPrice) { items = new List<InvoiceSubscriptionItemOptions> { new InvoiceSubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Quantity = req.Quantity, } }; } else { items = new List<InvoiceSubscriptionItemOptions> { new InvoiceSubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Deleted = true, }, new InvoiceSubscriptionItemOptions { Price = newPrice, Quantity = req.Quantity, }, }; } } else { items = new List<InvoiceSubscriptionItemOptions> { new InvoiceSubscriptionItemOptions { Price = newPrice, Quantity = req.Quantity, }, }; } var invoiceService = new InvoiceService(); var options = new UpcomingInvoiceOptions { Customer = req.Customer, Subscription = req.Subscription, SubscriptionItems = items, }; Invoice upcomingInvoice = invoiceService.Upcoming(options); if (req.Subscription == "" || req.Subscription is null) { return new RetrieveUpcomingInvoiceResponse { Invoice = upcomingInvoice, }; } else { var currentPeriodEnd = subscription.CurrentPeriodEnd; long immediateTotal = 0; long nextInvoiceSum = 0; foreach (var lineItem in upcomingInvoice.Lines.Data) { if (lineItem.Period.End == currentPeriodEnd) { immediateTotal += lineItem.Amount; } else { nextInvoiceSum += lineItem.Amount; } } return new RetrieveUpcomingInvoiceResponse { ImmediateTotal = immediateTotal, NextInvoiceSum = nextInvoiceSum, Invoice = upcomingInvoice, }; } }

When the customer confirms the subscription change, on the frontend pass the new quantity or new price ID to the backend. The sample passes the subscription ID from local storage, but you can retrieve it from your database for your logged in user.

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

On the backend, define the endpoint for your frontend to call, passing the subscription ID together with the new quantity or new price ID (as applicable). Like the retrieve-upcoming-invoice endpoint, this endpoint needs to account for the case where only the quantity is updated and for the case where the plan is changed.

# 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']) current_price = subscription.items.data[0].price.id new_price = ENV[data['newPriceId']] quantity = data['quantity'] updated_subscription = subscription if current_price == new_price updated_subscription = Stripe::Subscription.update( data['subscriptionId'], items: [{ id: subscription.items.data[0].id, quantity: quantity }], expand: %w[plan.product] ) else updated_subscription = Stripe::Subscription.update( data['subscriptionId'], items: [ { id: subscription.items.data[0].id, deleted: true }, { price: new_price, quantity: data['quantity'] } ], expand: %w[plan.product] ) end # invoice and charge the customer immediately for the payment representing any balance that the customer accrued # as a result of the change. For example, if the user added seats for this month, this would charge the proration amount for those # extra seats for the remaining part of the month. invoice = Stripe::Invoice.pay( Stripe::Invoice.create( { customer: subscription.customer, subscription: subscription.id, description: 'Change to #{quantity} seat(s) on the #{updated_subscription.plan.product.name} plan' } ).id ) { subscription: 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: new_price = os.getenv(data['newPriceId'].upper()) quantity = data['quantity'] subscriptionId = data['subscriptionId'] subscription = stripe.Subscription.retrieve(subscriptionId) current_price = subscription['items']['data'][0].price.id if current_price == new_price: updatedSubscription = stripe.Subscription.modify( subscriptionId, items=[{ 'id': subscription['items']['data'][0].id, 'quantity': quantity, }], expand=['plan.product'] ) else: updatedSubscription = stripe.Subscription.modify( subscriptionId, items=[{ 'id': subscription['items']['data'][0].id, 'deleted': True, }, { 'price': new_price, 'quantity': quantity }], expand=['plan.product'] ) invoice = stripe.Invoice.create( customer=subscription.customer, subscription=subscriptionId, description="Change to " + quantity + " seat(s) on the " + updatedSubscription.plan.product.name + " plan" ) invoice = stripe.Invoice.pay(invoice.id) 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); $current_price = $subscription->items->data[0]->price->id; $new_price = getenv(strtoupper($body->newPriceId)); $quantity = $body->quantity; if ($current_price == $new_price) { $this->logger->addInfo('updating quantity of existing item'); $updatedSubscription = $stripe->subscriptions->update( $body->subscriptionId, [ 'items' => [ [ 'id' => $subscription->items->data[0]->id, 'quantity' => $quantity ], ], 'expand' => ['plan.product']]); } else { $updatedSubscription = $stripe->subscriptions->update( $body->subscriptionId, [ 'items' => [ [ 'id' => $subscription->items->data[0]->id, 'deleted' => true ], [ 'price' => $new_price, 'quantity' => $quantity ], ], 'expand' => ['plan.product']]); } #invoice and charge the customer immediately for the payment representing any balance that the customer accrued #as a result of the change. For example, if the user added seats for this month, this would charge the proration amount for those # extra seats for the remaining part of the month. $invoice = $stripe->invoices->create([ 'customer' => $subscription->customer, 'subscription' => $subscription->id, 'description' => 'Change to '. $quantity . ' seat(s) on the '. $updatedSubscription->plan->product->name . ' plan' ]); $invoice = $invoice->pay(); return $response->withJson(['subscription' => $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() ); String newPrice = dotenv.get(postBody.getNewPriceId().toUpperCase()); Long quantity = postBody.getQuantity(); String currentPrice = subscription .getItems() .getData() .get(0) .getPrice() .getId(); SubscriptionUpdateParams.Builder updateParamsBuilder = new SubscriptionUpdateParams.Builder(); if (currentPrice.equals(newPrice)) { updateParamsBuilder.addItem( SubscriptionUpdateParams .Item.builder() .setId(subscription.getItems().getData().get(0).getId()) .setQuantity(quantity) .build() ); } else { updateParamsBuilder.addItem( SubscriptionUpdateParams .Item.builder() .setId(subscription.getItems().getData().get(0).getId()) .setDeleted(true) .build() ); updateParamsBuilder.addItem( SubscriptionUpdateParams .Item.builder() .setPrice(newPrice) .setQuantity(quantity) .build() ); } subscription = subscription.update( updateParamsBuilder .addAllExpand(Arrays.asList("plan.product")) .build() ); String planName = subscription.getPlan().getProductObject().getName(); Invoice invoice = Invoice.create( InvoiceCreateParams .builder() .setCustomer(subscription.getCustomer()) .setSubscription(subscription.getId()) .setDescription( "Change to " + quantity.toString() + " seat(s) on the the " + planName + " plan" ) .build() ); invoice = invoice.pay(); 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 subscriptionId = req.body.subscriptionId; const subscription = await stripe.subscriptions.retrieve(subscriptionId); const current_price = subscription.items.data[0].price.id; const new_price = process.env[req.body.newPriceId.toUpperCase()]; const quantity = req.body.quantity; var updatedSubscription; if (current_price == new_price) { updatedSubscription = await stripe.subscriptions.update(subscriptionId, { items: [ { id: subscription.items.data[0].id, quantity: quantity, }, ], }); } else { updatedSubscription = await stripe.subscriptions.update(subscriptionId, { items: [ { id: subscription.items.data[0].id, deleted: true, }, { price: new_price, quantity: quantity, }, ], expand: ['plan.product'], }); } var invoice = await stripe.invoices.create({ customer: subscription.customer, subscription: subscription.id, description: 'Change to ' + quantity + ' seat(s) on the ' + updatedSubscription.plan.product.name + ' plan', }); invoice = await stripe.invoices.pay(invoice.id); res.send(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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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"` Quantity int64 `json:"quantity"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Get(req.SubscriptionID, nil) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("sub.Get: %v", err) return } currentPriceID := s.Items.Data[0].Price.ID newPriceID := os.Getenv(req.NewPriceID) var params *stripe.SubscriptionParams if currentPriceID == newPriceID { params = &stripe.SubscriptionParams{ Items: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Quantity: stripe.Int64(req.Quantity), }}, } } else { params = &stripe.SubscriptionParams{ Items: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Deleted: stripe.Bool(true), }, { Price: stripe.String(newPriceID), Quantity: stripe.Int64(req.Quantity), }}, } } params.AddExpand("plan.product") updatedSubscription, err := sub.Update(req.SubscriptionID, params) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("sub.Update: %v", err) return } writeJSON(w, struct { Subscription *stripe.Subscription `json:"subscription"` }{ Subscription: updatedSubscription, }) }
using Newtonsoft.Json; public class UpdateSubscriptionRequest { [JsonRequired] [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonRequired] [JsonProperty("newPriceId")] public string NewPrice { get; set; } [JsonRequired] [JsonProperty("quantity")] public long Quantity { get; set; } }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("update-subscription")] public ActionResult<UpdateSubscriptionResponse> UpdateSubscription([FromBody] UpdateSubscriptionRequest req) { var newPrice = Environment.GetEnvironmentVariable(req.NewPrice); if (newPrice is null || newPrice == "") { return this.FailWithMessage($"No price with the new price ID ({req.NewPrice}) found in .env"); } var service = new SubscriptionService(); Subscription subscription; try { subscription = service.Get(req.Subscription); } catch (StripeException e) { return this.FailWithMessage($"Failed to retrieve subscription: {e}"); } var currentPrice = subscription.Items.Data[0].Price.Id; List<SubscriptionItemOptions> items; if (currentPrice == newPrice) { items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Quantity = req.Quantity, }, }; } else { items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Deleted = true, }, new SubscriptionItemOptions { Price = newPrice, Quantity = req.Quantity, }, }; } var options = new SubscriptionUpdateOptions { CancelAtPeriodEnd = false, Items = items, ProrationBehavior = "always_invoice", }; var updatedSubscription = service.Update(req.Subscription, options); return new UpdateSubscriptionResponse { Subscription = updatedSubscription, }; }

Optional Display subscription details Client and Server

To display details for the current subscription, on the frontend pass the subscription ID to a backend endpoint:

function getSubscriptionInformation(subscriptionId) { return fetch('/retrieve-subscription-information', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId }), }) .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-subscription-information' do content_type 'application/json' data = JSON.parse request.body.read subscriptionId = data['subscriptionId'] subscription = Stripe::Subscription.retrieve( { id: subscriptionId, expand: %w[ latest_invoice customer.invoice_settings.default_payment_method plan.product ] } ) upcoming_invoice = Stripe::Invoice.upcoming(subscription: subscriptionId) { card: subscription.customer.invoice_settings.default_payment_method.card, product_description: subscription.plan.product.name, current_price: subscription.plan.id, current_quantity: subscription.items.data[0].quantity, latest_invoice: subscription.latest_invoice, upcoming_invoice: upcoming_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-subscription-information', methods=['POST']) def retrieve_subscription_information(): data = json.loads(request.data) subscriptionId = data['subscriptionId'] try: subscription = stripe.Subscription.retrieve( subscriptionId, expand=['latest_invoice', 'customer.invoice_settings.default_payment_method', 'plan.product'] ) upcoming_invoice = stripe.Invoice.upcoming(subscription=subscriptionId) return jsonify( card=subscription.customer.invoice_settings.default_payment_method.card, product_description=subscription.plan.product.name, current_price=subscription.plan.id, current_quantity=subscription['items']['data'][0].quantity, latest_invoice=subscription.latest_invoice, upcoming_invoice=upcoming_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-subscription-information', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscriptionId = $body->subscriptionId; $subscription = $stripe->subscriptions->retrieve( $subscriptionId, [ 'expand' => ['latest_invoice', 'customer.invoice_settings.default_payment_method', 'plan.product'] ]); $upcomingInvoice = $stripe->invoices->upcoming(['subscription' => $subscriptionId]); return $response->withJson([ 'card' => $subscription->customer->invoice_settings->default_payment_method->card, 'product_description' => $subscription->plan->product->name, 'current_price' => $subscription->plan->id, 'current_quantity' => $subscription->items->data[0]->quantity, 'latest_invoice' => $subscription->latest_invoice, 'upcoming_invoice' => $upcomingInvoice ]); });
// 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-subscription-information", (request, response) -> { SubscriptionPostBody postBody = gson.fromJson( request.body(), SubscriptionPostBody.class ); SubscriptionRetrieveParams subParams = SubscriptionRetrieveParams .builder() .addAllExpand( Arrays.asList( "latest_invoice", "customer.invoice_settings.default_payment_method", "plan.product" ) ) .build(); Subscription subscription = Subscription.retrieve( postBody.getSubscriptionId(), subParams, null ); InvoiceUpcomingParams invoiceParams = InvoiceUpcomingParams .builder() .setSubscription(subscription.getId()) .build(); Invoice upcomingInvoice = Invoice.upcoming(invoiceParams); Map<String, Object> responseData = new HashMap<>(); responseData.put( "card", subscription .getCustomerObject() .getInvoiceSettings() .getDefaultPaymentMethodObject() .getCard() ); responseData.put( "product_description", subscription.getPlan().getProductObject().getName() ); responseData.put("current_price", subscription.getPlan().getId()); responseData.put( "current_quantity", subscription.getItems().getData().get(0).getQuantity() ); responseData.put( "latest_invoice", subscription.getLatestInvoiceObject() ); responseData.put("upcoming_invoice", upcomingInvoice); // 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('/retrieve-subscription-information', async (req, res) => { const subscriptionId = req.body.subscriptionId; console.log( 'subscription id is ' + subscriptionId + ' type is ' + typeof subscriptionId ); const subscription = await stripe.subscriptions.retrieve(subscriptionId, { expand: [ 'latest_invoice', 'customer.invoice_settings.default_payment_method', 'plan.product', ], }); const upcoming_invoice = await stripe.invoices.retrieveUpcoming({ subscription: subscriptionId, }); res.send({ card: subscription.customer.invoice_settings.default_payment_method.card, product_description: subscription.plan.product.name, current_price: subscription.plan.id, current_quantity: subscription.items.data[0].quantity, latest_invoice: subscription.latest_invoice, upcoming_invoice: upcoming_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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" func handleRetrieveSubscriptionInformation(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 } params := &stripe.SubscriptionParams{} params.AddExpand("latest_invoice") params.AddExpand("customer.invoice_settings.default_payment_method") params.AddExpand("plan.product") s, err := sub.Get(req.SubscriptionID, params) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("sub.Get: %v", err) return } invoiceParams := &stripe.InvoiceParams{ Subscription: stripe.String(req.SubscriptionID), } upcomingInvoice, err := invoice.GetNext(invoiceParams) if err != nil { writeJSONErrorMessage(w, err.Error(), 422) log.Printf("invoice.GetNext: %v", err) return } writeJSON(w, struct { Card *stripe.PaymentMethodCard `json:"card"` Description string `json:"product_description"` CurrentPriceID string `json:"current_price"` CurrentQuantity int64 `json:"current_quantity"` LatestInvoice *stripe.Invoice `json:"latest_invoice"` UpcomingInvoice *stripe.Invoice `json:"upcoming_invoice"` }{ Card: s.Customer.InvoiceSettings.DefaultPaymentMethod.Card, Description: s.Plan.Product.Name, CurrentPriceID: s.Plan.ID, LatestInvoice: s.LatestInvoice, UpcomingInvoice: upcomingInvoice, }) }
using Newtonsoft.Json; public class RetrieveSubscriptionInformationRequest { [JsonRequired] [JsonProperty("subscriptionId")] public string Subscription { get; set; } }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("retrieve-subscription-information")] public ActionResult<RetrieveSubscriptionInformationResponse> RetrieveSubscriptionInformation([FromBody] RetrieveSubscriptionInformationRequest req) { var options = new SubscriptionGetOptions(); options.AddExpand("latest_invoice"); options.AddExpand("customer.invoice_settings.default_payment_method"); options.AddExpand("plan.product"); var service = new SubscriptionService(); Subscription subscription; try { subscription = service.Get(req.Subscription, options); } catch (StripeException e) { return this.FailWithMessage($"Failed to retrieve subscription with ID ({req.Subscription}): {e}"); } var invoiceOptions = new UpcomingInvoiceOptions { Subscription = req.Subscription, }; var invoiceService = new InvoiceService(); Invoice upcomingInvoice; try { upcomingInvoice = invoiceService.Upcoming(invoiceOptions); } catch (StripeException e) { return this.FailWithMessage($"Failed to retrieve upcoming invoice: {e}"); } return new RetrieveSubscriptionInformationResponse { Card = subscription.Customer.InvoiceSettings.DefaultPaymentMethod.Card, ProductDescription = subscription.Plan.Product.Name, CurrentPrice = subscription.Plan.Id, CurrentQuantity = subscription.Items.Data[0].Quantity, LatestInvoice = subscription.LatestInvoice, UpcomingInvoice = upcomingInvoice, }; }

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, pass 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.
You can unsubscribe at any time. Read our privacy policy.