Create fixed-price subscriptions
Learn how to offer multiple pricing options to your customers and charge them a fixed amount each month.
This guide walks you through how to create fixed-price subscriptions for a photo hosting service. It shows you how to use Stripe Elements to create a custom payment form you embed in your application. You can find code for an example implementation with Elements on GitHub.
You can also use Checkout if you don’t want to build a custom payment form, or one of our quickstart options if you aren’t ready to build an integration yet.
What you’ll build
This guide shows you how to:
- Model your subscriptions with Products and Prices
- Create a signup flow
- Collect payment information and create the subscription
- Test and monitor payment and subscription status
- Handle payment errors
- Let customers change their plan or cancel the subscription
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# Available as a gem gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'# If you use bundler, you can add this line to your Gemfile gem 'stripe'
# Install through pip pip install --upgrade stripe# Install through pip pip install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/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# 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# Install the PHP library via Composer composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases# 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 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 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# 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# Install via npm npm install --save stripe
# Make sure your project is using Go Modules go mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71# Make sure your project is using Go Modules go mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71
// Then import the package import ( "github.com/stripe/stripe-go/v71" )// Then import the package import ( "github.com/stripe/stripe-go/v71" )
# Install via dotnet dotnet add package Stripe.net dotnet restore# Install via dotnet dotnet add package Stripe.net dotnet restore
# Or install via NuGet PM> Install-Package Stripe.net# 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/stripebrew install stripe/stripe-cli/stripe
To install the Stripe CLI on macOS without homebrew:
- Download the latest
mac-os
tar.gz file from https://github.com/stripe/stripe-cli/releases/latest - 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 Debian and Ubuntu-based distributions:
- Add Bintray’s GPG key to the apt sources keyring:
sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 379CE192D401AB61sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 379CE192D401AB61
- 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.listecho "deb https://dl.bintray.com/stripe/stripe-cli-deb stable main" | sudo tee -a /etc/apt/sources.list
- Update the package list:
sudo apt-get updatesudo apt-get update
- Install the CLI:
sudo apt-get install stripesudo apt-get install stripe
To install the Stripe CLI on RedHat and CentOS-based distributions:
- 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/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/
- Install the CLI:
sudo yum install stripesudo yum install stripe
To install the Stripe CLI on Linux without a package manager:
- Download the latest
linux
tar.gz file from https://github.com/stripe/stripe-cli/releases/latest - Unzip the file:
tar -xvf stripe_X.X.X_linux_x86_64.tar.gz
- Run the executable:
./stripe
To install the Stripe CLI with Scoop, run:
scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.gitscoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
scoop install stripescoop install stripe
To install the Stripe CLI on Windows without Scoop, run:
- Download the latest
windows
tar.gz file from https://github.com/stripe/stripe-cli/releases/latest - Unzip the
stripe_X.X.X_windows_x86_64.zip
file - 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:latestdocker run --rm -it stripe/stripe-cli:latest
To run the Stripe CLI, you must also pair it with your Stripe account. Run stripe login
and follow the prompts. For more information, see the Stripe CLI documentation page.
2 Test as you go Server
You can test different payment scenarios with Stripe’s test cards, but testing also includes checking events to monitor the status of subscriptions. If you’re building a subscription integration for the first time, monitoring events in the Dashboard might be enough to help monitor your work while you follow the steps.
A production-ready integration should monitor events automatically, though. You can call the Stripe API and check specific values in the response (polling), or you can set up a webhook endpoint and let Stripe push events to your integration. Webhook monitoring is simpler and more efficient, especially for asynchronous integrations like subscriptions. The Stripe CLI provides a listen command for testing event monitoring during development.
Set up webhook monitoring
You can set up a webhook endpoint in the Dashboard, or with the Webhook Endpoints API.
Here’s how to set up your webhook handler and verify the signature of the event. Note that the example specifies the major event types to monitor for subscriptions, but you can add more as needed.
See all 62 lines # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = JSON.parse(payload, symbolize_names: true) event = Stripe::Event.construct_from(data) end # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] data = event['data'] data_object = data['object'] if event_type == 'invoice.paid' # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. # puts data_object end if event_type == 'invoice.payment_failed' # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. # puts data_object end if event_type == 'customer.subscription.deleted' # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. # puts data_object end content_type 'application/json' { status: 'success' }.to_json end# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' 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? # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = JSON.parse(payload, symbolize_names: true) event = Stripe::Event.construct_from(data) end # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] data = event['data'] data_object = data['object'] if event_type == 'invoice.paid' # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. # puts data_object end if event_type == 'invoice.payment_failed' # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. # puts data_object end if event_type == 'customer.subscription.deleted' # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. # puts data_object end content_type 'application/json' { status: 'success' }.to_json end# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' 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 48 lines if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] data_object = data['object'] if event_type == 'invoice.paid': # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. print(data) if event_type == 'invoice.payment_failed': # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. print(data) if event_type == 'customer.subscription.deleted': # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. print(data) return jsonify({'status': 'success'})# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.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) if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] data_object = data['object'] if event_type == 'invoice.paid': # Used to provision services after the trial has ended. # The status of the invoice will show up as paid. Store the status in your # database to reference when a user accesses your service to avoid hitting rate # limits. print(data) if event_type == 'invoice.payment_failed': # If the payment fails or the customer does not have a valid payment method, # an invoice.payment_failed event is sent, the subscription becomes past_due. # Use this webhook to notify your user that their payment has # failed and to retrieve new card details. print(data) if event_type == 'customer.subscription.deleted': # handle subscription cancelled automatically based # upon your subscription settings. Or if the user cancels it. print(data) return jsonify({'status': 'success'})# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.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 59 lines $webhookSecret = getenv('STRIPE_WEBHOOK_SECRET'); if ($webhookSecret) { try { $event = $stripe->webhooks->constructEvent( $request->getBody(), $request->getHeaderLine('stripe-signature'), $webhookSecret ); } catch (Exception $e) { return $response->withJson([ 'error' => $e->getMessage() ])->withStatus(403); } } else { $event = $request->getParsedBody(); } $type = $event['type']; $object = $event['data']['object']; // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch ($type) { case 'invoice.paid': // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. $logger->info('đź”” Webhook received! ' . $object); break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. $logger->info('đź”” Webhook received! ' . $object); break; case 'customer.subscription.deleted': // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. $logger->info('đź”” Webhook received! ' . $object); break; // ... handle other event types default: // Unhandled event type } return $response->withJson([ 'status' => 'success' ])->withStatus(200); });// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\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) $webhookSecret = getenv('STRIPE_WEBHOOK_SECRET'); if ($webhookSecret) { try { $event = $stripe->webhooks->constructEvent( $request->getBody(), $request->getHeaderLine('stripe-signature'), $webhookSecret ); } catch (Exception $e) { return $response->withJson([ 'error' => $e->getMessage() ])->withStatus(403); } } else { $event = $request->getParsedBody(); } $type = $event['type']; $object = $event['data']['object']; // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch ($type) { case 'invoice.paid': // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. $logger->info('đź”” Webhook received! ' . $object); break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. $logger->info('đź”” Webhook received! ' . $object); break; case 'customer.subscription.deleted': // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. $logger->info('đź”” Webhook received! ' . $object); break; // ... handle other event types default: // Unhandled event type } return $response->withJson([ 'status' => 'success' ])->withStatus(200); });// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\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 57 lines Event event = null; try { event = Webhook.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { // Invalid signature response.status(400); return ""; } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } switch (event.getType()) { case "invoice.paid": // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. break; case "invoice.payment_failed": // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case "customer.subscription.deleted": // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. break; default: // Unhandled event type } response.status(200); return ""; } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/stripe-webhook", (request, response) -> { String payload = request.body(); String sigHeader = request.headers("Stripe-Signature"); String endpointSecret = dotenv.get("STRIPE_WEBHOOK_SECRET"); Event event = null; try { event = Webhook.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { // Invalid signature response.status(400); return ""; } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } switch (event.getType()) { case "invoice.paid": // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. break; case "invoice.payment_failed": // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case "customer.subscription.deleted": // handle subscription cancelled automatically based // upon your subscription settings. Or if the user // cancels it. break; default: // Unhandled event type } response.status(200); return ""; } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys 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 60 lines let event; try { event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(err); console.log(`⚠️ Webhook signature verification failed.`); console.log( `⚠️ Check the env file and enter the correct webhook secret.` ); return res.sendStatus(400); } // Extract the object from the event. const dataObject = event.data.object; // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch (event.type) { case 'invoice.paid': // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate limits. break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case 'customer.subscription.deleted': if (event.request != null) { // handle a subscription cancelled by your request // from above. } else { // handle subscription cancelled automatically based // upon your subscription settings. } break; default: // Unexpected event type } res.sendStatus(200); } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const Stripe = require('stripe'); const stripe = 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; try { event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(err); console.log(`⚠️ Webhook signature verification failed.`); console.log( `⚠️ Check the env file and enter the correct webhook secret.` ); return res.sendStatus(400); } // Extract the object from the event. const dataObject = event.data.object; // Handle the event // Review important events for Billing webhooks // https://stripe.com/docs/billing/webhooks // Remove comment to see the various objects sent for this sample switch (event.type) { case 'invoice.paid': // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate limits. break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. break; case 'customer.subscription.deleted': if (event.request != null) { // handle a subscription cancelled by your request // from above. } else { // handle subscription cancelled automatically based // upon your subscription settings. } break; default: // Unexpected event type } res.sendStatus(200); } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const Stripe = require('stripe'); const stripe = 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.
See all 45 lines if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("ioutil.ReadAll: %v", err) return } event, err := webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), os.Getenv("STRIPE_WEBHOOK_SECRET")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("webhook.ConstructEvent: %v", err) return } if event.Type == "invoice.paid" { // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. return } if event.Type == "invoice.payment_failed" { // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. return } if event.Type == "customer.subscription.deleted" { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. { return } }// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" func handleWebhook(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } b, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("ioutil.ReadAll: %v", err) return } event, err := webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), os.Getenv("STRIPE_WEBHOOK_SECRET")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("webhook.ConstructEvent: %v", err) return } if event.Type == "invoice.paid" { // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. return } if event.Type == "invoice.payment_failed" { // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. return } if event.Type == "customer.subscription.deleted" { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. { return } }// 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)
See all 47 lines { stripeEvent = EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], this.options.Value.WebhookSecret ); Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}"); } catch (Exception e) { Console.WriteLine($"Something failed {e}"); return BadRequest(); } if (stripeEvent.Type == "invoice.paid") { // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. } if (stripeEvent.Type == "invoice.payment_failed") { // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. } if (stripeEvent.Type == "customer.subscription.deleted") { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. } return Ok(); }// 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("stripe-webhook")] public async Task<IActionResult> Webhook() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent; try { stripeEvent = EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], this.options.Value.WebhookSecret ); Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}"); } catch (Exception e) { Console.WriteLine($"Something failed {e}"); return BadRequest(); } if (stripeEvent.Type == "invoice.paid") { // Used to provision services after the trial has ended. // The status of the invoice will show up as paid. Store the status in your // database to reference when a user accesses your service to avoid hitting rate // limits. } if (stripeEvent.Type == "invoice.payment_failed") { // If the payment fails or the customer does not have a valid payment method, // an invoice.payment_failed event is sent, the subscription becomes past_due. // Use this webhook to notify your user that their payment has // failed and to retrieve new card details. } if (stripeEvent.Type == "customer.subscription.deleted") { // handle subscription cancelled automatically based // upon your subscription settings. Or if the user cancels it. } return Ok(); }// 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("stripe-webhook")] public async Task<IActionResult> Webhook() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent; try
Event types to monitor are indicated at the steps in this guide where they’re applicable. For more events you can monitor, see Subscription events.
3 Create the business model Stripe CLI or Dashboard
You create your productsProducts represent items your customer can subscribe to with a Subscription. An associated Price object describes the pricing and other terms of the subscription. and their pricing options in the Dashboard or with the Stripe CLI. A fixed-price service with two different options, named Basic and Premium, needs a product and a price for each option.
In this sample, each product bills at monthly intervals. The price for one product is 5 USD, and the other is 15 USD.
Navigate to the Create a product page, and create two products. Add one price for each product, each with a monthly billing interval:
- Basic product
- Price: 5.00 USD
- Premium product
- Price: 15.00 USD
After you create the prices, record the price IDs so they can be used in subsequent steps. Each ID is displayed in the Pricing section of the product and should look similar to this: price_G0FvDp6vZvdwRZ
.
The Copy to live mode button at the top right of the page lets you clone your product from test mode to live mode when you’re ready.
Create the product objects:
# Premium product stripe products create \ --name="Billing Guide: Premium Service" \ --description="Premium service with extra features" # Basic product stripe products create \ --name="Billing Guide: Basic Service" \ --description="Basic service with minimum features"# Premium product stripe products create \ --name="Billing Guide: Premium Service" \ --description="Premium service with extra features" # Basic product stripe products create \ --name="Billing Guide: Basic Service" \ --description="Basic service with minimum features"
Record the product ID for each product. They look like this:
See all 21 lines "object": "product", "active": true, "attributes": [ ], "created": 1587577341, "description": "Premium service with extra features", "images": [ ], "livemode": false, "metadata": { }, "name": "Billing Guide: Premium Service", "statement_descriptor": null, "type": "service", "unit_label": null, "updated": 1587577341 }{ "id": "prod_H94k5odtwJXMtQ", "object": "product", "active": true, "attributes": [ ], "created": 1587577341, "description": "Premium service with extra features", "images": [ ], "livemode": false, "metadata": { }, "name": "Billing Guide: Premium Service", "statement_descriptor": null, "type": "service", "unit_label": null, "updated": 1587577341 }{ "id": "prod_H94k5odtwJXMtQ",
Use the product IDs to create a price for each product. Note that unit_amount
is specified in cents, so 1500
= 15 USD, for example.
# Premium price stripe prices create \ -d product=prod_H94k5odtwJXMtQ \ -d unit_amount=1500 \ -d currency=usd \ -d "recurring[interval]"=month # Basic price stripe prices create \ -d product=prod_HGd6W1VUqqXGvr \ -d unit_amount=500 \ -d currency=usd \ -d "recurring[interval]"=month# Premium price stripe prices create \ -d product=prod_H94k5odtwJXMtQ \ -d unit_amount=1500 \ -d currency=usd \ -d "recurring[interval]"=month # Basic price stripe prices create \ -d product=prod_HGd6W1VUqqXGvr \ -d unit_amount=500 \ -d currency=usd \ -d "recurring[interval]"=month
Record the price ID for each price so they can be used in subsequent steps. They look like this:
See all 27 lines "object": "price", "product": "prod_HGd6W1VUqqXGvr", "type": "recurring", "currency": "usd", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed" }, "active": true, "billing_scheme": "per_unit", "created": 1589319695, "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "unit_amount": 1500, "unit_amount_decimal": "1500", "tiers": null, "tiers_mode": null, "transform_quantity": null }{ "id": "price_HGd7M3DV3IMXkC", "object": "price", "product": "prod_HGd6W1VUqqXGvr", "type": "recurring", "currency": "usd", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed" }, "active": true, "billing_scheme": "per_unit", "created": 1589319695, "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "unit_amount": 1500, "unit_amount_decimal": "1500", "tiers": null, "tiers_mode": null, "transform_quantity": null }{ "id": "price_HGd7M3DV3IMXkC",
4 Create the Stripe customer Client and Server
In this guide, you create a customerStripe Customer objects allow you to perform recurring charges for the same customer, and to track multiple charges. If you create subscriptions, the subscription id is passed to the customer object. 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 return result; }); }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 return result; }); }
You should receive a customer.created
event.
5 Collect payment information Client
Let your customer choose a plan and provide payment information. In this guide, the customer chooses between Basic and Premium.
Then use Stripe Elements to collect card information, and customize Elements to match the look-and-feel of the application.

Selecting a plan and collecting payment details
Set up Stripe Elements
Stripe Elements is included with Stripe.js. Include the Stripe.js script on your checkout page by adding it to the head
of your HTML file.
Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.
<head> <title>Subscription prices</title> <script src="https://js.stripe.com/v3/"></script> </head><head> <title>Subscription prices</title> <script src="https://js.stripe.com/v3/"></script> </head>
Create an instance of Elements with the following JavaScript:
// 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 let stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); let elements = stripe.elements();// 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 let stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); let 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.
<body> <form id="payment-form"> <div id="card-element"> <!-- Elements will create input elements here --> </div> <!-- We'll put the error messages in this element --> <div id="card-element-errors" role="alert"></div> <button type="submit">Subscribe</button> </form> </body><body> <form id="payment-form"> <div id="card-element"> <!-- Elements will create input elements here --> </div> <!-- We'll put the error messages in this element --> <div id="card-element-errors" role="alert"></div> <button type="submit">Subscribe</button> </form> </body>
Create an instance of an Element and mount it to the Element container:
let card = elements.create('card', { style: style }); card.mount('#card-element');let card = elements.create('card', { style: style }); card.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.
card.on('change', function (event) { displayError(event); }); function displayError(event) { changeLoadingStatePrices(false); let displayError = document.getElementById('card-element-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } }card.on('change', function (event) { displayError(event); }); function displayError(event) { changeLoadingStatePrices(false); let displayError = document.getElementById('card-element-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.
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, passing the ID of the customer you already created:
var form = document.getElementById('subscription-form'); form.addEventListener('submit', function (ev) { ev.preventDefault(); }); function createPaymentMethod({ card }) { const customerId = {{ CUSTOMER_ID }}; // Set up payment method for recurring usage let billingName = document.querySelector('#name').value; let priceId = document.getElementById('priceId').innerHTML.toUpperCase(); stripe .createPaymentMethod({ type: 'card', card: card, billing_details: { name: billingName, }, }) .then((result) => { if (result.error) { displayError(result); } else { createSubscription({ customerId: customerId, paymentMethodId: result.paymentMethod.id, priceId: priceId, }); } }); }var form = document.getElementById('subscription-form'); form.addEventListener('submit', function (ev) { ev.preventDefault(); }); function createPaymentMethod({ card }) { const customerId = {{ CUSTOMER_ID }}; // Set up payment method for recurring usage let billingName = document.querySelector('#name').value; let priceId = document.getElementById('priceId').innerHTML.toUpperCase(); stripe .createPaymentMethod({ type: 'card', card: card, billing_details: { name: billingName, }, }) .then((result) => { if (result.error) { displayError(result); } 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); }) ); }function createSubscription({ customerId, paymentMethodId, priceId }) { return ( fetch('/create-subscription', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, paymentMethodId: paymentMethodId, priceId: priceId, }), }) .then((response) => { return response.json(); }) // If the card is declined, display an error to the user. .then((result) => { if (result.error) { // The card had an error when trying to attach it to a customer. throw result; } return result; }) // Normalize the result to contain the object returned by Stripe. // Add the additional details we need. .then((result) => { return { paymentMethodId: paymentMethodId, priceId: priceId, subscription: result, }; }) // Some payment methods require a customer to be on session // to complete the payment process. Check the status of the // payment intent to handle these actions. .then(handlePaymentThatRequiresCustomerAction) // If attaching this card to a Customer object succeeds, // but attempts to charge the customer fail, you // get a requires_payment_method error. .then(handleRequiresPaymentMethod) // No more actions required. Provision your service for the user. .then(onSubscriptionComplete) .catch((error) => { // An error has happened. Display the failure to the user here. // We utilize the HTML element we created. showCardError(error); }) ); }
On the backend, define the endpoint that creates the subscription for the frontend to call. The code updates the customer with the payment method, and then passes the customer ID to the subscription. The payment method is also assigned as the default payment method for the subscription invoices.
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/create-subscription' do content_type 'application/json' data = JSON.parse request.body.read begin Stripe::PaymentMethod.attach( data['paymentMethodId'], { customer: data['customerId'] } ) rescue Stripe::CardError => e halt 200, { 'Content-Type' => 'application/json' }, { 'error': { message: e.error.message } }.to_json end # Set the default payment method on the customer Stripe::Customer.update( data['customerId'], invoice_settings: { default_payment_method: data['paymentMethodId'] } ) # Create the subscription subscription = Stripe::Subscription.create( customer: data['customerId'], items: [{ price: 'price_HGd7M3DV3IMXkC' }], expand: %w[latest_invoice.payment_intent] ) subscription.to_json end# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/create-subscription' do content_type 'application/json' data = JSON.parse request.body.read begin Stripe::PaymentMethod.attach( data['paymentMethodId'], { customer: data['customerId'] } ) rescue Stripe::CardError => e halt 200, { 'Content-Type' => 'application/json' }, { 'error': { message: e.error.message } }.to_json end # Set the default payment method on the customer Stripe::Customer.update( data['customerId'], invoice_settings: { default_payment_method: data['paymentMethodId'] } ) # Create the subscription subscription = Stripe::Subscription.create( customer: data['customerId'], items: [{ price: 'price_HGd7M3DV3IMXkC' }], expand: %w[latest_invoice.payment_intent] ) subscription.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/create-subscription', methods=['POST']) def createSubscription(): data = json.loads(request.data) try: # Attach the payment method to the customer stripe.PaymentMethod.attach( data['paymentMethodId'], customer=data['customerId'], ) # Set the default payment method on the customer stripe.Customer.modify( data['customerId'], invoice_settings={ 'default_payment_method': data['paymentMethodId'], }, ) # Create the subscription subscription = stripe.Subscription.create( customer=data['customerId'], items=[ { 'price': 'price_HGd7M3DV3IMXkC' } ], expand=['latest_invoice.payment_intent'], ) return jsonify(subscription) except Exception as e: return jsonify(error={'message': str(e)}), 200# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/create-subscription', methods=['POST']) def createSubscription(): data = json.loads(request.data) try: # Attach the payment method to the customer stripe.PaymentMethod.attach( data['paymentMethodId'], customer=data['customerId'], ) # Set the default payment method on the customer stripe.Customer.modify( data['customerId'], invoice_settings={ 'default_payment_method': data['paymentMethodId'], }, ) # Create the subscription subscription = stripe.Subscription.create( customer=data['customerId'], items=[ { 'price': 'price_HGd7M3DV3IMXkC' } ], expand=['latest_invoice.payment_intent'], ) return jsonify(subscription) except Exception as e: return jsonify(error={'message': str(e)}), 200
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/create-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; try { $payment_method = $stripe->paymentMethods->retrieve( $body->paymentMethodId ); $payment_method->attach([ 'customer' => $body->customerId, ]); } catch (Exception $e) { return $response->withJson($e->jsonBody); } // Set the default payment method on the customer $stripe->customers->update($body->customerId, [ 'invoice_settings' => [ 'default_payment_method' => $body->paymentMethodId ] ]); // Create the subscription $subscription = $stripe->subscriptions->create([ 'customer' => $body->customerId, 'items' => [ [ 'price' => 'price_HGd7M3DV3IMXkC', ], ], 'expand' => ['latest_invoice.payment_intent'], ]); return $response->withJson($subscription); });// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/create-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; try { $payment_method = $stripe->paymentMethods->retrieve( $body->paymentMethodId ); $payment_method->attach([ 'customer' => $body->customerId, ]); } catch (Exception $e) { return $response->withJson($e->jsonBody); } // Set the default payment method on the customer $stripe->customers->update($body->customerId, [ 'invoice_settings' => [ 'default_payment_method' => $body->paymentMethodId ] ]); // Create the subscription $subscription = $stripe->subscriptions->create([ 'customer' => $body->customerId, 'items' => [ [ 'price' => 'price_HGd7M3DV3IMXkC', ], ], 'expand' => ['latest_invoice.payment_intent'], ]); return $response->withJson($subscription); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/create-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer CreateSubscriptionBody postBody = gson.fromJson( request.body(), CreateSubscriptionBody.class ); Customer customer = Customer.retrieve(postBody.getCustomerId()); try { // Set the default payment method on the customer PaymentMethod pm = PaymentMethod.retrieve( postBody.getPaymentMethodId() ); pm.attach( PaymentMethodAttachParams .builder() .setCustomer(customer.getId()) .build() ); } catch (CardException e) { // Since it's a decline, CardException will be caught Map<String, String> responseErrorMessage = new HashMap<>(); responseErrorMessage.put("message", e.getLocalizedMessage()); Map<String, Object> responseError = new HashMap<>(); responseError.put("error", responseErrorMessage); return gson.toJson(responseError); } CustomerUpdateParams customerUpdateParams = CustomerUpdateParams .builder() .setInvoiceSettings( CustomerUpdateParams .InvoiceSettings.builder() .setDefaultPaymentMethod(postBody.getPaymentMethodId()) .build() ) .build(); customer.update(customerUpdateParams); // Create the subscription SubscriptionCreateParams subCreateParams = SubscriptionCreateParams .builder() .addItem( SubscriptionCreateParams .Item.builder() .setPrice("price_HGd7M3DV3IMXkC") .build() ) .setCustomer(customer.getId()) .addAllExpand(Arrays.asList("latest_invoice.payment_intent")) .build(); Subscription subscription = Subscription.create(subCreateParams); return subscription.toJson(); } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/create-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer CreateSubscriptionBody postBody = gson.fromJson( request.body(), CreateSubscriptionBody.class ); Customer customer = Customer.retrieve(postBody.getCustomerId()); try { // Set the default payment method on the customer PaymentMethod pm = PaymentMethod.retrieve( postBody.getPaymentMethodId() ); pm.attach( PaymentMethodAttachParams .builder() .setCustomer(customer.getId()) .build() ); } catch (CardException e) { // Since it's a decline, CardException will be caught Map<String, String> responseErrorMessage = new HashMap<>(); responseErrorMessage.put("message", e.getLocalizedMessage()); Map<String, Object> responseError = new HashMap<>(); responseError.put("error", responseErrorMessage); return gson.toJson(responseError); } CustomerUpdateParams customerUpdateParams = CustomerUpdateParams .builder() .setInvoiceSettings( CustomerUpdateParams .InvoiceSettings.builder() .setDefaultPaymentMethod(postBody.getPaymentMethodId()) .build() ) .build(); customer.update(customerUpdateParams); // Create the subscription SubscriptionCreateParams subCreateParams = SubscriptionCreateParams .builder() .addItem( SubscriptionCreateParams .Item.builder() .setPrice("price_HGd7M3DV3IMXkC") .build() ) .setCustomer(customer.getId()) .addAllExpand(Arrays.asList("latest_invoice.payment_intent")) .build(); Subscription subscription = Subscription.create(subCreateParams); return subscription.toJson(); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const Stripe = require('stripe'); const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/create-subscription', async (req, res) => { // Attach the payment method to the customer try { await stripe.paymentMethods.attach(req.body.paymentMethodId, { customer: req.body.customerId, }); } catch (error) { return res.status('402').send({ error: { message: error.message } }); } // Change the default invoice settings on the customer to the new payment method await stripe.customers.update( req.body.customerId, { invoice_settings: { default_payment_method: req.body.paymentMethodId, }, } ); // Create the subscription const subscription = await stripe.subscriptions.create({ customer: req.body.customerId, items: [{ price: 'price_HGd7M3DV3IMXkC' }], expand: ['latest_invoice.payment_intent'], }); res.send(subscription); });// 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'); const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/create-subscription', async (req, res) => { // Attach the payment method to the customer try { await stripe.paymentMethods.attach(req.body.paymentMethodId, { customer: req.body.customerId, }); } catch (error) { return res.status('402').send({ error: { message: error.message } }); } // Change the default invoice settings on the customer to the new payment method await stripe.customers.update( req.body.customerId, { invoice_settings: { default_payment_method: req.body.paymentMethodId, }, } ); // Create the subscription const subscription = await stripe.subscriptions.create({ customer: req.body.customerId, items: [{ price: 'price_HGd7M3DV3IMXkC' }], expand: ['latest_invoice.payment_intent'], }); res.send(subscription); });
// 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"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // Attach PaymentMethod params := &stripe.PaymentMethodAttachParams{ Customer: stripe.String(req.CustomerID), } pm, err := paymentmethod.Attach( req.PaymentMethodID, params, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("paymentmethod.Attach: %v %s", err, pm.ID) return } // Update invoice settings default customerParams := &stripe.CustomerParams{ InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{ DefaultPaymentMethod: stripe.String(pm.ID), }, } c, err := customer.Update( req.CustomerID, customerParams, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("customer.Update: %v %s", err, c.ID) return } // Create subscription subscriptionParams := &stripe.SubscriptionParams{ Customer: stripe.String(req.CustomerID), Items: []*stripe.SubscriptionItemsParams{ { Plan: stripe.String(os.Getenv(req.PriceID)), }, }, } subscriptionParams.AddExpand("latest_invoice.payment_intent") s, err := sub.New(subscriptionParams) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.New: %v", err) return } writeJSON(w, s) }// 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"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // Attach PaymentMethod params := &stripe.PaymentMethodAttachParams{ Customer: stripe.String(req.CustomerID), } pm, err := paymentmethod.Attach( req.PaymentMethodID, params, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("paymentmethod.Attach: %v %s", err, pm.ID) return } // Update invoice settings default customerParams := &stripe.CustomerParams{ InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{ DefaultPaymentMethod: stripe.String(pm.ID), }, } c, err := customer.Update( req.CustomerID, customerParams, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("customer.Update: %v %s", err, c.ID) return } // Create subscription subscriptionParams := &stripe.SubscriptionParams{ Customer: stripe.String(req.CustomerID), Items: []*stripe.SubscriptionItemsParams{ { Plan: stripe.String(os.Getenv(req.PriceID)), }, }, } subscriptionParams.AddExpand("latest_invoice.payment_intent") s, err := sub.New(subscriptionParams) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.New: %v", err) return } writeJSON(w, s) }
using Newtonsoft.Json; public class CreateSubscriptionRequest { [JsonProperty("paymentMethodId")] public string PaymentMethod { get; set; } [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("priceId")] public string Price { get; set; } }using Newtonsoft.Json; public class CreateSubscriptionRequest { [JsonProperty("paymentMethodId")] public string PaymentMethod { get; set; } [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("priceId")] public string Price { get; set; } }
// 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(); var paymentMethod = service.Attach(req.PaymentMethod, options); // Update customer's default invoice payment method var customerOptions = new CustomerUpdateOptions { InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = paymentMethod.Id, }, }; var customerService = new CustomerService(); customerService.Update(req.Customer, customerOptions); // Create subscription var subscriptionOptions = new SubscriptionCreateOptions { Customer = req.Customer, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Price = Environment.GetEnvironmentVariable(req.Price), }, }, }; subscriptionOptions.AddExpand("latest_invoice.payment_intent"); var subscriptionService = new SubscriptionService(); try { Subscription subscription = subscriptionService.Create(subscriptionOptions); return subscription; } catch (StripeException e) { Console.WriteLine($"Failed to create subscription.{e}"); return BadRequest(); } }// 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(); var paymentMethod = service.Attach(req.PaymentMethod, options); // Update customer's default invoice payment method var customerOptions = new CustomerUpdateOptions { InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = paymentMethod.Id, }, }; var customerService = new CustomerService(); customerService.Update(req.Customer, customerOptions); // Create subscription var subscriptionOptions = new SubscriptionCreateOptions { Customer = req.Customer, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Price = Environment.GetEnvironmentVariable(req.Price), }, }, }; subscriptionOptions.AddExpand("latest_invoice.payment_intent"); var subscriptionService = new SubscriptionService(); try { Subscription subscription = subscriptionService.Create(subscriptionOptions); return subscription; } catch (StripeException e) { Console.WriteLine($"Failed to create subscription.{e}"); return BadRequest(); } }
Here’s an example response. The minimum fields to store are highlighted, but you should store whatever your application will frequently access.
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 }{ "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", "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 }{ "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",
The example verifies initial payment status with the API response by checking the value of subscription.latest_invoice.payment_intent.status
. The invoice tracks payment status for the subscription; the payment intent tracks the status of the provided payment method. To retrieve subscription.latest_invoice.payment_intent.status
, you expand the latest_invoice
child object of the response.
You should also receive an invoice.paid
event. You can disregard this event for initial payment, but monitor it for subsequent payments. The invoice.paid
event type corresponds to the payment_intent.status
of succeeded
, so payment is complete, and the subscription status is active
.
7 Provision access to your service Client and Server
To give the customer access to your service:
- Verify the subscription status is
active
. - 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.
- Store the
product.id
andsubscription.id
in your database along with thecustomer.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. } }function onSubscriptionComplete(result) { // Payment was successful. if (result.subscription.status === 'active') { // Change your UI to show a success message to your customer. // Call your backend to grant access to your service based on // `result.subscription.items.data[0].price.product` the customer subscribed to. } }
It’s possible for a user to leave your application after payment is made and before this function is called. Continue to monitor the invoice.paid
event on your webhook endpoint to verify that the payment succeeded and that you should provision the subscription.
This is also good practice because during the lifecycle of the subscription, you need to keep provisioning in sync with subscription status. Otherwise, customers might be able to access your service even if their payments fail.
8 Manage payment authentication Client and Server
If you support payment methods that require customer authentication with 3D Secure, the value of latest_invoice.payment_intent.status
is initially requires_action
. The response from the createSubscription
call looks like this:
{ "id": "sub_1ELI8bClCIKljWvsvK36TXlC", "object": "subscription", "status": "incomplete", ... "latest_invoice": { "id": "in_EmGqfJMYy3Nt9M", "status": "open", ... "payment_intent": { "status": "requires_action", "client_secret": "pi_91_secret_W9", "next_action": { "type": "use_stripe_sdk", ... }, ... } } }{ "id": "sub_1ELI8bClCIKljWvsvK36TXlC", "object": "subscription", "status": "incomplete", ... "latest_invoice": { "id": "in_EmGqfJMYy3Nt9M", "status": "open", ... "payment_intent": { "status": "requires_action", "client_secret": "pi_91_secret_W9", "next_action": { "type": "use_stripe_sdk", ... }, ... } } }
In production, you’d monitor the invoice.payment_action_required
event type.
To handle this scenario, on the frontend notify the customer that authentication is required to complete payment and start the subscription. Retrieve the client secret for the payment intent, and pass it in a call to stripe.confirmCardPayment
.
function handlePaymentThatRequiresCustomerAction({ subscription, invoice, priceId, paymentMethodId, isRetry, }) { if (subscription && subscription.status === 'active') { // Subscription is active, no customer actions required. return { subscription, priceId, paymentMethodId }; } // If it's a first payment attempt, the payment intent is on the subscription latest invoice. // If it's a retry, the payment intent will be on the invoice itself. let paymentIntent = invoice ? invoice.payment_intent : subscription.latest_invoice.payment_intent; if ( paymentIntent.status === 'requires_action' || (isRetry === true && paymentIntent.status === 'requires_payment_method') ) { return stripe .confirmCardPayment(paymentIntent.client_secret, { payment_method: paymentMethodId, }) .then((result) => { if (result.error) { // Start code flow to handle updating the payment details. // Display error message in your UI. // The card was declined (i.e. insufficient funds, card has expired, etc). throw result; } else { if (result.paymentIntent.status === 'succeeded') { // Show a success message to your customer. return { priceId: priceId, subscription: subscription, invoice: invoice, paymentMethodId: paymentMethodId, }; } } }) .catch((error) => { displayError(error); }); } else { // No customer action needed. return { subscription, priceId, paymentMethodId }; } }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. return { priceId: priceId, subscription: subscription, invoice: invoice, paymentMethodId: paymentMethodId, }; } } }) .catch((error) => { displayError(error); }); } else { // No customer action needed. return { subscription, priceId, paymentMethodId }; } }
This displays an authentication modal to your customers, attempts payment, then closes the modal and returns context to your application.
Make sure to monitor the invoice.paid
event on your webhook endpoint to verify that the payment succeeded. It’s possible for users to leave your application before confirmCardPayment()
finishes, so verifying whether the payment succeeded allows you to correctly provision your product.
9 Manage subscription payment failure Client and Server
If the value of subscription.latest_invoice.payment_intent.status
is requires_payment_method
, the card was processed when the customer first provided card details, but payment then failed later – in a production scenario, if a customer’s card was stolen or canceled after the subscription was set up, for example. The example relies on the API response object so that you can test error handling. In production you’d monitor the invoice.payment_failed
webhook event for payments after initial payment success.
Catch the error, let your customer know their card was declined, and return them to the payment form to try a different card.
function handleRequiresPaymentMethod({ subscription, paymentMethodId, priceId, }) { if (subscription.status === 'active') { // subscription is active, no customer actions required. return { subscription, priceId, paymentMethodId }; } else if ( subscription.latest_invoice.payment_intent.status === 'requires_payment_method' ) { // Using localStorage to manage the state of the retry here, // feel free to replace with what you prefer. // Store the latest invoice ID and status. localStorage.setItem('latestInvoiceId', subscription.latest_invoice.id); localStorage.setItem( 'latestInvoicePaymentIntentStatus', subscription.latest_invoice.payment_intent.status ); throw { error: { message: 'Your card was declined.' } }; } else { return { subscription, priceId, paymentMethodId }; } }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); }) ); }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' 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.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\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 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'); const stripe = 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); });// 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'); const stripe = 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); });
// 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 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) }// 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 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; } }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; } }
// 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("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; }// 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("retry-invoice")] public ActionResult<Invoice> RetryInvoice([FromBody] RetryInvoiceRequest req) { // Attach payment method var options = new PaymentMethodAttachOptions { Customer = req.Customer, }; var service = new PaymentMethodService(); var paymentMethod = service.Attach(req.PaymentMethod, options); // Update customer's default invoice payment method var customerOptions = new CustomerUpdateOptions { InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = paymentMethod.Id, }, }; var customerService = new CustomerService(); customerService.Update(req.Customer, customerOptions); var invoiceOptions = new InvoiceGetOptions(); invoiceOptions.AddExpand("payment_intent"); var invoiceService = new InvoiceService(); Invoice invoice = invoiceService.Get(req.Invoice, invoiceOptions); return invoice; }
10 Cancel the subscription Client and Server
It’s common to allow customers to cancel their subscriptions. This example adds a cancellation option to the account settings page.
The example collects the subscription ID on the frontend, but you will most likely get this information from your database for your logged in user.
Account settings with the ability to cancel the subscription
function cancelSubscription() { return fetch('/cancel-subscription', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId, }), }) .then(response => { return response.json(); }) .then(cancelSubscriptionResponse => { // Display to the user that the subscription has been cancelled. }); }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' 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.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\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 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'); const stripe = 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); });// 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'); const stripe = 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); });
// 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 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) }// 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 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; } }using Newtonsoft.Json; public class CancelSubscriptionRequest { [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("cancel-subscription")] public ActionResult<Subscription> CancelSubscription([FromBody] CancelSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Cancel(req.Subscription, null); return 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 StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("cancel-subscription")] public ActionResult<Subscription> CancelSubscription([FromBody] CancelSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Cancel(req.Subscription, null); return subscription; }
You should receive a customer.subscription.deleted
event.
After the subscription is canceled, update your database to remove the Stripe subscription ID you previously stored, and limit access to your service.
When a subscription is canceled, it cannot be reactivated. Instead, collect updated billing information from your customer, update their default payment method, and create a new subscription with their existing customer record.
11 Test your integration
To make sure your integration is ready for production, you can work with the following test cards. Use them with any CVC, postal code, and future expiration date.
Card number | What it does |
---|---|
4242424242424242 | Succeeds and immediately creates an active subscription. |
4000002500003155 |
Requires authentication. confirmCardPayment() will trigger a modal asking for the customer to authenticate. Once the user confirms, the subscription will become active. See manage payment authentication.
|
4000008260003178 |
Always fails with a decline code of insufficient_funds . See create subscription step on how to handle this server side.
|
4000000000000341 |
Succeeds when it initially attaches to Customer object, but fails on the first payment of a subscription with the payment_intent value of requires_payment_method . See the manage subscription payment failure step.
|
Check out the documentation for testing Billing for more information and ways to test your integration.
Optional Let customers change their plans Client and Server
To let your customers change their subscription, collect the price ID of the option they want to change to. Then send the new price ID from the frontend to a backend endpoint. This example also passes the subscription ID, but you can retrieve it from your database for your logged in user.
function updateSubscription(priceId, subscriptionId) { return fetch('/update-subscription', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId, newPriceId: priceId, }), }) .then(response => { return response.json(); }) .then(response => { return response; }); }function updateSubscription(priceId, subscriptionId) { return fetch('/update-subscription', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ subscriptionId: subscriptionId, newPriceId: priceId, }), }) .then(response => { return response.json(); }) .then(response => { return response; }); }
On the backend, define the endpoint for your frontend to call, passing the subscription ID and the new price ID. The subscription is now Premium, at 15 USD per month, instead of Basic at 5 USD per month.
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/update-subscription' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve(data['subscriptionId']) updated_subscription = Stripe::Subscription.update( data['subscriptionId'], cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: 'price_H1NlVtpo6ubk0m' } ] ) updated_subscription.to_json end# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/update-subscription' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve(data['subscriptionId']) updated_subscription = Stripe::Subscription.update( data['subscriptionId'], cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: 'price_H1NlVtpo6ubk0m' } ] ) updated_subscription.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/update-subscription', methods=['POST']) def updateSubscription(): data = json.loads(request.data) try: subscription = stripe.Subscription.retrieve(data['subscriptionId']) updatedSubscription = stripe.Subscription.modify( data['subscriptionId'], cancel_at_period_end=False, items=[{ 'id': subscription['items']['data'][0].id, 'price': 'price_H1NlVtpo6ubk0m', }] ) return jsonify(updatedSubscription) except Exception as e: return jsonify(error=str(e)), 403# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/update-subscription', methods=['POST']) def updateSubscription(): data = json.loads(request.data) try: subscription = stripe.Subscription.retrieve(data['subscriptionId']) updatedSubscription = stripe.Subscription.modify( data['subscriptionId'], cancel_at_period_end=False, items=[{ 'id': subscription['items']['data'][0].id, 'price': 'price_H1NlVtpo6ubk0m', }] ) return jsonify(updatedSubscription) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/update-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve($body->subscriptionId); $updatedSubscription = $stripe->subscriptions->update($body->subscriptionId, [ 'items' => [ [ 'id' => $subscription->items->data[0]->id, 'price' => 'price_H1NlVtpo6ubk0m', ], ], ]); return $response->withJson($updatedSubscription); });// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/update-subscription', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve($body->subscriptionId); $updatedSubscription = $stripe->subscriptions->update($body->subscriptionId, [ 'items' => [ [ 'id' => $subscription->items->data[0]->id, 'price' => 'price_H1NlVtpo6ubk0m', ], ], ]); return $response->withJson($updatedSubscription); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/update-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer UpdatePostBody postBody = gson.fromJson( request.body(), UpdatePostBody.class ); Subscription subscription = Subscription.retrieve( postBody.getSubscriptionId() ); SubscriptionUpdateParams params = SubscriptionUpdateParams .builder() .addItem( SubscriptionUpdateParams .Item.builder() .setId(subscription.getItems().getData().get(0).getId()) .setPrice("price_H1NlVtpo6ubk0m") .build() ) .setCancelAtPeriodEnd(false) .build(); subscription.update(params); return subscription.toJson(); } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/update-subscription", (request, response) -> { response.type("application/json"); // Set the default payment method on the customer UpdatePostBody postBody = gson.fromJson( request.body(), UpdatePostBody.class ); Subscription subscription = Subscription.retrieve( postBody.getSubscriptionId() ); SubscriptionUpdateParams params = SubscriptionUpdateParams .builder() .addItem( SubscriptionUpdateParams .Item.builder() .setId(subscription.getItems().getData().get(0).getId()) .setPrice("price_H1NlVtpo6ubk0m") .build() ) .setCancelAtPeriodEnd(false) .build(); subscription.update(params); return subscription.toJson(); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const Stripe = require('stripe'); const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/update-subscription', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const updatedSubscription = await stripe.subscriptions.update( req.body.subscriptionId, { cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: "price_H1NlVtpo6ubk0m", }, ], } ); res.send(updatedSubscription); });// 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'); const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/update-subscription', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const updatedSubscription = await stripe.subscriptions.update( req.body.subscriptionId, { cancel_at_period_end: false, items: [ { id: subscription.items.data[0].id, price: "price_H1NlVtpo6ubk0m", }, ], } ); res.send(updatedSubscription); });
// 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"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Get(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Get: %v", err) return } params := &stripe.SubscriptionParams{ CancelAtPeriodEnd: stripe.Bool(false), Items: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Price: stripe.String("price_H1NlVtpo6ubk0m"), }}, } updatedSubscription, err := sub.Update(req.SubscriptionID, params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Update: %v", err) return } writeJSON(w, updatedSubscription) }// 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"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Get(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Get: %v", err) return } params := &stripe.SubscriptionParams{ CancelAtPeriodEnd: stripe.Bool(false), Items: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Price: stripe.String("price_H1NlVtpo6ubk0m"), }}, } updatedSubscription, err := sub.Update(req.SubscriptionID, params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Update: %v", err) return } writeJSON(w, updatedSubscription) }
using Newtonsoft.Json; public class UpdateSubscriptionRequest { [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonProperty("newPriceId")] public string NewPrice { get; set; } }using Newtonsoft.Json; public class UpdateSubscriptionRequest { [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonProperty("newPriceId")] public string NewPrice { 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<Subscription> UpdateSubscription([FromBody] UpdateSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Get(req.Subscription); var options = new SubscriptionUpdateOptions { CancelAtPeriodEnd = false, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Price = "price_H1NlVtpo6ubk0m", } } }; var updatedSubscription = service.Update(req.Subscription, options); return updatedSubscription; }// 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<Subscription> UpdateSubscription([FromBody] UpdateSubscriptionRequest req) { var service = new SubscriptionService(); var subscription = service.Get(req.Subscription); var options = new SubscriptionUpdateOptions { CancelAtPeriodEnd = false, Items = new List<SubscriptionItemOptions> { new SubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Price = "price_H1NlVtpo6ubk0m", } } }; var updatedSubscription = service.Update(req.Subscription, options); return updatedSubscription; }
You should receive a customer.subscription.updated
event.
Optional Preview a price change Client and Server
When your customer changes their subscription, there’s often an adjustment to the amount they owe, known as a proration. You can use the upcoming invoice endpoint to display the adjusted amount to your customers.
On the frontend, pass the upcoming invoice details to a backend endpoint.
function retrieveUpcomingInvoice( customerId, subscriptionId, newPriceId, trialEndDate ) { return fetch('/retrieve-upcoming-invoice', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, subscriptionId: subscriptionId, newPriceId: newPriceId, }), }) .then(response => { return response.json(); }) .then(invoice { return invoice; }); }function retrieveUpcomingInvoice( customerId, subscriptionId, newPriceId, trialEndDate ) { return fetch('/retrieve-upcoming-invoice', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ customerId: customerId, subscriptionId: subscriptionId, newPriceId: newPriceId, }), }) .then(response => { return response.json(); }) .then(invoice { return invoice; }); }
On the backend, define the endpoint for your frontend to call.
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retrieve-upcoming-invoice' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve(data['subscriptionId']) invoice = Stripe::Invoice.upcoming( customer: data['customerId'], subscription: data['subscriptionId'], subscription_items: [ { id: subscription.items.data[0].id, deleted: true }, { price: ENV[data['newPriceId']], deleted: false } ] ) invoice.to_json end# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/retrieve-upcoming-invoice' do content_type 'application/json' data = JSON.parse request.body.read subscription = Stripe::Subscription.retrieve(data['subscriptionId']) invoice = Stripe::Invoice.upcoming( customer: data['customerId'], subscription: data['subscriptionId'], subscription_items: [ { id: subscription.items.data[0].id, deleted: true }, { price: ENV[data['newPriceId']], deleted: false } ] ) invoice.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retrieve-upcoming-invoice', methods=['POST']) def retrieveUpcomingInvoice(): data = json.loads(request.data) try: # Retrieve the subscription subscription = stripe.Subscription.retrieve(data['subscriptionId']) # Retrieve the invoice invoice = stripe.Invoice.upcoming( customer=data['customerId'], subscription=data['subscriptionId'], subscription_items=[ { 'id': subscription['items']['data'][0].id, 'deleted': True, }, { 'price': 'price_H1NlVtpo6ubk0m', 'deleted': False, } ], ) return jsonify(invoice) except Exception as e: return jsonify(error=str(e)), 403# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/retrieve-upcoming-invoice', methods=['POST']) def retrieveUpcomingInvoice(): data = json.loads(request.data) try: # Retrieve the subscription subscription = stripe.Subscription.retrieve(data['subscriptionId']) # Retrieve the invoice invoice = stripe.Invoice.upcoming( customer=data['customerId'], subscription=data['subscriptionId'], subscription_items=[ { 'id': subscription['items']['data'][0].id, 'deleted': True, }, { 'price': 'price_H1NlVtpo6ubk0m', 'deleted': False, } ], ) return jsonify(invoice) except Exception as e: return jsonify(error=str(e)), 403
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retrieve-upcoming-invoice', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve( $body->subscriptionId ); $invoice = $stripe->invoices->upcoming([ "customer" => $body->customerId, "subscription_prorate" => true, "subscription" => $body->subscriptionId, "subscription_items" => [ [ 'id' => $subscription->items->data[0]->id, 'deleted' => true ], [ 'price' => getenv($body->newPriceId), 'deleted' => false ], ] ]); return $response->withJson($invoice); });// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/retrieve-upcoming-invoice', function (Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); $stripe = $this->stripe; $subscription = $stripe->subscriptions->retrieve( $body->subscriptionId ); $invoice = $stripe->invoices->upcoming([ "customer" => $body->customerId, "subscription_prorate" => true, "subscription" => $body->subscriptionId, "subscription_items" => [ [ 'id' => $subscription->items->data[0]->id, 'deleted' => true ], [ 'price' => getenv($body->newPriceId), 'deleted' => false ], ] ]); return $response->withJson($invoice); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/retrieve-upcoming-invoice", (request, response) -> { response.type("application/json"); UpcomingInvoicePostBody postBody = gson.fromJson( request.body(), UpcomingInvoicePostBody.class ); Subscription subscription = Subscription.retrieve( postBody.getSubscriptionId() ); InvoiceUpcomingParams invoiceParams = InvoiceUpcomingParams .builder() .setCustomer(postBody.getCustomerId()) .setSubscription(postBody.getSubscriptionId()) .addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setId(subscription.getItems().getData().get(0).getId()) .setDeleted(true) .build() ) .addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setPrice(dotenv.get(postBody.getNewPriceId().toUpperCase())) .build() ) .build(); Invoice invoice = Invoice.upcoming(invoiceParams); return invoice.toJson(); } );// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post( "/retrieve-upcoming-invoice", (request, response) -> { response.type("application/json"); UpcomingInvoicePostBody postBody = gson.fromJson( request.body(), UpcomingInvoicePostBody.class ); Subscription subscription = Subscription.retrieve( postBody.getSubscriptionId() ); InvoiceUpcomingParams invoiceParams = InvoiceUpcomingParams .builder() .setCustomer(postBody.getCustomerId()) .setSubscription(postBody.getSubscriptionId()) .addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setId(subscription.getItems().getData().get(0).getId()) .setDeleted(true) .build() ) .addSubscriptionItem( InvoiceUpcomingParams .SubscriptionItem.builder() .setPrice(dotenv.get(postBody.getNewPriceId().toUpperCase())) .build() ) .build(); Invoice invoice = Invoice.upcoming(invoiceParams); return invoice.toJson(); } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const Stripe = require('stripe'); const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/retrieve-upcoming-invoice', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const invoice = await stripe.invoices.retrieveUpcoming({ subscription_prorate: true, customer: req.body.customerId, subscription: req.body.subscriptionId, subscription_items: [ { id: subscription.items.data[0].id, deleted: true, }, { // This price ID is the price you want to change the subscription to. price: 'price_H1NlVtpo6ubk0m', deleted: false, }, ], }); res.send(invoice); });// 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'); const stripe = Stripe('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/retrieve-upcoming-invoice', async (req, res) => { const subscription = await stripe.subscriptions.retrieve( req.body.subscriptionId ); const invoice = await stripe.invoices.retrieveUpcoming({ subscription_prorate: true, customer: req.body.customerId, subscription: req.body.subscriptionId, subscription_items: [ { id: subscription.items.data[0].id, deleted: true, }, { // This price ID is the price you want to change the subscription to. price: 'price_H1NlVtpo6ubk0m', deleted: false, }, ], }); res.send(invoice); });
// 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 { SubscriptionID string `json:"subscriptionId"` CustomerID string `json:"customerId"` NewPriceID string `json:"newPriceId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Get(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Get: %v", err) return } params := &stripe.InvoiceParams{ Customer: stripe.String(req.CustomerID), Subscription: stripe.String(req.SubscriptionID), SubscriptionItems: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Deleted: stripe.Bool(true), }, { Price: stripe.String(os.Getenv(req.NewPriceID)), Deleted: stripe.Bool(false), }}, } in, err := invoice.GetNext(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("invoice.GetNext: %v", err) return } writeJSON(w, in) }// 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 { SubscriptionID string `json:"subscriptionId"` CustomerID string `json:"customerId"` NewPriceID string `json:"newPriceId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } s, err := sub.Get(req.SubscriptionID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("sub.Get: %v", err) return } params := &stripe.InvoiceParams{ Customer: stripe.String(req.CustomerID), Subscription: stripe.String(req.SubscriptionID), SubscriptionItems: []*stripe.SubscriptionItemsParams{{ ID: stripe.String(s.Items.Data[0].ID), Deleted: stripe.Bool(true), }, { Price: stripe.String(os.Getenv(req.NewPriceID)), Deleted: stripe.Bool(false), }}, } in, err := invoice.GetNext(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("invoice.GetNext: %v", err) return } writeJSON(w, in) }
using Newtonsoft.Json; public class RetrieveUpcomingInvoiceRequest { [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonProperty("newPriceId")] public string NewPrice { get; set; } }using Newtonsoft.Json; public class RetrieveUpcomingInvoiceRequest { [JsonProperty("customerId")] public string Customer { get; set; } [JsonProperty("subscriptionId")] public string Subscription { get; set; } [JsonProperty("newPriceId")] public string NewPrice { get; set; } }
// 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<Invoice> RetrieveUpcomingInvoice([FromBody] RetrieveUpcomingInvoiceRequest req) { var service = new SubscriptionService(); var subscription = service.Get(req.Subscription); var invoiceService = new InvoiceService(); var options = new UpcomingInvoiceOptions { Customer = req.Customer, Subscription = req.Subscription, SubscriptionItems = new List<InvoiceSubscriptionItemOptions> { new InvoiceSubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Deleted = true, }, new InvoiceSubscriptionItemOptions { // TODO: This should be Price, but isn't in Stripe.net yet. Plan = Environment.GetEnvironmentVariable(req.NewPrice), Deleted = false, }, } }; Invoice upcoming = invoiceService.Upcoming(options); return upcoming; }// 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<Invoice> RetrieveUpcomingInvoice([FromBody] RetrieveUpcomingInvoiceRequest req) { var service = new SubscriptionService(); var subscription = service.Get(req.Subscription); var invoiceService = new InvoiceService(); var options = new UpcomingInvoiceOptions { Customer = req.Customer, Subscription = req.Subscription, SubscriptionItems = new List<InvoiceSubscriptionItemOptions> { new InvoiceSubscriptionItemOptions { Id = subscription.Items.Data[0].Id, Deleted = true, }, new InvoiceSubscriptionItemOptions { // TODO: This should be Price, but isn't in Stripe.net yet. Plan = Environment.GetEnvironmentVariable(req.NewPrice), Deleted = false, }, } }; Invoice upcoming = invoiceService.Upcoming(options); return upcoming; }
Optional Display the customer payment method Client and Server
Displaying the brand and last 4 digits of your customer’s card can help them know which card is being charged, or if they need to update their payment method.
On the frontend, send the payment method ID to a backend endpoint that retrieves the payment method details.
function retrieveCustomerPaymentMethod(paymentMethodId) { return fetch('/retrieve-customer-payment-method', { method: 'post', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ paymentMethodId: paymentMethodId, }), }) .then((response) => { return response.json(); }) .then((response) => { return response; }); }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' 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.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\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 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'); const stripe = 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); });// 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'); const stripe = 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); });
// 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 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) }// 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 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; } }using Newtonsoft.Json; public class RetrieveCustomerPaymentMethodRequest { [JsonProperty("paymentMethodId")] public string PaymentMethod { 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-customer-payment-method")] public ActionResult<PaymentMethod> RetrieveCustomerPaymentMethod([FromBody] RetrieveCustomerPaymentMethodRequest req) { var service = new PaymentMethodService(); var paymentMethod = service.Get(req.PaymentMethod); return 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 StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [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:
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" }{ "id": "pm_1GcbHY2eZvKYlo2CoqlVxo42", "object": "payment_method", "billing_details": { "address": { "city": null, "country": null, "line1": null, "line2": null, "postal_code": null, "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" }{ "id": "pm_1GcbHY2eZvKYlo2CoqlVxo42", "object": "payment_method", "billing_details": { "address": { "city": null, "country": null, "line1": null, "line2": null, "postal_code": null,