Implementing a Subscription Signup & Payment Flow

    An end-to-end guide on how to implement a subscription signup & payment flow in your application.

    Introduction to Billing primitives

    Use these core primitives to express your business model on Stripe Billing.

    • A Customer is your buyer. Customers have a unique ID, an email address, and payment methods.
    • A Product is what you are selling. Products represent goods or services.
    • A Plan is how much and how often you will charge for a Product. One Product can have many Plans, such as a monthly or annual plan for the same Product.
    • A Subscription represents a Customer’s access to a Product through a Plan. The status of Subscription indicates when to provision access to your service.
    • An Invoice is generated every time the Subscription is billed. Invoices have line items, tax rates and total how much your customer owes.
    • A PaymentIntent represents the state of all attempts to pay an Invoice.

    To successfully build a subscription service you will need to implement the following payment lifecycle scenarios:

    1. Implementing a subscription signup flow and charging your customer
    2. Handling the initial payment attempt outcome and activating your customer’s subscription
    3. Handling the outcome of recurring payment attempts

    If you create non-payment invoices and your business is impacted by SCA, you also need to build logic to manage non-payment invoices.

    Implementing a subscription signup flow

    When creating a new subscription, the initial sign up flow is your best opportunity to have customers interact with your prompts from their browser or app. Use this session to create a subscription for your customer’s chosen product and plan, then complete payment through a set of API requests to Stripe from your backend.

    Step 1: Collect customer & subscription information

    Collect your customer’s non-payment details on your frontend. Usually this means collecting information including their name, email address and potentially shipping information (if you deliver physical goods, for instance).

    After having decided on the best way to model your business, you will have one or more plans that establish the frequency and amount to charge your customer. Display your plans list to the customer through your application and ask them to choose the plan that meets their needs (for example, a SaaS product might offer a “Standard” and “Pro” plan).

    Step 2: Collect payment information

    Securely collect your customer’s payment information via Elements—Stripe’s web-based credit card collection form library—or Stripe’s iOS and Android SDKs. When building a web application, we recommend using a card element along with the createToken() Stripe.js function to generate a card token. The result of this tokenization call has the following structure:

    {
      "id": "tok_visa",
      "object": "token",
      ...
      "card": {
        "id": "card_1ELazKClCIKljWvsMn3LEAZD",
        "object": "card",
        ...
      }
    }
    

    When testing your integration you can use test tokens to simulate payment outcomes such as payment failures due to card errors.

    Step 3: Create the customer

    After collecting the required payment, customer, and subscription information on your application frontend, it’s time to pass this information to your application backend. Begin by creating a customer in Stripe:

    curl https://api.stripe.com/v1/customers \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d email="jenny.rosen@example.com" \
      -d source=tok_visa
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    Stripe::Customer.create({
      email: 'jenny.rosen@example.com',
      source: 'tok_visa',
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.Customer.create(
      email='jenny.rosen@example.com',
      source='tok_visa',
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    \Stripe\Customer::create([
      'email' => 'jenny.rosen@example.com',
      'source' => 'tok_visa',
    ]);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> customerParams = new HashMap<String, Object>();
    customerParams.put("email", "jenny.rosen@example.com");
    customerParams.put("source", "tok_visa");
    Customer.create(customerParams);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    stripe.customers.create({
      email: 'jenny.rosen@example.com',
      source: 'tok_visa',
    }, function(err, customer) {
      // asynchronously called
    });
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.CustomerParams{
      Email: stripe.String("jenny.rosen@example.com"),
    }
    params.SetSource("tok_visa")
    cus, err := customer.New(params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var options = new CustomerCreateOptions {
      Email = "jenny.rosen@example.com",
      Source = "tok_visa"
    };
    
    var service = new CustomerService();
    Customer customer = service.Create(options);
    

    Which will return:

    {
      "id": "cus_4fdAW5ftNQow1a",
      "object": "customer",
      "created": 1571609224,
      ...
      "email": "jenny.rosen@example.com",
      "default_source": "card_1ELazKClCIKljWvsMn3LEAZD",
      ...
    }
    

    Step 4: Create the subscription & attempt payment

    Once a customer has been created with an associated payment method in Stripe, attempt to create the subscription:

    curl https://api.stripe.com/v1/subscriptions \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d customer=cus_4fdAW5ftNQow1a \
      -d "items[0][plan]=plan_CBXbz9i7AIOTzr" \
      -d "expand[]=latest_invoice.payment_intent"
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    Stripe::Subscription.create({
      customer: 'cus_4fdAW5ftNQow1a',
      items: [
        {
          plan: 'plan_CBXbz9i7AIOTzr',
        },
      ],
      expand: ['latest_invoice.payment_intent']
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.Subscription.create(
      customer='cus_4fdAW5ftNQow1a',
      items=[
        {
          'plan': 'plan_CBXbz9i7AIOTzr',
        },
      ],
      expand: ['latest_invoice.payment_intent'],
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    \Stripe\Subscription::create([
      'customer' => 'cus_4fdAW5ftNQow1a',
      'items' => [
        [
          'plan' => 'plan_CBXbz9i7AIOTzr',
        ],
      ],
      'expand' => ['latest_invoice.payment_intent'],
    ]);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> item = new HashMap<String, Object>();
    item.put("plan", "plan_CBXbz9i7AIOTzr");
    
    Map<String, Object> items = new HashMap<String, Object>();
    items.put("0", item);
    
    List<String> expandList = new LinkedList<String>();
    expandList.add("latest_invoice.payment_intent");
    
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("customer", "cus_4fdAW5ftNQow1a");
    params.put("items", items);
    params.put("expand", expandList);
    
    Subscription.create(params);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    stripe.subscriptions.create({
      customer: 'cus_4fdAW5ftNQow1a',
      items: [
        {
          plan: 'plan_CBXbz9i7AIOTzr',
        },
      ],
      expand: ['latest_invoice.payment_intent'],
    }, function(err, subscription) {
        // asynchronously called
      }
    );
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.SubscriptionParams{
      Customer: stripe.String("cus_4fdAW5ftNQow1a"),
      Items: []*stripe.SubscriptionItemsParams{
        {
          Plan: stripe.String("plan_CBXbz9i7AIOTzr"),
        },
      },
    }
    params.AddExpand("latest_invoice.payment_intent")
    s, err := sub.New(params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var items = new List<SubscriptionItemOption> {
      new SubscriptionItemOption {
        Plan = "plan_CBXbz9i7AIOTzr"
      }
    };
    var options = new SubscriptionCreateOptions {
      Customer = "cus_4fdAW5ftNQow1a",
      Items = items
    };
    options.AddExpand("latest_invoice.payment_intent");
    
    var service = new SubscriptionService();
    Subscription subscription = service.Create(options);
    

    When creating the subscription, expand the latest_invoice.payment_intent field in order to determine payment outcome using the invoice’s associated PaymentIntent. This will return one of the following outcomes:

    Scenario Subscription Invoice PaymentIntent
    Payment succeeds active paid succeeded
    Trial starts trialing paid Not applicable, SetupIntents are used instead because of non-payment
    Payment fails incomplete open requires_payment_method
    Customer action required incomplete open requires_action

    Subscriptions use Invoices to represent amounts owed for a billing period. Stripe Billing uses the Payment Intents API to power payment attempts for all invoices — whether those invoices are tied to subscriptions, or represent one-off requests for payment.

    In each of the outcomes, the Subscription status lets us know whether we can provision the good or service associated with the subscription. The PaymentIntent status represents the state of the payment attempt for this subscription’s invoice.

    In the next section, we will explore how your application can interpret the Subscription and PaymentIntent statuses returned on subscription creation.

    Handling initial payment outcome

    In this section, we will explore how to handle the outcome of the payment attempted when your subscription is initially created. Use a combination of Subscription status, PaymentIntent status, and the latest Invoice status to determine the next course of action in the following cases:

    1. Payment success
    2. Trial start
    3. Payment failure
    4. Payment requires customer action

    Outcome 1: Payment succeeds

    Response Subscription PaymentIntent
    {
      "id": "sub_1ELI8bClCIKljWvsvK36TXlC",
      "object": "subscription",
      "status": "active",
      ...
      "latest_invoice": {
        "id": "in_EmGqfJMYy3Nt9M",
        "status": "paid",
        ...
        "payment_intent": {
          "status": "succeeded",
          ...
        }
      }
    }
    
    active succeeded

    In the case where payment succeeds, your subscription will have status=active and the associated PaymentIntent will have status=succeeded. The payment is complete, and you should promptly provision access to the good or service.

    Outcome 2: Trial starts

    Response Subscription PaymentIntent
    {
      "id": "sub_1ELI8bClCIKljWvsvK36TXlC",
      "object": "subscription",
      "status": "trialing",
      "pending_setup_intent": null,
      ...
      "latest_invoice": {
        "id": "in_EmGqfJMYy3Nt9M",
        "status": "paid",
        "payment_intent": null,
        ...
        }
    }
    
    trialing

    Not applicable, SetupIntents are used instead because of non-payment

    In the case where a trial begins (perhaps because you chose to pass trial_period_days in your subscription creation request), the subscription will have status=trialing. There will be no associated PaymentIntent since no payment is attempted at the beginning of a trial. As with the payment success, promptly provision your customer’s good or service when the subscription has this status.

    If the payment is impacted by SCA and collecting authentication is needed, a SetupIntent object is created automatically. The managing non-payment invoices has detailed instructions for managing these scenarios.

    Outcome 3: Payment fails

    Response Subscription PaymentIntent
    {
      "id": "sub_1ELI8bClCIKljWvsvK36TXlC",
      "object": "subscription",
      "status": "incomplete",
      ...
      "latest_invoice": {
        "id": "in_EmGqfJMYy3Nt9M",
        "status": "open",
        ...
        "payment_intent": {
          "status": "requires_payment_method",
          ...
        }
      }
    }
    
    incomplete requires_payment_method

    If payment fails due to a card_error such as a decline, reattempt payment using a different payment method. This section outlines the steps to take to reattempt payment with a different payment method.

    Step 1: Notify customer of failure and collect a new payment method

    On your application frontend, explain to the customer that the charge attempt for the subscription failed, and that a new payment method is required to proceed. Leverage the same logic that was used to collect the initial payment method during signup to generate a new card token. The tokenization response will look like:

    {
      "id": "tok_102xzJ2cugxaFXVf5Vtz9yL2",
      "object": "token",
      ...
      "card": {
        "id": "card_1ELbEdClCIKljWvsr89rvotl",
        "object": "card",
        ...
      }
    }
    

    Step 2: Attach new payment method to the customer

    Once you have collected a new token from your customer, pass this to your application backend and then update your customer’s default source:

    curl https://api.stripe.com/v1/customers/cus_4fdAW5ftNQow1a \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source=tok_mastercard
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    Stripe::Customer.update(
      'cus_4fdAW5ftNQow1a',
      {
        source: 'tok_mastercard',
      }
    )
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.Customer.modify(
      'cus_4fdAW5ftNQow1a',
      source='tok_mastercard',
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    \Stripe\Customer::update(
      'cus_4fdAW5ftNQow1a',
      [
        'source' => 'tok_mastercard',
      ]
    );
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Customer customer = Customer.retrieve("cus_4fdAW5ftNQow1a");
    Map<String, Object> params = new HashMap<>();
    params.put("source", "tok_mastercard");
    customer.update(params);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    stripe.customers.update(
      'cus_4fdAW5ftNQow1a',
      source: 'tok_mastercard',
        function(err, customer) {
        // asynchronously called
      }
    );
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.CustomerParams{}
    params.SetSource("tok_mastercard")
    c, _ := customer.Update("cus_4fdAW5ftNQow1a", params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var options = new CustomerUpdateOptions
    {
      Source = "tok_mastercard",
    };
    var customerService = new CustomerService();
    customerService.Update("cus_4fdAW5ftNQow1a", options);
    

    Which will return:

    {
      "id": "cus_4fdAW5ftNQow1a",
      "object": "customer",
      ...
      "default_source": "card_1ELbEdClCIKljWvsr89rvotl"
    }
    

    Step 3: Reattempt payment

    Reattempt payment of the latest invoice by using the invoice pay endpoint:

    curl https://api.stripe.com/v1/invoices/in_EmGqfJMYy3Nt9M/pay \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -x POST \
      -d "expand[]=payment_intent"
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    Stripe::Invoice.pay({
      invoice: 'in_EmGqfJMYy3Nt9M',
      expand: ['payment_intent']
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.Invoice.pay(
      invoice='in_EmGqfJMYy3Nt9M',
      expand: ['payment_intent'],
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    $invoice = \Stripe\Invoice::retrieve(['id' => 'in_EmGqfJMYy3Nt9M']);
    $invoice->pay(['expand' => ['payment_intent']]);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Invoice invoice = Invoice.retrieve("in_EmGqfJMYy3Nt9M");
    List<String> expandList = new LinkedList<String>();
    expandList.add("payment_intent");
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("expand", expandList);
    invoice.pay(params);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    stripe.invoices.pay({
      invoice: 'in_EmGqfJMYy3Nt9M',
      expand: ['payment_intent'],
    }, function(err, invoice) {
        // asynchronously called
      }
    );
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.InvoicePayParams{
      Invoice: stripe.String("in_EmGqfJMYy3Nt9M"),
    params.AddExpand("payment_intent")
    i, err := invoice.Pay("in_EmGqfJMYy3Nt9M", params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var options = new InvoicePayOptions{};
    var service = new InvoiceService();
    var invoice = service.Pay("in_EmGqfJMYy3Nt9M", options);
    options.AddExpand("payment_intent");
    

    If the call to pay the invoice fails with an HTTP 402 error, you need to retrieve the invoice manually and expand the PaymentIntent.

    After attempting payment or retrieving the invoice, use invoice.payment_intent.status to determine how to proceed:

    • status=succeeded - The attempt to pay with the new payment method succeeded. Let the customer know and provision access to your good or service.
    • status=requires_payment_method - The attempt to pay with the new payment method failed. Go back to step 1 and collect a new payment method.
    • status=requires_action - In order to complete payment, further action is required by the customer. See “Payment requires customer action” below.

    Outcome 4: Payment requires customer action

    Response Subscription PaymentIntent
    {
      "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",
            ...
          },
          ...
        }
      }
    }
    
    incomplete requires_action

    Some payment methods require additional steps, such as authentication, in order to complete the payment process. This requirement for customer authentication is expressed with PaymentIntent status=requires_action.

    Currently, the only way status=requires_action can be initiated is through an authentication mechanism called 3D Secure but we expect more payment methods to trigger this in the future. Some card charges can require 3D Secure, which can either be triggered by your own Radar rules or by the associated card’s issuing bank.

    Regulations in Europe often require 3D Secure. We recommend that you read our Strong Customer Authentication guide to determine whether handling this status is important for your business. If you have an existing billing integration and want to add support for this flow, also take a look at the Billing SCA Migration guide.

    If you choose to implement 3D Secure handling in your application, you will need to support the following flow:

    Step 1: Notify the customer that further action is required

    Have your application alert the user that until authentication is completed, the payment will not be processed and services will not be provisioned.

    Step 2: Complete the required action

    The following code demonstrates using Stripe.js to complete the flow for a web application:

    var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    // This can be found on invoice.payment_intent.client_secret
    var paymentIntentSecret = 'pi_91_secret_W9';
    
    stripe.handleCardPayment(paymentIntentSecret).then(function(result) {
      if (result.error) {
        // Display error.message in your UI.
      } else {
        // The payment has succeeded. Display a success message.
      }
    });
    const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    // This can be found on invoice.payment_intent.client_secret
    const paymentIntentSecret = 'pi_91_secret_W9';
    
    const {paymentIntent, error} = await stripe.handleCardPayment(clientSecret);
    
    if (error) {
      // Display error.message in your UI.
    } else {
      // The payment has succeeded. Display a success message.
    }

    iOS and Android code examples of this same flow exist as well.

    Calling the above helper functions provided by Stripe will:

    1. Display an authentication modal to your users.
    2. Attempt payment upon successful authentication.
    3. Close the modal and restore context to your application after payment is attempted or authentication fails.

    In order to test this flow, make use of the 3D Secure Required test card in your tokenization step.

    If you do not want to use pre-built Stripe libraries for completing 3D Secure, take a look at this guide for instructions on how to do this yourself.

    Recommended: Handling asynchronous payment success

    When the 3D Secure authentication step is completed, payment will be automatically attempted by Stripe’s systems. It is possible that after payment occurs, the user drops off of your application and the callback function in handleCardPayment() above does not get reached. If you rely solely on your application getting a return value from handleCardPayment(), you might end up in a situation where payment has occurred, but subscription access has not been provisioned.

    In order to handle this case, we suggest implementing the following provisioning process in your application:

    {
      "id": "in_1DgciUClCIKljWvsfCmPf1WG",
      "object": "invoice",
      "status": "paid"
      ...
      "billing_reason": "subscription_create",
      "subscription": "sub_E8uXk63MAbZbto"
    }
    
    • When billing_reason=subscription_create, make sure the subscription associated with this invoice has been provisioned for your customer. When creating subscriptions, be sure to store Stripe’s subscription.id as a foreign key in your internal database to make this lookup possible.

    Handling recurring charges

    In the case of recurring charges, Stripe Billing is able to do most of the heavy lifting on your behalf:

    Stripe automatically handles recurring failed charges

    In the case of recurring charges, Stripe Billing will automatically take care of recovering failed payments via email reminders to your customers.

    If you decide you would rather build your own failure handling, please refer to the next section of this guide.

    Building your own handling for recurring charge failures

    If a payment fails, or requires further action from the customer, the subscription will enter status=past_due with a PaymentIntent in either the requires_payment_method or requires_action status.

    {
      "id": "sub_E8uXk63MAbZbto",
      "object": "subscription",
      ...
      "status": "past_due",
      "latest_invoice": "in_1EMLu1ClCIKljWvsfTjRFAxa"
    }
    
    • When this happens, you will need to get your user back into your application to collect a different payment method and complete payment. This can be achieved via an email or mobile push notification. Stripe Billing provides built-in reminder emails to handle this case, which you can configure on your billing settings page.
    • Once the customer is back in your application, re-use either your payment failure flow or customer action flow depending on the status of the associated PaymentIntent.

    After the payment succeeds, the subscription will have status=active and the invoice will have status=paid.

    Managing non-payment invoices

    Creating subscriptions with free trials, using metered billing, and invoices discounted through coupons or customer balances often result in non-payment invoices. This means the customer isn’t immediately charged when the subscription is created.

    Even though customers aren’t charged for the first invoice, authenticating and authorizing their card is often beneficial. This can increase the chance that the first non-zero payment completes successfully. Payments made this way are known as off-session payments. To manage these scenarios, Stripe created SetupIntents.

    Using SetupIntents

    SetupIntents can be used to:

    • Collect payment information
    • Authenticate the customer’s card to claim exemptions later
    • Authorize the customer’s card without charging it

    Authenticating payments allows the customer to grant permissions to charge their card. This is required for Strong Customer Authentication, and is often performed through 3DS. Collecting payment method information and authorizing it ensures the payment method can be successfully charged.

    In off-session scenarios, using SetupIntents enables you to charge customers for their first non-zero payment without having to bring them back to your website or app for authentication. This reduces the friction on your customers.

    SetupIntents are automatically created for subscriptions that don’t require an initial payment. If authentication and authorization are required, they’re executed as well. If both succeed, or if they aren’t needed, no action is required, and the subscription.pending_setup_intent field is null. If either step fails, Stripe recommends using the SetupIntent on your frontend to resolve the issue while the customer is on-session. The next two sections explain in detail how to manage scenarios where authentication or authorization fail.

    Managing authentication failures

    Authentication failures occur when Stripe is unable to authenticate the customer with their card issuer. When this happens, the status of the SetupIntent is set to requires_action.

    To resolve these scenarios, your frontend should call handleCardSetup so that the customer can complete the authentication flow manually. Note that the code example below expands the pending_setup_intent to complete the flow.

    const {pending_setup_intent} = subscription;
    
    if (pending_setup_intent) {
      const {client_secret, status} = subscription.pending_setup_intent;
    
      if (status === "requires_action") {
        stripe.handleCardSetup(client_secret).then(function(result) {
          if (result.error) {
            // Display error.message in your UI.
          } else {
            // The setup has succeeded. Display a success message.
          }
        });
      }
    }
    const {pending_setup_intent} = subscription;
    
    if (pending_setup_intent) {
      const {client_secret, status} = subscription.pending_setup_intent;
    
      if (status === "requires_action") {
        const {setupIntent, error} = await stripe.handleCardSetup(client_secret);
    
        if (error) {
          // Display error.message in your UI.
        } else {
          // The setup has succeeded. Display a success message.
        }
      }
    }

    After completing this flow, authorization is executed if it’s required. If authorization succeeds, or if it’s not required, pending_setup_intent is updated to null upon completion.

    Managing authorization failures

    Payment authorization failures occur when Stripe can’t verify that a card can be charged. When this happens, the status of the SetupIntent is set to requires_payment_method. This generally means that subsequent charges with that card will fail.

    To resolve these scenarios, follow the steps from Outcome 3: Payment fails to collect a new payment method and then update the customer or subscription’s default payment method. Note that the code example below expands the pending_setup_intent to complete the flow.

    const {pending_setup_intent, latest_invoice} = subscription;
    
    if (pending_setup_intent) {
      const {client_secret, status} = subscription.pending_setup_intent;
    
      if (status === "requires_action") {
        stripe.handleCardSetup(client_secret).then(function(result) {
          if (result.error) {
            // Display error.message in your UI.
          } else {
            // The setup has succeeded. Display a success message.
          }
        });
      } else if (status === "requires_payment_method") {
        // Collect new payment method
      }
    }
    const {pending_setup_intent, latest_invoice} = subscription;
    
    if (pending_setup_intent) {
      const {client_secret, status} = subscription.pending_setup_intent;
    
      if (status === "requires_action") {
        const {setupIntent, error} = await stripe.handleCardSetup(client_secret);
    
        if (error) {
          // Display error.message in your UI.
        } else {
          // The setup has succeeded. Display a success message.
        }
      } else if (status === "requires_payment_method") {
        // Collect new payment method
      }
    }

    Was this page helpful?

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

    On this page