puffin-app/docs/stripe-api-setup.md
Matt 06733cb2cb
All checks were successful
Build and Push Docker Image / docker (push) Successful in 42s
Integrate Stripe Checkout and add comprehensive UI enhancements
## Stripe Payment Integration
- Add Express.js backend server with Stripe Checkout Sessions
- Create SQLite database for order tracking
- Implement Stripe webhook handlers for payment events
- Integrate with Wren Climate API for carbon offset fulfillment
- Add CheckoutSuccess and CheckoutCancel pages
- Create checkout API client for frontend
- Update OffsetOrder component to redirect to Stripe Checkout
- Add processing fee calculation (3% of base amount)
- Implement order status tracking (pending → paid → fulfilled)

Backend (server/):
- Express server with CORS and middleware
- SQLite database with Order schema
- Stripe configuration and client
- Order CRUD operations model
- Checkout session creation endpoint
- Webhook handler for payment confirmation
- Wren API client for offset fulfillment

Frontend:
- CheckoutSuccess page with order details display
- CheckoutCancel page with retry encouragement
- Updated OffsetOrder to use Stripe checkout flow
- Added checkout routes to App.tsx
- TypeScript interfaces for checkout flow

## Visual & UX Enhancements
- Add CertificationBadge component for project verification status
- Create PortfolioDonutChart for visual portfolio allocation
- Implement RadialProgress for percentage displays
- Add reusable form components (FormInput, FormTextarea, FormSelect, FormFieldWrapper)
- Refactor OffsetOrder with improved layout and animations
- Add offset percentage slider with visual feedback
- Enhance MobileOffsetOrder with better responsive design
- Improve TripCalculator with cleaner UI structure
- Update CurrencySelect with better styling
- Add portfolio distribution visualization
- Enhance project cards with hover effects and animations
- Improve color palette and gradient usage throughout

## Configuration
- Add VITE_API_BASE_URL environment variable
- Create backend .env.example template
- Update frontend .env.example with API URL
- Add Stripe documentation references

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 21:45:14 +01:00

573 KiB
Raw Blame History

Accept a payment

Securely accept payments online.

Build a payment form or use a prebuilt checkout page to start accepting online payments.

Stripe-hosted page

This is a Stripe-hosted page for when platform is web and ui is stripe-hosted. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=stripe-hosted.

Redirect to a Stripe-hosted payment page using Stripe Checkout. See how this integration compares to Stripes other integration types.

Integration effort

Complexity: 2/5

Integration type

Redirect to Stripe-hosted payment page

UI customization

Limited customization

  • 20 preset fonts
  • 3 preset border radius
  • Custom background and border color
  • Custom logo

Try it out

Use our official libraries to access the Stripe API from your application:

Ruby

# Available as a gem
sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Python

# Install through pip
pip3 install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin:
# https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md
# Specify that version in your requirements.txt file
stripe>=5.0.0

PHP

# Install the PHP library with Composer
composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:30.0.0"
<!--
  For Maven, add the following dependency to your POM and replace 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>30.0.0</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

Node.js

# Install with npm
npm install stripe --save

Go

# Make sure your project is using Go Modules
go mod init
# Install stripe-go
go get -u github.com/stripe/stripe-go/v83
// Then import the package
import (
  "github.com/stripe/stripe-go/v83"
)

.NET

# Install with dotnet
dotnet add package Stripe.net
dotnet restore
# Or install with NuGet
Install-Package Stripe.net

Redirect your customer to Stripe Checkout [Client-side] [Server-side]

Add a checkout button to your website that calls a server-side endpoint to create a Checkout Session.

You can also create a Checkout Session for an existing customer, allowing you to prefill Checkout fields with known contact information and unify your purchase history for that customer.

<html>
  <head>
    <title>Buy cool new product</title>
  </head>
  <body>
    <!-- Use action="/create-checkout-session.php" if your server is PHP based. -->
    <form action="/create-checkout-session" method="POST">
      <button type="submit">Checkout</button>
    </form>
  </body>
</html>

A Checkout Session is the programmatic representation of what your customer sees when theyre redirected to the payment form. You can configure it with options such as:

You must populate success_url with the URL value of a page on your website that Checkout returns your customer to after they complete the payment. You can optionally also provide a cancel_url value of a page on your website that Checkout returns your customer to if they terminate the payment process before completion.

Checkout Sessions expire 24 hours after creation by default.

After creating a Checkout Session, redirect your customer to the URL returned in the response.

Ruby

# This example sets up an endpoint using the Sinatra framework.


require 'json'
require 'sinatra'
require 'stripe'

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/create-checkout-session' dosession = Stripe::Checkout::Session.create({
    line_items: [{
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    }],
    mode: 'payment',
    # These placeholder URLs will be replaced in a following step.
    success_url: 'https://example.com/success',
    cancel_url: 'https://example.com/cancel',
  })

  redirect session.url, 303
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.

import os
import stripe

from flask import Flask, redirect

app = Flask(__name__)

stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():session = stripe.checkout.Session.create(
    line_items=[{
      'price_data': {
        'currency': 'usd',
        'product_data': {
          'name': 'T-shirt',
        },
        'unit_amount': 2000,
      },
      'quantity': 1,
    }],
    mode='payment',
    success_url='http://localhost:4242/success',
    cancel_url='http://localhost:4242/cancel',
  )

  return redirect(session.url, code=303)

if __name__== '__main__':
    app.run(port=4242)

PHP

<?php

require 'vendor/autoload.php';

$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');
$checkout_session = $stripe->checkout->sessions->create([
  'line_items' => [[
    'price_data' => [
      'currency' => 'usd',
      'product_data' => [
        'name' => 'T-shirt',
      ],
      'unit_amount' => 2000,
    ],
    'quantity' => 1,
  ]],
  'mode' => 'payment',
  'success_url' => 'http://localhost:4242/success',
  'cancel_url' => 'http://localhost:4242/cancel',
]);

header("HTTP/1.1 303 See Other");
header("Location: " . $checkout_session->url);
?>

Java

import java.util.HashMap;
import java.util.Map;
import static spark.Spark.get;
import static spark.Spark.post;
import static spark.Spark.port;
import static spark.Spark.staticFiles;

import com.stripe.Stripe;
import com.stripe.model.checkout.Session;
import com.stripe.param.checkout.SessionCreateParams;

public class Server {

  public static void main(String[] args) {
    port(4242);
    Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

    post("/create-checkout-session", (request, response) -> {SessionCreateParams params =
        SessionCreateParams.builder()
          .setMode(SessionCreateParams.Mode.PAYMENT)
          .setSuccessUrl("http://localhost:4242/success")
          .setCancelUrl("http://localhost:4242/cancel")
          .addLineItem(
          SessionCreateParams.LineItem.builder()
            .setQuantity(1L)
            .setPriceData(
              SessionCreateParams.LineItem.PriceData.builder()
                .setCurrency("usd")
                .setUnitAmount(2000L)
                .setProductData(
                  SessionCreateParams.LineItem.PriceData.ProductData.builder()
                    .setName("T-shirt")
                    .build())
                .build())
            .build())
          .build();

      Session session = Session.create(params);

      response.redirect(session.getUrl(), 303);
      return "";
    });
  }
}

Node.js

// This example sets up an endpoint using the Express framework.

const express = require('express');
const app = express();
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>')

app.post('/create-checkout-session', async (req, res) => {const session = await stripe.checkout.sessions.create({
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {
            name: 'T-shirt',
          },
          unit_amount: 2000,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: 'http://localhost:4242/success',
    cancel_url: 'http://localhost:4242/cancel',
  });

  res.redirect(303, session.url);
});

app.listen(4242, () => console.log(`Listening on port ${4242}!`));

Go

package main

import (
  "net/http"

  "github.com/labstack/echo"
  "github.com/labstack/echo/middleware"
  "github.com/stripe/stripe-go/v76.0.0"
  "github.com/stripe/stripe-go/v76.0.0/checkout/session"
)

// This example sets up an endpoint using the Echo framework.
// Watch this video to get started: https://youtu.be/ePmEVBu8w6Y.

func main() {
  stripe.Key = "<<YOUR_SECRET_KEY>>"

  e := echo.New()
  e.Use(middleware.Logger())
  e.Use(middleware.Recover())

  e.POST("/create-checkout-session", createCheckoutSession)

  e.Logger.Fatal(e.Start("localhost:4242"))
}

func createCheckoutSession(c echo.Context) (err error) {params := &stripe.CheckoutSessionParams{
    Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
    LineItems: []*stripe.CheckoutSessionLineItemParams{
      &stripe.CheckoutSessionLineItemParams{
        PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
          Currency: stripe.String("usd"),
          ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
            Name: stripe.String("T-shirt"),
          },
          UnitAmount: stripe.Int64(2000),
        },
        Quantity: stripe.Int64(1),
      },
    },
    SuccessURL: stripe.String("http://localhost:4242/success"),
    CancelURL:  stripe.String("http://localhost:4242/cancel"),
  }

  s, _ := session.New(params)

  if err != nil {
    return err
  }

  return c.Redirect(http.StatusSeeOther, s.URL)
}

.NET

// This example sets up an endpoint using the ASP.NET MVC framework.
// Watch this video to get started: https://youtu.be/2-mMOB8MhmE.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Stripe;
using Stripe.Checkout;

namespace server.Controllers
{
  public class PaymentsController : Controller
  {
    public PaymentsController()
    {
      StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";
    }

    [HttpPost("create-checkout-session")]
    public ActionResult CreateCheckoutSession()
    {var options = new SessionCreateOptions
      {
        LineItems = new List<SessionLineItemOptions>
        {
          new SessionLineItemOptions
          {
            PriceData = new SessionLineItemPriceDataOptions
            {
              UnitAmount = 2000,
              Currency = "usd",
              ProductData = new SessionLineItemPriceDataProductDataOptions
              {
                Name = "T-shirt",
              },
            },
            Quantity = 1,
          },
        },
        Mode = "payment",
        SuccessUrl = "http://localhost:4242/success",
        CancelUrl = "http://localhost:4242/cancel",
      };

      var service = new SessionService();
      Session session = service.Create(options);

      Response.Headers.Add("Location", session.Url);
      return new StatusCodeResult(303);
    }
  }
}

Payment methods

By default, Stripe enables cards and other common payment methods. You can turn individual payment methods on or off in the Stripe Dashboard. In Checkout, Stripe evaluates the currency and any restrictions, then dynamically presents the supported payment methods to the customer.

To see how your payment methods appear to customers, enter a transaction ID or set an order amount and currency in the Dashboard.

You can enable Apple Pay and Google Pay in your payment methods settings. By default, Apple Pay is enabled and Google Pay is disabled. However, in some cases Stripe filters them out even when theyre enabled. We filter Google Pay if you enable automatic tax without collecting a shipping address.

Checkouts Stripe-hosted pages dont need integration changes to enable Apple Pay or Google Pay. Stripe handles these payments the same way as other card payments.

Confirm your endpoint

Confirm your endpoint is accessible by starting your web server (for example, localhost:4242) and running the following command:

curl -X POST -is "http://localhost:4242/create-checkout-session" -d ""

You should see a response in your terminal that looks like this:

HTTP/1.1 303 See Other
Location: https://checkout.stripe.com/c/pay/cs_test_...
...

Testing

You should now have a working checkout button that redirects your customer to Stripe Checkout.

  1. Click the checkout button.
  2. Youre redirected to the Stripe Checkout payment form.

If your integration isnt working:

  1. Open the Network tab in your browsers developer tools.
  2. Click the checkout button and confirm it sent an XHR request to your server-side endpoint (POST /create-checkout-session).
  3. Verify the request is returning a 200 status.
  4. Use console.log(session) inside your button click listener to confirm the correct data returned.

Show a success page [Client-side] [Server-side]

Its important for your customer to see a success page after they successfully submit the payment form. Host this success page on your site.

Create a minimal success page:

<html>
  <head><title>Thanks for your order!</title></head>
  <body>
    <h1>Thanks for your order!</h1>
    <p>
      We appreciate your business!
      If you have any questions, please email
      <a href="mailto:orders@example.com">orders@example.com</a>.
    </p>
  </body>
</html>

Next, update the Checkout Session creation endpoint to use this new page:

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  --data-urlencode success_url="http://localhost:4242/success.html" \
  --data-urlencode cancel_url="http://localhost:4242/cancel.html"
stripe checkout sessions create  \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --success-url="http://localhost:4242/success.html" \
  --cancel-url="http://localhost:4242/cancel.html"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'http://localhost:4242/success.html',
  cancel_url: 'http://localhost:4242/cancel.html',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "success_url": "http://localhost:4242/success.html",
  "cancel_url": "http://localhost:4242/cancel.html",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'success_url' => 'http://localhost:4242/success.html',
  'cancel_url' => 'http://localhost:4242/cancel.html',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setSuccessUrl("http://localhost:4242/success.html")
    .setCancelUrl("http://localhost:4242/cancel.html")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'http://localhost:4242/success.html',
  cancel_url: 'http://localhost:4242/cancel.html',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  SuccessURL: stripe.String("http://localhost:4242/success.html"),
  CancelURL: stripe.String("http://localhost:4242/cancel.html"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    SuccessUrl = "http://localhost:4242/success.html",
    CancelUrl = "http://localhost:4242/cancel.html",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

If you want to customize your success page, read the custom success page guide.

Testing

  1. Click your checkout button.
  2. Fill out the payment details with the test card information:
    • Enter 4242 4242 4242 4242 as the card number.
    • Enter any future date for card expiry.
    • Enter any 3-digit number for CVC.
    • Enter any billing postal code.
  3. Click Pay.
  4. Youre redirected to your new success page.

Next, find the new payment in the Stripe Dashboard. Successful payments appear in the Dashboards list of payments. When you click a payment, it takes you to the payment details page. The Checkout summary section contains billing information and the list of items purchased, which you can use to manually fulfill the order.

Handle post-payment events

Stripe sends a checkout.session.completed event when a customer completes a Checkout Session payment. Use the Dashboard webhook tool or follow the webhook guide to receive and handle these events, which might trigger you to:

  • Send an order confirmation email to your customer.
  • Log the sale in a database.
  • Start a shipping workflow.

Listen for these events rather than waiting for your customer to be redirected back to your website. Triggering fulfillment only from your Checkout landing page is unreliable. Setting up your integration to listen for asynchronous events allows you to accept different types of payment methods with a single integration.

Learn more in our fulfillment guide for Checkout.

Handle the following events when collecting payments with the Checkout:

Event Description Action
checkout.session.completed Sent when a customer successfully completes a Checkout Session. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
checkout.session.async_payment_succeeded Sent when a payment made with a delayed payment method, such as ACH direct debt, succeeds. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
checkout.session.async_payment_failed Sent when a payment made with a delayed payment method, such as ACH direct debt, fails. Notify the customer of the failure and bring them back on-session to attempt payment again.

Test your integration

To test your Stripe-hosted payment form integration:

  1. Create a Checkout Session.
  2. Fill out the payment details with a method from the following table.
    • Enter any future date for card expiry.
    • Enter any 3-digit number for CVC.
    • Enter any billing postal code.
  3. Click Pay. Youre redirected to your success_url.
  4. Go to the Dashboard and look for the payment on the Transactions page. If your payment succeeded, youll see it in that list.
  5. Click your payment to see more details, like a Checkout summary with billing information and the list of purchased items. You can use this information to fulfill the order.

Learn more about testing your integration.

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Wallets

Payment method Scenario How to test
Alipay Your customer successfully pays with a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.

Bank redirects

Payment method Scenario How to test
BECS Direct Debit Your customer successfully pays with BECS Direct Debit. Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status 3 minutes later.
BECS Direct Debit Your customers payment fails with an account_closed error code. Fill out the form using the account number 111111113 and BSB 000000.
Bancontact, EPS, iDEAL, and Przelewy24 Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

Vouchers

Payment method Scenario How to test
Boleto, OXXO Your customer pays with a Boleto or OXXO voucher. Select Boleto or OXXO as the payment method and submit the payment. Close the dialog after it appears.

See Testing for additional information to test your integration.

Test cards

Number Description
4242 4242 4242 4242 Succeeds and immediately processes the payment.
4000 0000 0000 3220 Requires 3D Secure 2 authentication for a successful payment.
4000 0000 0000 9995 Always fails with a decline code of insufficient_funds.

Optional: Create products and prices

You can set up your Checkout Session to accept tips and donations, or sell pay-what-you-want products and services.

Before you create a Checkout Session, you can create Products (Products represent what your business sells—whether that's a good or a service) and Prices (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) upfront. Use products to represent different physical goods or levels of service, and Prices (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) to represent each products pricing.

For example, you can create a T-shirt as a product with a price of 20 USD. This allows you to update and add prices without needing to change the details of your underlying products. You can either create products and prices with the Stripe Dashboard or API. Learn more about how products and prices work.

API

The API only requires a name to create a Product. Checkout displays the product name, description, and images that you supply.

curl https://api.stripe.com/v1/products \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d name=T-shirt
stripe products create  \
  --name=T-shirt
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

product = client.v1.products.create({name: 'T-shirt'})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
product = client.v1.products.create({"name": "T-shirt"})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$product = $stripe->products->create(['name' => 'T-shirt']);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

ProductCreateParams params = ProductCreateParams.builder().setName("T-shirt").build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Product product = client.v1().products().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const product = await stripe.products.create({
  name: 'T-shirt',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.ProductCreateParams{Name: stripe.String("T-shirt")}
result, err := sc.V1Products.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new ProductCreateOptions { Name = "T-shirt" };
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Products;
Product product = service.Create(options);

Next, create a Price to define how much to charge for your product. This includes how much the product costs and what currency to use.

curl https://api.stripe.com/v1/prices \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d product="{{PRODUCT_ID}}" \
  -d unit_amount=2000 \
  -d currency=usd
stripe prices create  \
  --product="{{PRODUCT_ID}}" \
  --unit-amount=2000 \
  --currency=usd
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

price = client.v1.prices.create({
  product: '{{PRODUCT_ID}}',
  unit_amount: 2000,
  currency: 'usd',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
price = client.v1.prices.create({
  "product": "{{PRODUCT_ID}}",
  "unit_amount": 2000,
  "currency": "usd",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$price = $stripe->prices->create([
  'product' => '{{PRODUCT_ID}}',
  'unit_amount' => 2000,
  'currency' => 'usd',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

PriceCreateParams params =
  PriceCreateParams.builder()
    .setProduct("{{PRODUCT_ID}}")
    .setUnitAmount(2000L)
    .setCurrency("usd")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Price price = client.v1().prices().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const price = await stripe.prices.create({
  product: '{{PRODUCT_ID}}',
  unit_amount: 2000,
  currency: 'usd',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.PriceCreateParams{
  Product: stripe.String("{{PRODUCT_ID}}"),
  UnitAmount: stripe.Int64(2000),
  Currency: stripe.String(stripe.CurrencyUSD),
}
result, err := sc.V1Prices.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new PriceCreateOptions
{
    Product = "{{PRODUCT_ID}}",
    UnitAmount = 2000,
    Currency = "usd",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Prices;
Price price = service.Create(options);

Dashboard

Copy products created in a sandbox to live mode so that you dont need to re-create them. In the Product detail view in the Dashboard, click Copy to live mode in the upper right corner. You can only do this once for each product created in a sandbox. Subsequent updates to the test product arent reflected for the live product.

Make sure youre in a sandbox by clicking Sandboxes within the Dashboard account picker. Next, define the items you want to sell. To create a new product and price:

  • Navigate to the Products section in the Dashboard.
  • Click Add product.
  • Select One time when setting the price.

Checkout displays the product name, description, and images that you supply.

Each price you create has an ID. When you create a Checkout Session, reference the price ID and quantity. If youre selling in multiple currencies, make your Price multi-currency (A single Price object can support multiple currencies. Each purchase uses one of the supported currencies for the Price, depending on how you use the Price in your integration). Checkout automatically determines the customers local currency and presents that currency if the Price supports it.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --data-urlencode success_url="https://example.com/success?session_id={CHECKOUT_SESSION_ID}" \
  --data-urlencode cancel_url="https://example.com/cancel"
stripe checkout sessions create  \
  --mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --success-url="https://example.com/success?session_id={CHECKOUT_SESSION_ID}" \
  --cancel-url="https://example.com/cancel"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://example.com/cancel',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "mode": "payment",
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "success_url": "https://example.com/success?session_id={CHECKOUT_SESSION_ID}",
  "cancel_url": "https://example.com/cancel",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'mode' => 'payment',
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'success_url' => 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
  'cancel_url' => 'https://example.com/cancel',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setSuccessUrl("https://example.com/success?session_id={CHECKOUT_SESSION_ID}")
    .setCancelUrl("https://example.com/cancel")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://example.com/cancel',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  SuccessURL: stripe.String("https://example.com/success?session_id={CHECKOUT_SESSION_ID}"),
  CancelURL: stripe.String("https://example.com/cancel"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    Mode = "payment",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    SuccessUrl = "https://example.com/success?session_id={CHECKOUT_SESSION_ID}",
    CancelUrl = "https://example.com/cancel",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Optional: Prefill customer data [Server-side]

If youve already collected your customers email and want to prefill it in the Checkout Session for them, pass customer_email when creating a Checkout Session.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  --data-urlencode customer_email="customer@example.com" \
  -d "line_items[0][price]"="{{PRICE_ID}}" \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  --data-urlencode success_url="https://example.com/success" \
  --data-urlencode cancel_url="https://example.com/cancel"
stripe checkout sessions create  \
  --customer-email="customer@example.com" \
  -d "line_items[0][price]"="{{PRICE_ID}}" \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --success-url="https://example.com/success" \
  --cancel-url="https://example.com/cancel"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_email: 'customer@example.com',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success',
  cancel_url: 'https://example.com/cancel',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_email": "customer@example.com",
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "mode": "payment",
  "success_url": "https://example.com/success",
  "cancel_url": "https://example.com/cancel",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_email' => 'customer@example.com',
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'success_url' => 'https://example.com/success',
  'cancel_url' => 'https://example.com/cancel',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerEmail("customer@example.com")
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setSuccessUrl("https://example.com/success")
    .setCancelUrl("https://example.com/cancel")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_email: 'customer@example.com',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success',
  cancel_url: 'https://example.com/cancel',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerEmail: stripe.String("customer@example.com"),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  SuccessURL: stripe.String("https://example.com/success"),
  CancelURL: stripe.String("https://example.com/cancel"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerEmail = "customer@example.com",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    Mode = "payment",
    SuccessUrl = "https://example.com/success",
    CancelUrl = "https://example.com/cancel",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Optional: Save payment method details [Server-side]

By default, payment methods used to make a one-time payment with Checkout arent available for future use.

Save payment methods to charge them off-session

You can set Checkout to save payment methods used to make a one-time payment by passing the payment_intent_data.setup_future_usage argument. This is useful if you need to capture a payment method on-file to use for future fees, such as cancellation or no-show fees.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d customer_creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  --data-urlencode success_url="https://example.com/success.html" \
  --data-urlencode cancel_url="https://example.com/cancel.html" \
  -d "payment_intent_data[setup_future_usage]"=off_session
stripe checkout sessions create  \
  --customer-creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --success-url="https://example.com/success.html" \
  --cancel-url="https://example.com/cancel.html" \
  -d "payment_intent_data[setup_future_usage]"=off_session
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
  payment_intent_data: {setup_future_usage: 'off_session'},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_creation": "always",
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "success_url": "https://example.com/success.html",
  "cancel_url": "https://example.com/cancel.html",
  "payment_intent_data": {"setup_future_usage": "off_session"},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_creation' => 'always',
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'success_url' => 'https://example.com/success.html',
  'cancel_url' => 'https://example.com/cancel.html',
  'payment_intent_data' => ['setup_future_usage' => 'off_session'],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerCreation(SessionCreateParams.CustomerCreation.ALWAYS)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setSuccessUrl("https://example.com/success.html")
    .setCancelUrl("https://example.com/cancel.html")
    .setPaymentIntentData(
      SessionCreateParams.PaymentIntentData.builder()
        .setSetupFutureUsage(
          SessionCreateParams.PaymentIntentData.SetupFutureUsage.OFF_SESSION
        )
        .build()
    )
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
  payment_intent_data: {
    setup_future_usage: 'off_session',
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerCreation: stripe.String(stripe.CheckoutSessionCustomerCreationAlways),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  SuccessURL: stripe.String("https://example.com/success.html"),
  CancelURL: stripe.String("https://example.com/cancel.html"),
  PaymentIntentData: &stripe.CheckoutSessionCreatePaymentIntentDataParams{
    SetupFutureUsage: stripe.String("off_session"),
  },
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerCreation = "always",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    SuccessUrl = "https://example.com/success.html",
    CancelUrl = "https://example.com/cancel.html",
    PaymentIntentData = new Stripe.Checkout.SessionPaymentIntentDataOptions
    {
        SetupFutureUsage = "off_session",
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

If you use Checkout in subscription mode, Stripe automatically saves the payment method to charge it for subsequent payments. Card payment methods saved to customers using either setup_future_usage or subscription mode dont appear for return purchases in Checkout (more on this below). We recommend using custom text to link out to any relevant terms regarding the usage of saved payment information.

Global privacy laws are complicated and nuanced. We recommend contacting your legal and privacy team prior to implementing setup_future_usage because it might implicate your existing privacy compliance framework. Refer to the guidance issued by the European Protection Board to learn more about saving payment details.

Save payment methods to prefill them in Checkout

By default, Checkout uses Link to provide your customers with the option to securely save and reuse their payment information. If you prefer to manage payment methods yourself, use saved_payment_method_options.payment_method_save when creating a Checkout Session to let your customers save their payment methods for future purchases in Checkout.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d customer_creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  --data-urlencode success_url="https://example.com/success.html" \
  --data-urlencode cancel_url="https://example.com/cancel.html" \
  -d "saved_payment_method_options[payment_method_save]"=enabled
stripe checkout sessions create  \
  --customer-creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --success-url="https://example.com/success.html" \
  --cancel-url="https://example.com/cancel.html" \
  -d "saved_payment_method_options[payment_method_save]"=enabled
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
  saved_payment_method_options: {payment_method_save: 'enabled'},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_creation": "always",
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "success_url": "https://example.com/success.html",
  "cancel_url": "https://example.com/cancel.html",
  "saved_payment_method_options": {"payment_method_save": "enabled"},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_creation' => 'always',
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'success_url' => 'https://example.com/success.html',
  'cancel_url' => 'https://example.com/cancel.html',
  'saved_payment_method_options' => ['payment_method_save' => 'enabled'],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerCreation(SessionCreateParams.CustomerCreation.ALWAYS)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setSuccessUrl("https://example.com/success.html")
    .setCancelUrl("https://example.com/cancel.html")
    .setSavedPaymentMethodOptions(
      SessionCreateParams.SavedPaymentMethodOptions.builder()
        .setPaymentMethodSave(
          SessionCreateParams.SavedPaymentMethodOptions.PaymentMethodSave.ENABLED
        )
        .build()
    )
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
  saved_payment_method_options: {
    payment_method_save: 'enabled',
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerCreation: stripe.String(stripe.CheckoutSessionCustomerCreationAlways),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  SuccessURL: stripe.String("https://example.com/success.html"),
  CancelURL: stripe.String("https://example.com/cancel.html"),
  SavedPaymentMethodOptions: &stripe.CheckoutSessionCreateSavedPaymentMethodOptionsParams{
    PaymentMethodSave: stripe.String(stripe.CheckoutSessionSavedPaymentMethodOptionsPaymentMethodSaveEnabled),
  },
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerCreation = "always",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    SuccessUrl = "https://example.com/success.html",
    CancelUrl = "https://example.com/cancel.html",
    SavedPaymentMethodOptions = new Stripe.Checkout.SessionSavedPaymentMethodOptionsOptions
    {
        PaymentMethodSave = "enabled",
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Passing this parameter in either payment or subscription mode displays an optional checkbox to let customers explicitly save their payment method for future purchases. When customers check this checkbox, Checkout saves the payment method with allow_redisplay: always. Checkout uses this parameter to determine whether a payment method can be prefilled on future purchases. When using saved_payment_method_options.payment_method_save, you dont need to pass in setup_future_usage to save the payment method.

Using saved_payment_method_options.payment_method_save requires a Customer. To save a new customer, set the Checkout Sessions customer_creation to always. Otherwise, the session doesnt save the customer or the payment method.

If payment_method_save isnt passed in or if the customer doesnt agree to save the payment method, Checkout still saves payment methods created in subscription mode or using setup_future_usage. These payment methods have an allow_redisplay value of limited, which prevents them from being prefilled for returning purchases and allows you to comply with card network rules and data protection regulations. Learn how to change the default behavior enabled by these modes and how to change or override allow_redisplay behavior.

You can use Checkout to save cards and other payment methods to charge them off-session, but Checkout only prefills saved cards. Learn how to prefill saved cards. To save a payment method without an initial payment, use Checkout in setup mode.

Let customers remove saved payment methods

To let your customers remove a saved payment method so it doesnt resurface for future payments, use saved_payment_method_options.payment_method_remove when creating a Checkout Session.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d customer={{CUSTOMER_ID}} \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  --data-urlencode success_url="https://example.com/success.html" \
  --data-urlencode cancel_url="https://example.com/cancel.html" \
  -d "saved_payment_method_options[payment_method_remove]"=enabled
stripe checkout sessions create  \
  --customer={{CUSTOMER_ID}} \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --success-url="https://example.com/success.html" \
  --cancel-url="https://example.com/cancel.html" \
  -d "saved_payment_method_options[payment_method_remove]"=enabled
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer: '{{CUSTOMER_ID}}',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
  saved_payment_method_options: {payment_method_remove: 'enabled'},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer": "{{CUSTOMER_ID}}",
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "success_url": "https://example.com/success.html",
  "cancel_url": "https://example.com/cancel.html",
  "saved_payment_method_options": {"payment_method_remove": "enabled"},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer' => '{{CUSTOMER_ID}}',
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'success_url' => 'https://example.com/success.html',
  'cancel_url' => 'https://example.com/cancel.html',
  'saved_payment_method_options' => ['payment_method_remove' => 'enabled'],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomer("{{CUSTOMER_ID}}")
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setSuccessUrl("https://example.com/success.html")
    .setCancelUrl("https://example.com/cancel.html")
    .setSavedPaymentMethodOptions(
      SessionCreateParams.SavedPaymentMethodOptions.builder().build()
    )
    .putExtraParam("saved_payment_method_options[payment_method_remove]", "enabled")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer: '{{CUSTOMER_ID}}',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
  saved_payment_method_options: {
    payment_method_remove: 'enabled',
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  Customer: stripe.String("{{CUSTOMER_ID}}"),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  SuccessURL: stripe.String("https://example.com/success.html"),
  CancelURL: stripe.String("https://example.com/cancel.html"),
  SavedPaymentMethodOptions: &stripe.CheckoutSessionCreateSavedPaymentMethodOptionsParams{},
}
params.AddExtra("saved_payment_method_options[payment_method_remove]", "enabled")
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    Customer = "{{CUSTOMER_ID}}",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    SuccessUrl = "https://example.com/success.html",
    CancelUrl = "https://example.com/cancel.html",
    SavedPaymentMethodOptions = new Stripe.Checkout.SessionSavedPaymentMethodOptionsOptions
    {
        PaymentMethodRemove = "enabled",
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

The customer cant remove a payment method if its tied to an active subscription and the customer doesnt have a default payment method saved for invoice and subscription payments.

Optional: Separate authorization and capture [Server-side]

Stripe supports two-step card payments so you can first authorize a card, then capture funds later. When Stripe authorizes a payment, the card issuer guarantees the funds and places a hold for the payment amount on the customers card. You then have a certain amount of time to capture the funds, depending on the card). If you dont capture the payment before the authorization expires, the payment is cancelled and the issuer releases the held funds.

Separating authorization and capture is useful if you need to take additional actions between confirming that a customer is able to pay and collecting their payment. For example, if youre selling stock-limited items, you may need to confirm that an item purchased by your customer using Checkout is still available before capturing their payment and fulfilling the purchase. Accomplish this using the following workflow:

  1. Confirm that Stripe authorized the customers payment method.
  2. Consult your inventory management system to confirm that the item is still available.
  3. Update your inventory management system to indicate that a customer has purchased the item.
  4. Capture the customers payment.
  5. Inform your customer whether their purchase was successful on your confirmation page.

To indicate that you want to separate authorization and capture, you must set the value of payment_intent_data.capture_method to manual when creating the Checkout Session. This instructs Stripe to only authorize the amount on the customers card.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d "payment_intent_data[capture_method]"=manual \
  --data-urlencode success_url="https://example.com/success.html" \
  --data-urlencode cancel_url="https://example.com/cancel.html"
stripe checkout sessions create  \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  -d "payment_intent_data[capture_method]"=manual \
  --success-url="https://example.com/success.html" \
  --cancel-url="https://example.com/cancel.html"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  payment_intent_data: {capture_method: 'manual'},
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "mode": "payment",
  "payment_intent_data": {"capture_method": "manual"},
  "success_url": "https://example.com/success.html",
  "cancel_url": "https://example.com/cancel.html",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'payment_intent_data' => ['capture_method' => 'manual'],
  'success_url' => 'https://example.com/success.html',
  'cancel_url' => 'https://example.com/cancel.html',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setPaymentIntentData(
      SessionCreateParams.PaymentIntentData.builder()
        .setCaptureMethod(SessionCreateParams.PaymentIntentData.CaptureMethod.MANUAL)
        .build()
    )
    .setSuccessUrl("https://example.com/success.html")
    .setCancelUrl("https://example.com/cancel.html")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  payment_intent_data: {
    capture_method: 'manual',
  },
  success_url: 'https://example.com/success.html',
  cancel_url: 'https://example.com/cancel.html',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  PaymentIntentData: &stripe.CheckoutSessionCreatePaymentIntentDataParams{
    CaptureMethod: stripe.String("manual"),
  },
  SuccessURL: stripe.String("https://example.com/success.html"),
  CancelURL: stripe.String("https://example.com/cancel.html"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    Mode = "payment",
    PaymentIntentData = new Stripe.Checkout.SessionPaymentIntentDataOptions
    {
        CaptureMethod = "manual",
    },
    SuccessUrl = "https://example.com/success.html",
    CancelUrl = "https://example.com/cancel.html",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

To capture an uncaptured payment, you can use either the Dashboard or the capture endpoint. Programmatically capturing payments requires access to the PaymentIntent created during the Checkout Session, which you can get from the Session object.

Optional: Customer account management [No code]

Let your customers manage their own accounts by sharing a link to your customer portal (The customer portal is a secure, Stripe-hosted page that lets your customers manage their subscriptions and billing details). The customer portal lets customers log in with their email to manage subscriptions, update payment methods, and so on.

See also

Embedded form

This is a Embedded form for when platform is web and ui is embedded-form. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=embedded-form.

Embed a prebuilt payment form on your site using Stripe Checkout. See how this integration compares to Stripes other integration types.

Integration effort

Complexity: 2/5

Integration type

Embed prebuilt payment form on your site

UI customization

Limited customization

  • 20 preset fonts
  • 3 preset border radius
  • Custom background and border color
  • Custom logo

Use the branding settings in the Stripe Dashboard to match Checkout to your site design.

Use our official libraries to access the Stripe API from your application:

Ruby

# Available as a gem
sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Python

# Install through pip
pip3 install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin:
# https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md
# Specify that version in your requirements.txt file
stripe>=5.0.0

PHP

# Install the PHP library with Composer
composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:30.0.0"
<!--
  For Maven, add the following dependency to your POM and replace 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>30.0.0</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

Node.js

# Install with npm
npm install stripe --save

Go

# Make sure your project is using Go Modules
go mod init
# Install stripe-go
go get -u github.com/stripe/stripe-go/v83
// Then import the package
import (
  "github.com/stripe/stripe-go/v83"
)

.NET

# Install with dotnet
dotnet add package Stripe.net
dotnet restore
# Or install with NuGet
Install-Package Stripe.net

Create a Checkout Session [Server-side]

From your server, create a Checkout Session (A Checkout Session represents your customer's session as they pay for one-time purchases or subscriptions through Checkout. After a successful payment, the Checkout Session contains a reference to the Customer, and either the successful PaymentIntent or an active Subscription) and set the ui_mode to embedded. You can configure the Checkout Session with line items to include and options such as currency.

You can also create a Checkout Session for an existing customer, allowing you to prefill Checkout fields with known contact information and unify your purchase history for that customer.

To return customers to a custom page that you host on your website, specify that pages URL in the return_url parameter. Include the {CHECKOUT_SESSION_ID} template variable in the URL to retrieve the sessions status on the return page. Checkout automatically substitutes the variable with the Checkout Session ID before redirecting.

Read more about configuring the return page and other options for customizing redirect behavior.

After you create the Checkout Session, use the client_secret returned in the response to mount Checkout.

Ruby

# This example sets up an endpoint using the Sinatra framework.
# To learn more about Sinatra, watch this video: https://youtu.be/8aA9Enb8NVc.
require 'json'
require 'sinatra'
require 'stripe'

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/create-checkout-session' do
  session = Stripe::Checkout::Session.create({
    line_items: [{
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    }],
    mode: 'payment',ui_mode: 'embedded',
    return_url: 'https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}'
  })

  {clientSecret: session.client_secret}.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# To learn more about Flask, watch this video: https://youtu.be/7Ul1vfsmsDck.
import os
import stripe
from flask import Flask, redirect

app = Flask(__name__)

stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():
  session = stripe.checkout.Session.create(
    line_items = [{
      'price_data': {
        'currency': 'usd',
        'product_data': {
          'name': 'T-shirt',
        },
        'unit_amount': 2000,
      },
      'quantity': 1,
    }],
    mode = 'payment',ui_mode = 'embedded',
    return_url = 'https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}',
  )

  return jsonify(clientSecret=session.client_secret)

if __name__ == '__main__':
  app.run(port=4242)

PHP

<?php

require 'vendor/autoload.php';

$stripe = new \Stripe\StripeClient([
  "api_key" => '<<YOUR_SECRET_KEY>>'
]);

$checkout_session = $stripe->checkout->sessions->create([
  'line_items' => [[
    'price_data' => [
      'currency' => 'usd',
      'product_data' => [
        'name' => 'T-shirt',
      ],
      'unit_amount' => 2000,
    ],
    'quantity' => 1,
  ]],
  'mode' => 'payment','ui_mode' => 'embedded',
  'return_url' => 'https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}',
]);

  echo json_encode(array('clientSecret' => $checkout_session->client_secret));
?>

Java


import java.util.HashMap;
import java.util.Map;
import static spark.Spark.get;
import static spark.Spark.post;
import static spark.Spark.port;
import static spark.Spark.staticFiles;
import com.google.gson.Gson;

import com.stripe.Stripe;
import com.stripe.model.checkout.Session;
import com.stripe.param.checkout.SessionCreateParams;

public class Server {

  public static void main(String[] args) {
    port(4242);
    Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

    Gson gson = new Gson();

    post("/create-checkout-session", (request, response) -> {

      SessionCreateParams params =
        SessionCreateParams.builder()
          .setMode(SessionCreateParams.Mode.PAYMENT).setUiMode(SessionCreateParams.UiMode.EMBEDDED)
          .setReturnUrl("https://example.com/return?session_id={CHECKOUT_SESSION_ID}")
          .addLineItem(
          SessionCreateParams.LineItem.builder()
            .setQuantity(1L)
            .setPriceData(
              SessionCreateParams.LineItem.PriceData.builder()
                .setCurrency("usd")
                .setUnitAmount(2000L)
                .setProductData(
                  SessionCreateParams.LineItem.PriceData.ProductData.builder()
                    .setName("T-shirt")
                    .build())
                .build())
            .build())
          .build();

      Session session = Session.create(params);

      Map<String, String> map = new HashMap();
      map.put("clientSecret", session.getRawJsonObject().getAsJsonPrimitive("client_secret").getAsString());

      return map;
    }, gson::toJson);
  }
}

Node.js

// This example sets up an endpoint using the Express framework.
const express = require('express');
const app = express();

const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

app.post('/create-checkout-session', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    line_items: [{
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    }],
    mode: 'payment',ui_mode: 'embedded',
    return_url: 'https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}'
  });

  res.send({clientSecret: session.client_secret});
});

app.listen(4242, () => console.log(`Listening on port ${4242}!`));

Go


package main

import (
  "net/http"

  "github.com/labstack/echo"
  "github.com/labstack/echo/middleware"
  "github.com/stripe/stripe-go/v76.0.0"
  "github.com/stripe/stripe-go/v76.0.0/checkout/session"
)

// This example sets up an endpoint using the Echo framework.
// To learn more about Echo, watch this video: https://youtu.be/ePmEVBu8w6Y.

func main() {
  stripe.Key = "<<YOUR_SECRET_KEY>>"

  e := echo.New()
  e.Use(middleware.Logger())
  e.Use(middleware.Recover())

  e.POST("/create-checkout-session", createCheckoutSession)

  e.Logger.Fatal(e.Start("localhost:4242"))
}

type CheckoutData struct {
  ClientSecret string `json:"clientSecret"`
}

func createCheckoutSession(c echo.Context) (err error) {
  params := &stripe.CheckoutSessionParams{
    Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),UIMode: stripe.String("embedded"),
    ReturnURL: stripe.String("https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}"),
    LineItems: []*stripe.CheckoutSessionLineItemParams{
      &stripe.CheckoutSessionLineItemParams{
        PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
          Currency: stripe.String("usd"),
          ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
            Name: stripe.String("T-shirt"),
          },
          UnitAmount: stripe.Int64(2000),
        },
        Quantity: stripe.Int64(1),
      },
    },
  }

  s, _ := session.New(params)

  if err != nil {
    return err
  }

  data := CheckoutData{
    ClientSecret: s.ClientSecret,
  }

  return c.JSON(http.StatusOK, data)
}

.NET

// This example sets up an endpoint using the ASP.NET MVC framework.
// To learn more about ASP.NET MVC, watch this video: https://youtu.be/2-mMOB8MhmE.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Stripe;
using Stripe.Checkout;

namespace server.Controllers
{
  public class PaymentsController : Controller
  {
    public PaymentsController()
    {
      StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";
    }

    [HttpPost("create-checkout-session")]
    public ActionResult CreateCheckoutSession()
    {
      var options = new SessionCreateOptions
      {
        LineItems = new List<SessionLineItemOptions>
        {
          new SessionLineItemOptions
          {
            PriceData = new SessionLineItemPriceDataOptions
            {
              UnitAmount = 2000,
              Currency = "usd",
              ProductData = new SessionLineItemPriceDataProductDataOptions
              {
                Name = "T-shirt",
              },
            },
            Quantity = 1,
          },
        },
        Mode = "payment",
        UiMode = "embedded",
        ReturnUrl = "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
      };

      var service = new SessionService();
      Session session = service.Create(options);

      return Json(new {clientSecret = session.ClientSecret});
    }
  }
}

Mount Checkout [Client-side]

HTML + JS

Checkout is available as part of Stripe.js. Include the Stripe.js script on your page by adding it to the head of your HTML file. Next, create an empty DOM node (container) to use for mounting.

<head>
  <script src="https://js.stripe.com/clover/stripe.js"></script>
</head>
<body>
  <div id="checkout">
    <!-- Checkout will insert the payment form here -->
  </div>
</body>

Initialize Stripe.js with your publishable API key.

Create an asynchronous fetchClientSecret function that makes a request to your server to create the Checkout Session and retrieve the client secret. Pass this function into options when you create the Checkout instance:

// Initialize Stripe.js
const stripe = Stripe('<<YOUR_PUBLISHABLE_KEY>>');

initialize();

// Fetch Checkout Session and retrieve the client secret
async function initialize() {
  const fetchClientSecret = async () => {
    const response = await fetch("/create-checkout-session", {
      method: "POST",
    });
    const { clientSecret } = await response.json();
    return clientSecret;
  };

  // Initialize Checkout
  const checkout = await stripe.initEmbeddedCheckout({
    fetchClientSecret,
  });

  // Mount Checkout
  checkout.mount('#checkout');
}

React

Install react-stripe-js and the Stripe.js loader from npm:

npm install --save @stripe/react-stripe-js @stripe/stripe-js

To use the Embedded Checkout component, create an EmbeddedCheckoutProvider. Call loadStripe with your publishable API key and pass the returned Promise to the provider.

Create an asynchronous fetchClientSecret function that makes a request to your server to create the Checkout Session and retrieve the client secret. Pass this function into the options prop accepted by the provider.

import * as React from 'react';
import {loadStripe} from '@stripe/stripe-js';
import {
  EmbeddedCheckoutProvider,
  EmbeddedCheckout
} from '@stripe/react-stripe-js';

// Make sure to call `loadStripe` outside of a components render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('pk_test_123');

const App = () => {
  const fetchClientSecret = useCallback(() => {
    // Create a Checkout Session
    return fetch("/create-checkout-session", {
      method: "POST",
    })
      .then((res) => res.json())
      .then((data) => data.clientSecret);
  }, []);

  const options = {fetchClientSecret};

  return (
    <div id="checkout">
      <EmbeddedCheckoutProvider
        stripe={stripePromise}
        options={options}
      >
        <EmbeddedCheckout />
      </EmbeddedCheckoutProvider>
    </div>
  )
}

Checkout renders in an iframe that securely sends payment information to Stripe over an HTTPS connection.

Avoid placing Checkout within another iframe because some payment methods require redirecting to another page for payment confirmation.

Customize appearance

Customize Checkout to match the design of your site by setting the background color, button color, border radius, and fonts in your accounts branding settings.

By default, Checkout renders with no external padding or margin. We recommend using a container element such as a div to apply your desired margin (for example, 16px on all sides).

Show a return page

After your customer attempts payment, Stripe redirects them to a return page that you host on your site. When you created the Checkout Session, you specified the URL of the return page in the return_url parameter. Read more about other options for customizing redirect behavior.

When rendering your return page, retrieve the Checkout Session status using the Checkout Session ID in the URL. Handle the result according to the session status as follows:

  • complete: The payment succeeded. Use the information from the Checkout Session to render a success page.
  • open: The payment failed or was canceled. Remount Checkout so that your customer can try again.

Ruby

get '/session-status' do
  session = Stripe::Checkout::Session.retrieve(params[:session_id])

  {status: session.status, customer_email:  session.customer_details.email}.to_json
end

Python

@app.route('/session-status', methods=['GET'])
def session_status():
  session = stripe.checkout.Session.retrieve(request.args.get('session_id'))

  return jsonify(status=session.status, customer_email=session.customer_details.email)

PHP

try {
  // retrieve JSON from POST body
  $jsonStr = file_get_contents('php://input');
  $jsonObj = json_decode($jsonStr);

  $session = $stripe->checkout->sessions->retrieve($jsonObj->session_id);

  echo json_encode(['status' => $session->status, 'customer_email' => $session->customer_details->email]);
  http_response_code(200);
} catch (Error $e) {
  http_response_code(500);
  echo json_encode(['error' => $e->getMessage()]);
}

Java

get("/session-status", (request, response) -> {
      Session session = Session.retrieve(request.queryParams("session_id"));

      Map<String, String> map = new HashMap();
      map.put("status", session.getRawJsonObject().getAsJsonPrimitive("status").getAsString());
      map.put("customer_email", session.getRawJsonObject().getAsJsonObject("customer_details").getAsJsonPrimitive("email").getAsString());

      return map;
    }, gson::toJson);

Node.js

app.get('/session_status', async (req, res) => {
  const session = await stripe.checkout.sessions.retrieve(req.query.session_id);

  res.send({
    status: session.status,
    payment_status: session.payment_status,
    customer_email: session.customer_details.email
  });
});

Go

func retrieveCheckoutSession(w http.ResponseWriter, r *http.Request) {
  s, _ := session.Get(r.URL.Query().Get("session_id"), nil)

  writeJSON(w, struct {
    Status string `json:"status"`
    CustomerEmail string `json:"customer_email"`
  }{
    Status: string(s.Status),
    CustomerEmail: string(s.CustomerDetails.Email),
  })
}

.NET

[Route("session-status")]
[ApiController]
public class SessionStatusController : Controller
{
    [HttpGet]
    public ActionResult SessionStatus([FromQuery] string session_id)
    {
        var sessionService = new SessionService();
        Session session = sessionService.Get(session_id);

        return Json(new {status = session.Status,  customer_email = session.CustomerDetails.Email});
    }
}
const session = await fetch(`/session_status?session_id=${session_id}`)
if (session.status == 'open') {
  // Remount embedded Checkout
} else if (session.status == 'complete') {
  // Show success page
  // Optionally use session.payment_status or session.customer_email
  // to customize the success page
}

Redirect-based payment methods

During payment, some payment methods redirect the customer to an intermediate page, such as a bank authorization page. When they complete that page, Stripe redirects them to your return page.

Learn more about redirect-based payment methods and redirect behavior.

Handle post-payment events

Stripe sends a checkout.session.completed event when a customer completes a Checkout Session payment. Use the Dashboard webhook tool or follow the webhook guide to receive and handle these events, which might trigger you to:

  • Send an order confirmation email to your customer.
  • Log the sale in a database.
  • Start a shipping workflow.

Listen for these events rather than waiting for your customer to be redirected back to your website. Triggering fulfillment only from your Checkout landing page is unreliable. Setting up your integration to listen for asynchronous events allows you to accept different types of payment methods with a single integration.

Learn more in our fulfillment guide for Checkout.

Handle the following events when collecting payments with the Checkout:

Event Description Action
checkout.session.completed Sent when a customer successfully completes a Checkout Session. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
checkout.session.async_payment_succeeded Sent when a payment made with a delayed payment method, such as ACH direct debt, succeeds. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
checkout.session.async_payment_failed Sent when a payment made with a delayed payment method, such as ACH direct debt, fails. Notify the customer of the failure and bring them back on-session to attempt payment again.

Test your integration

To test your embedded payment form integration:

  1. Create an embedded Checkout Session and mount Checkout on your page.
  2. Fill out the payment details with a method from the table below.
    • Enter any future date for card expiry.
    • Enter any 3-digit number for CVC.
    • Enter any billing postal code.
  3. Click Pay. Youre redirected to your return_url.
  4. Go to the Dashboard and look for the payment on the Transactions page. If your payment succeeded, youll see it in that list.
  5. Click your payment to see more details, like a Checkout summary with billing information and the list of purchased items. You can use this information to fulfill the order.

Learn more about testing your integration.

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Wallets

Payment method Scenario How to test
Alipay Your customer successfully pays with a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.

Bank redirects

Payment method Scenario How to test
BECS Direct Debit Your customer successfully pays with BECS Direct Debit. Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status 3 minutes later.
BECS Direct Debit Your customers payment fails with an account_closed error code. Fill out the form using the account number 111111113 and BSB 000000.
Bancontact, EPS, iDEAL, and Przelewy24 Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

Vouchers

Payment method Scenario How to test
Boleto, OXXO Your customer pays with a Boleto or OXXO voucher. Select Boleto or OXXO as the payment method and submit the payment. Close the dialog after it appears.

See Testing for additional information to test your integration.

Optional: Add more payment methods

By default, Checkout supports many payment methods. You have to take additional steps to enable and display some methods, like Apple Pay, Google Pay, and buy now, pay later methods.

Apple Pay and Google Pay

To accept payments from Apple Pay and Google Pay, you must:

  • Enable them in your payment methods settings. Apple Pay is enabled by default.
  • Serve your application over HTTPS in development and production.
  • Register your domain.
  • Serve your application over HTTPS in development and production. You can use a service like ngrok to serve your application for local testing.

In addition, a Checkout Session only displays the Apple Pay button to customers when all of the following conditions are true:

  • The customers device is running macOS version 17 or later or iOS version 17 or later.
  • The customer is using the Safari browser.
  • The customer has a valid card registered with Apple Pay.

A Checkout Session only displays the Google Pay button to customers when all of the following conditions are true:

  • The customers device is running Chrome 61 or newer.
  • The customer has a valid card registered with Google Pay.

Regional testing

Stripe Checkout doesnt support Apple Pay or Google Pay for Stripe accounts or customers in India. If your IP address is in India, you cant test your Apple Pay or Google Pay integration, even if the Stripe account is outside India.

Optional: Create products and prices

You can set up your Checkout Session to accept tips and donations, or sell pay-what-you-want products and services.

Before you create a Checkout Session, you can create Products (Products represent what your business sells—whether that's a good or a service) and Prices (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) upfront. Use products to represent different physical goods or levels of service, and Prices (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) to represent each products pricing.

For example, you can create a T-shirt as a product with a price of 20 USD. This allows you to update and add prices without needing to change the details of your underlying products. You can either create products and prices with the Stripe Dashboard or API. Learn more about how products and prices work.

API

The API only requires a name to create a Product. Checkout displays the product name, description, and images that you supply.

curl https://api.stripe.com/v1/products \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d name=T-shirt
stripe products create  \
  --name=T-shirt
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

product = client.v1.products.create({name: 'T-shirt'})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
product = client.v1.products.create({"name": "T-shirt"})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$product = $stripe->products->create(['name' => 'T-shirt']);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

ProductCreateParams params = ProductCreateParams.builder().setName("T-shirt").build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Product product = client.v1().products().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const product = await stripe.products.create({
  name: 'T-shirt',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.ProductCreateParams{Name: stripe.String("T-shirt")}
result, err := sc.V1Products.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new ProductCreateOptions { Name = "T-shirt" };
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Products;
Product product = service.Create(options);

Next, create a Price to define how much to charge for your product. This includes how much the product costs and what currency to use.

curl https://api.stripe.com/v1/prices \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d product="{{PRODUCT_ID}}" \
  -d unit_amount=2000 \
  -d currency=usd
stripe prices create  \
  --product="{{PRODUCT_ID}}" \
  --unit-amount=2000 \
  --currency=usd
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

price = client.v1.prices.create({
  product: '{{PRODUCT_ID}}',
  unit_amount: 2000,
  currency: 'usd',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
price = client.v1.prices.create({
  "product": "{{PRODUCT_ID}}",
  "unit_amount": 2000,
  "currency": "usd",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$price = $stripe->prices->create([
  'product' => '{{PRODUCT_ID}}',
  'unit_amount' => 2000,
  'currency' => 'usd',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

PriceCreateParams params =
  PriceCreateParams.builder()
    .setProduct("{{PRODUCT_ID}}")
    .setUnitAmount(2000L)
    .setCurrency("usd")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Price price = client.v1().prices().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const price = await stripe.prices.create({
  product: '{{PRODUCT_ID}}',
  unit_amount: 2000,
  currency: 'usd',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.PriceCreateParams{
  Product: stripe.String("{{PRODUCT_ID}}"),
  UnitAmount: stripe.Int64(2000),
  Currency: stripe.String(stripe.CurrencyUSD),
}
result, err := sc.V1Prices.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new PriceCreateOptions
{
    Product = "{{PRODUCT_ID}}",
    UnitAmount = 2000,
    Currency = "usd",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Prices;
Price price = service.Create(options);

Dashboard

Copy products created in a sandbox to live mode so that you dont need to re-create them. In the Product detail view in the Dashboard, click Copy to live mode in the upper right corner. You can only do this once for each product created in a sandbox. Subsequent updates to the test product arent reflected for the live product.

Make sure youre in a sandbox by clicking Sandboxes within the Dashboard account picker. Next, define the items you want to sell. To create a new product and price:

  • Navigate to the Products section in the Dashboard.
  • Click Add product.
  • Select One time when setting the price.

Checkout displays the product name, description, and images that you supply.

Each price you create has an ID. When you create a Checkout Session, reference the price ID and quantity. If youre selling in multiple currencies, make your Price multi-currency (A single Price object can support multiple currencies. Each purchase uses one of the supported currencies for the Price, depending on how you use the Price in your integration). Checkout automatically determines the customers local currency and presents that currency if the Price supports it.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  -d ui_mode=embedded \
  --data-urlencode return_url="https://example.com/return"
stripe checkout sessions create  \
  --mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --ui-mode=embedded \
  --return-url="https://example.com/return"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "mode": "payment",
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "ui_mode": "embedded",
  "return_url": "https://example.com/return",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'mode' => 'payment',
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'ui_mode' => 'embedded',
  'return_url' => 'https://example.com/return',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setUiMode(SessionCreateParams.UiMode.EMBEDDED)
    .setReturnUrl("https://example.com/return")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  UIMode: stripe.String(stripe.CheckoutSessionUIModeEmbedded),
  ReturnURL: stripe.String("https://example.com/return"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    Mode = "payment",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    UiMode = "embedded",
    ReturnUrl = "https://example.com/return",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Optional: Prefill customer data [Server-side]

If youve already collected your customers email and want to prefill it in the Checkout Session for them, pass customer_email when creating a Checkout Session.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  --data-urlencode customer_email="customer@example.com" \
  -d "line_items[0][price]"="{{PRICE_ID}}" \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d ui_mode=embedded \
  --data-urlencode return_url="https://example.com/return"
stripe checkout sessions create  \
  --customer-email="customer@example.com" \
  -d "line_items[0][price]"="{{PRICE_ID}}" \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --ui-mode=embedded \
  --return-url="https://example.com/return"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_email: 'customer@example.com',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_email": "customer@example.com",
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "mode": "payment",
  "ui_mode": "embedded",
  "return_url": "https://example.com/return",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_email' => 'customer@example.com',
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'ui_mode' => 'embedded',
  'return_url' => 'https://example.com/return',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerEmail("customer@example.com")
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setUiMode(SessionCreateParams.UiMode.EMBEDDED)
    .setReturnUrl("https://example.com/return")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_email: 'customer@example.com',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerEmail: stripe.String("customer@example.com"),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  UIMode: stripe.String(stripe.CheckoutSessionUIModeEmbedded),
  ReturnURL: stripe.String("https://example.com/return"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerEmail = "customer@example.com",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    Mode = "payment",
    UiMode = "embedded",
    ReturnUrl = "https://example.com/return",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Optional: Save payment method details [Server-side]

By default, payment methods used to make a one-time payment with Checkout arent available for future use.

Save payment methods to charge them off-session

You can set Checkout to save payment methods used to make a one-time payment by passing the payment_intent_data.setup_future_usage argument. This is useful if you need to capture a payment method on-file to use for future fees, such as cancellation or no-show fees.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d customer_creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d ui_mode=embedded \
  --data-urlencode return_url="https://example.com/return" \
  -d "payment_intent_data[setup_future_usage]"=off_session
stripe checkout sessions create  \
  --customer-creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --ui-mode=embedded \
  --return-url="https://example.com/return" \
  -d "payment_intent_data[setup_future_usage]"=off_session
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
  payment_intent_data: {setup_future_usage: 'off_session'},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_creation": "always",
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "ui_mode": "embedded",
  "return_url": "https://example.com/return",
  "payment_intent_data": {"setup_future_usage": "off_session"},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_creation' => 'always',
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'ui_mode' => 'embedded',
  'return_url' => 'https://example.com/return',
  'payment_intent_data' => ['setup_future_usage' => 'off_session'],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerCreation(SessionCreateParams.CustomerCreation.ALWAYS)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setUiMode(SessionCreateParams.UiMode.EMBEDDED)
    .setReturnUrl("https://example.com/return")
    .setPaymentIntentData(
      SessionCreateParams.PaymentIntentData.builder()
        .setSetupFutureUsage(
          SessionCreateParams.PaymentIntentData.SetupFutureUsage.OFF_SESSION
        )
        .build()
    )
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
  payment_intent_data: {
    setup_future_usage: 'off_session',
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerCreation: stripe.String(stripe.CheckoutSessionCustomerCreationAlways),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  UIMode: stripe.String(stripe.CheckoutSessionUIModeEmbedded),
  ReturnURL: stripe.String("https://example.com/return"),
  PaymentIntentData: &stripe.CheckoutSessionCreatePaymentIntentDataParams{
    SetupFutureUsage: stripe.String("off_session"),
  },
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerCreation = "always",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    UiMode = "embedded",
    ReturnUrl = "https://example.com/return",
    PaymentIntentData = new Stripe.Checkout.SessionPaymentIntentDataOptions
    {
        SetupFutureUsage = "off_session",
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

If you use Checkout in subscription mode, Stripe automatically saves the payment method to charge it for subsequent payments. Card payment methods saved to customers using either setup_future_usage or subscription mode dont appear for return purchases in Checkout (more on this below). We recommend using custom text to link out to any relevant terms regarding the usage of saved payment information.

Global privacy laws are complicated and nuanced. We recommend contacting your legal and privacy team prior to implementing setup_future_usage because it might implicate your existing privacy compliance framework. Refer to the guidance issued by the European Protection Board to learn more about saving payment details.

Save payment methods to prefill them in Checkout

By default, Checkout uses Link to provide your customers with the option to securely save and reuse their payment information. If you prefer to manage payment methods yourself, use saved_payment_method_options.payment_method_save when creating a Checkout Session to let your customers save their payment methods for future purchases in Checkout.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d customer_creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d ui_mode=embedded \
  --data-urlencode return_url="https://example.com/return" \
  -d "saved_payment_method_options[payment_method_save]"=enabled
stripe checkout sessions create  \
  --customer-creation=always \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --ui-mode=embedded \
  --return-url="https://example.com/return" \
  -d "saved_payment_method_options[payment_method_save]"=enabled
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
  saved_payment_method_options: {payment_method_save: 'enabled'},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_creation": "always",
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "ui_mode": "embedded",
  "return_url": "https://example.com/return",
  "saved_payment_method_options": {"payment_method_save": "enabled"},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_creation' => 'always',
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'ui_mode' => 'embedded',
  'return_url' => 'https://example.com/return',
  'saved_payment_method_options' => ['payment_method_save' => 'enabled'],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerCreation(SessionCreateParams.CustomerCreation.ALWAYS)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setUiMode(SessionCreateParams.UiMode.EMBEDDED)
    .setReturnUrl("https://example.com/return")
    .setSavedPaymentMethodOptions(
      SessionCreateParams.SavedPaymentMethodOptions.builder()
        .setPaymentMethodSave(
          SessionCreateParams.SavedPaymentMethodOptions.PaymentMethodSave.ENABLED
        )
        .build()
    )
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_creation: 'always',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
  saved_payment_method_options: {
    payment_method_save: 'enabled',
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerCreation: stripe.String(stripe.CheckoutSessionCustomerCreationAlways),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  UIMode: stripe.String(stripe.CheckoutSessionUIModeEmbedded),
  ReturnURL: stripe.String("https://example.com/return"),
  SavedPaymentMethodOptions: &stripe.CheckoutSessionCreateSavedPaymentMethodOptionsParams{
    PaymentMethodSave: stripe.String(stripe.CheckoutSessionSavedPaymentMethodOptionsPaymentMethodSaveEnabled),
  },
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerCreation = "always",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    UiMode = "embedded",
    ReturnUrl = "https://example.com/return",
    SavedPaymentMethodOptions = new Stripe.Checkout.SessionSavedPaymentMethodOptionsOptions
    {
        PaymentMethodSave = "enabled",
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Passing this parameter in either payment or subscription mode displays an optional checkbox to let customers explicitly save their payment method for future purchases. When customers check this checkbox, Checkout saves the payment method with allow_redisplay: always. Checkout uses this parameter to determine whether a payment method can be prefilled on future purchases. When using saved_payment_method_options.payment_method_save, you dont need to pass in setup_future_usage to save the payment method.

Using saved_payment_method_options.payment_method_save requires a Customer. To save a new customer, set the Checkout Sessions customer_creation to always. Otherwise, the session doesnt save the customer or the payment method.

If payment_method_save isnt passed in or if the customer doesnt agree to save the payment method, Checkout still saves payment methods created in subscription mode or using setup_future_usage. These payment methods have an allow_redisplay value of limited, which prevents them from being prefilled for returning purchases and allows you to comply with card network rules and data protection regulations. Learn how to change the default behavior enabled by these modes and how to change or override allow_redisplay behavior.

You can use Checkout to save cards and other payment methods to charge them off-session, but Checkout only prefills saved cards. Learn how to prefill saved cards. To save a payment method without an initial payment, use Checkout in setup mode.

Let customers remove saved payment methods

To let your customers remove a saved payment method so it doesnt resurface for future payments, use saved_payment_method_options.payment_method_remove when creating a Checkout Session.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d customer={{CUSTOMER_ID}} \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d ui_mode=embedded \
  --data-urlencode return_url="https://example.com/return" \
  -d "saved_payment_method_options[payment_method_remove]"=enabled
stripe checkout sessions create  \
  --customer={{CUSTOMER_ID}} \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"=T-shirt \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  --ui-mode=embedded \
  --return-url="https://example.com/return" \
  -d "saved_payment_method_options[payment_method_remove]"=enabled
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer: '{{CUSTOMER_ID}}',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {name: 'T-shirt'},
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
  saved_payment_method_options: {payment_method_remove: 'enabled'},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer": "{{CUSTOMER_ID}}",
  "line_items": [
    {
      "price_data": {
        "currency": "usd",
        "product_data": {"name": "T-shirt"},
        "unit_amount": 2000,
      },
      "quantity": 1,
    },
  ],
  "mode": "payment",
  "ui_mode": "embedded",
  "return_url": "https://example.com/return",
  "saved_payment_method_options": {"payment_method_remove": "enabled"},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer' => '{{CUSTOMER_ID}}',
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'ui_mode' => 'embedded',
  'return_url' => 'https://example.com/return',
  'saved_payment_method_options' => ['payment_method_remove' => 'enabled'],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomer("{{CUSTOMER_ID}}")
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPriceData(
          SessionCreateParams.LineItem.PriceData.builder()
            .setCurrency("usd")
            .setProductData(
              SessionCreateParams.LineItem.PriceData.ProductData.builder()
                .setName("T-shirt")
                .build()
            )
            .setUnitAmount(2000L)
            .build()
        )
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setUiMode(SessionCreateParams.UiMode.EMBEDDED)
    .setReturnUrl("https://example.com/return")
    .setSavedPaymentMethodOptions(
      SessionCreateParams.SavedPaymentMethodOptions.builder().build()
    )
    .putExtraParam("saved_payment_method_options[payment_method_remove]", "enabled")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer: '{{CUSTOMER_ID}}',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: {
          name: 'T-shirt',
        },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
  saved_payment_method_options: {
    payment_method_remove: 'enabled',
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  Customer: stripe.String("{{CUSTOMER_ID}}"),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      PriceData: &stripe.CheckoutSessionCreateLineItemPriceDataParams{
        Currency: stripe.String(stripe.CurrencyUSD),
        ProductData: &stripe.CheckoutSessionCreateLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  UIMode: stripe.String(stripe.CheckoutSessionUIModeEmbedded),
  ReturnURL: stripe.String("https://example.com/return"),
  SavedPaymentMethodOptions: &stripe.CheckoutSessionCreateSavedPaymentMethodOptionsParams{},
}
params.AddExtra("saved_payment_method_options[payment_method_remove]", "enabled")
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    Customer = "{{CUSTOMER_ID}}",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
                {
                    Name = "T-shirt",
                },
                UnitAmount = 2000,
            },
            Quantity = 1,
        },
    },
    Mode = "payment",
    UiMode = "embedded",
    ReturnUrl = "https://example.com/return",
    SavedPaymentMethodOptions = new Stripe.Checkout.SessionSavedPaymentMethodOptionsOptions
    {
        PaymentMethodRemove = "enabled",
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

The customer cant remove a payment method if its tied to an active subscription and the customer doesnt have a default payment method saved for invoice and subscription payments.

Optional: Customer account management [No code]

Let your customers manage their own accounts by sharing a link to your customer portal (The customer portal is a secure, Stripe-hosted page that lets your customers manage their subscriptions and billing details). The customer portal lets customers log in with their email to manage subscriptions, update payment methods, and so on.

Optional: Separate authorization and capture [Server-side]

Stripe supports two-step card payments so you can first authorize a card, then capture funds later. When Stripe authorizes a payment, the card issuer guarantees the funds and places a hold for the payment amount on the customers card. You then have a certain amount of time to capture the funds, depending on the card). If you dont capture the payment before the authorization expires, the payment is cancelled and the issuer releases the held funds.

Separating authorization and capture is useful if you need to take additional actions between confirming that a customer is able to pay and collecting their payment. For example, if youre selling stock-limited items, you may need to confirm that an item purchased by your customer using Checkout is still available before capturing their payment and fulfilling the purchase. Accomplish this using the following workflow:

  1. Confirm that Stripe authorized the customers payment method.
  2. Consult your inventory management system to confirm that the item is still available.
  3. Update your inventory management system to indicate that a customer has purchased the item.
  4. Capture the customers payment.
  5. Inform your customer whether their purchase was successful on your confirmation page.

To indicate that you want to separate authorization and capture, you must set the value of payment_intent_data.capture_method to manual when creating the Checkout Session. This instructs Stripe to only authorize the amount on the customers card.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d "payment_intent_data[capture_method]"=manual \
  -d ui_mode=embedded \
  --data-urlencode return_url="https://example.com/return"
stripe checkout sessions create  \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  -d "payment_intent_data[capture_method]"=manual \
  --ui-mode=embedded \
  --return-url="https://example.com/return"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  payment_intent_data: {capture_method: 'manual'},
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "mode": "payment",
  "payment_intent_data": {"capture_method": "manual"},
  "ui_mode": "embedded",
  "return_url": "https://example.com/return",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'payment_intent_data' => ['capture_method' => 'manual'],
  'ui_mode' => 'embedded',
  'return_url' => 'https://example.com/return',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setPaymentIntentData(
      SessionCreateParams.PaymentIntentData.builder()
        .setCaptureMethod(SessionCreateParams.PaymentIntentData.CaptureMethod.MANUAL)
        .build()
    )
    .setUiMode(SessionCreateParams.UiMode.EMBEDDED)
    .setReturnUrl("https://example.com/return")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  payment_intent_data: {
    capture_method: 'manual',
  },
  ui_mode: 'embedded',
  return_url: 'https://example.com/return',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  PaymentIntentData: &stripe.CheckoutSessionCreatePaymentIntentDataParams{
    CaptureMethod: stripe.String("manual"),
  },
  UIMode: stripe.String(stripe.CheckoutSessionUIModeEmbedded),
  ReturnURL: stripe.String("https://example.com/return"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    Mode = "payment",
    PaymentIntentData = new Stripe.Checkout.SessionPaymentIntentDataOptions
    {
        CaptureMethod = "manual",
    },
    UiMode = "embedded",
    ReturnUrl = "https://example.com/return",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

To capture an uncaptured payment, you can use either the Dashboard or the capture endpoint. Programmatically capturing payments requires access to the PaymentIntent created during the Checkout Session, which you can get from the Session object.

Optional: Order fulfillment

Learn how to programmatically get a notification whenever a customer pays.

See also

Embedded components

This is a Embedded components for when platform is web and ui is embedded-components. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=embedded-components.

Build a checkout page on your website using Stripe Elements and Checkout Sessions, an integration that manages tax, discounts, shipping rates, and more.

Set up the server [Server-side]

Use the official Stripe libraries to access the API from your application.

Node.js

npm install stripe@18.0.0 --save

Ruby

gem install stripe -v 14.0.0

PHP

composer require stripe/stripe-php:17.0.0

Python

pip3 install 'stripe==12.0.0

Go

go get -u github.com/stripe/stripe-go/v82.0.0

.NET

# Install with dotnet
dotnet add package Stripe.net --version 48.0.0
dotnet restore
# Or install with NuGet
Install-Package Stripe.net -Version 48.0.0

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:29.0.0
<!--
  For Maven, add the following dependency to your POM and replace 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>29.0.0</version>
</dependency>

Set the SDK to use at least the 2025-03-31.basil API version.

TypeScript

// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
import Stripe from 'stripe';
const stripe = new Stripe('<<YOUR_SECRET_KEY>>', {
  apiVersion: '2025-03-31.basil' as any,
});

Node.js


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>', {
  apiVersion: '2025-03-31.basil',
});

Ruby


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'
Stripe.api_version = '2025-03-31.basil'

PHP


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient([
  "api_key" => "<<YOUR_SECRET_KEY>>",
  "stripe_version" => "2025-03-31.basil"
]);

Python


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'
stripe.api_version = '2025-03-31.basil'

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"
stripe.APIVersion += "2025-03-31.basil"

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"
stripe.APIVersion += "2025-03-31.basil"

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";
Stripe.apiVersion = "2025-03-31.basil"

Create a Checkout Session [Server-side]

Add an endpoint on your server that creates a Checkout Session and returns its client secret to your front end. A Checkout Session represents your customers session as they pay for one-time purchases or subscriptions. Checkout Sessions expire 24 hours after creation.

TypeScript

import express, {Express} from 'express';

const app: Express = express();

app.post('/create-checkout-session', async (req: Express.Request, res: Express.Response) => {
  const session = await stripe.checkout.sessions.create({
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {
            name: 'T-shirt',
          },
          unit_amount: 2000,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',ui_mode: 'custom',
    // The URL of your payment completion page
    return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}'
  });

  res.json({checkoutSessionClientSecret: session.client_secret});
});

app.listen(3000, () => {
  console.log('Running on port 3000');
});

Node.js

const express = require('express');
const app = express();

app.post('/create-checkout-session', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {
            name: 'T-shirt',
          },
          unit_amount: 2000,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',ui_mode: 'custom',
    // The URL of your payment completion page
    return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}'
  });

  res.json({checkoutSessionClientSecret: session.client_secret});
});

app.listen(3000, () => {
  console.log('Running on port 3000');
});

Ruby

require 'sinatra'
require 'stripe'

set :static, true
set :port, 4242

post '/create-checkout-session' do
  content_type 'application/json'
  data = JSON.parse(request.body.read)

  session = Stripe::Checkout::Session.create({
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {name: 'T-shirt'},
          unit_amount: 2000,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',ui_mode: 'custom',
    return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
  })

  {
    checkoutSessionClientSecret: session.client_secret,
  }.to_json
end

PHP

$stripe->checkout->sessions->create([
  'line_items' => [
    [
      'price_data' => [
        'currency' => 'usd',
        'product_data' => ['name' => 'T-shirt'],
        'unit_amount' => 2000,
      ],
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment','ui_mode' => 'custom',
  'return_url' => 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
]);

Python

import json
import os
from flask import Flask, render_template, jsonify, request
app = Flask(__name__, static_folder='public',
            static_url_path='', template_folder='public')

@app.route('/create-checkout-session', methods=['POST'])
def checkout():
    try:
        session = stripe.checkout.Session.create(
          line_items=[
            {
              "price_data": {
                "currency": "usd",
                "product_data": {"name": "T-shirt"},
                "unit_amount": 2000,
              },
              "quantity": 1,
            },
          ],
          mode="payment",ui_mode="custom",
          # The URL of your payment completion page
          return_url="https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
        )
        return jsonify({
            'checkoutSessionClientSecret': session['client_secret']
        })
    except Exception as e:
        return jsonify(error=str(e)), 403

if __name__ == '__main__':
    app.run(port=4242)

Go

params := &stripe.CheckoutSessionParams{
  LineItems: []*stripe.CheckoutSessionLineItemParams{
    &stripe.CheckoutSessionLineItemParams{
      PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
        Currency: stripe.String(string(stripe.CurrencyUSD)),
        ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
          Name: stripe.String("T-shirt"),
        },
        UnitAmount: stripe.Int64(2000),
      },
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),UIMode: stripe.String(string(stripe.CheckoutSessionUIModeCustom)),
  ReturnURL: stripe.String("https://example.com/return?session_id={CHECKOUT_SESSION_ID}"),
};
result, err := session.New(params);

.NET

// This example sets up an endpoint using the ASP.NET MVC framework.
// To learn more about ASP.NET MVC, watch this video: https://youtu.be/2-mMOB8MhmE.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Stripe;
using Stripe.Checkout;

namespace server.Controllers
{
  public class PaymentsController : Controller
  {
    public PaymentsController()
    {
      StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";
    }

    [HttpPost("create-checkout-session")]
    public ActionResult CreateCheckoutSession()
    {
      var options = new SessionCreateOptions
      {
        LineItems = new List<SessionLineItemOptions>
        {
          new SessionLineItemOptions
          {
            PriceData = new SessionLineItemPriceDataOptions
            {
              UnitAmount = 2000,
              Currency = "usd",
              ProductData = new SessionLineItemPriceDataProductDataOptions
              {
                Name = "T-shirt",
              },
            },
            Quantity = 1,
          },
        },
        Mode = "payment",UiMode = "custom",
        ReturnUrl = "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
      };

      var service = new SessionService();
      Session session = service.Create(options);

      return Json(new {checkoutSessionClientSecret = session.ClientSecret});
    }
  }
}

Java

import java.util.HashMap;
import java.util.Map;
import static spark.Spark.get;
import static spark.Spark.post;
import static spark.Spark.port;
import static spark.Spark.staticFiles;
import com.google.gson.Gson;

import com.stripe.Stripe;
import com.stripe.model.checkout.Session;
import com.stripe.param.checkout.SessionCreateParams;

public class Server {

  public static void main(String[] args) {
    port(4242);
    Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

    Gson gson = new Gson();

    post("/create-checkout-session", (request, response) -> {

      SessionCreateParams params =
        SessionCreateParams.builder()
          .setMode(SessionCreateParams.Mode.PAYMENT).setUiMode(SessionCreateParams.UiMode.CUSTOM)
          .setReturnUrl("https://example.com/return?session_id={CHECKOUT_SESSION_ID}")
          .addLineItem(
          SessionCreateParams.LineItem.builder()
            .setQuantity(1L)
            .setPriceData(
              SessionCreateParams.LineItem.PriceData.builder()
                .setCurrency("usd")
                .setUnitAmount(2000L)
                .setProductData(
                  SessionCreateParams.LineItem.PriceData.ProductData.builder()
                    .setName("T-shirt")
                    .build())
                .build())
            .build())
          .build();

      Session session = Session.create(params);

      Map<String, String> map = new HashMap();
      map.put("checkoutSessionClientSecret", session.getRawJsonObject().getAsJsonPrimitive("client_secret").getAsString());

      return map;
    }, gson::toJson);
  }
}

Set up the front end [Client-side]

HTML + 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. Dont include the script in a bundle or host a copy of it yourself.

Ensure youre on the latest Stripe.js version by including the following script tag <script src=“https://js.stripe.com/clover/stripe.js”></script>. Learn more about Stripe.js versioning.

<head>
  <title>Checkout</title>
  <script src="https://js.stripe.com/clover/stripe.js"></script>
</head>

Stripe provides an npm package that you can use to load Stripe.js as a module. See the project on GitHub. Version 7.0.0 or later is required.

Initialize stripe.js.

// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = Stripe(
  '<<YOUR_PUBLISHABLE_KEY>>',
);

React

Install React Stripe.js and the Stripe.js loader from the npm public registry. You need at least version 5.0.0 for React Stripe.js and version 8.0.0 for the Stripe.js loader.


npm install --save @stripe/react-stripe-js@^5.0.0 @stripe/stripe-js@^8.0.0

Initialize a stripe instance on your front end with your publishable key.

import {loadStripe} from '@stripe/stripe-js';
const stripe = loadStripe("<<YOUR_PUBLISHABLE_KEY>>");

Initialize Checkout [Client-side]

HTML + JS

Create either a clientSecret promise that resolves with the client secret or set it as the secret directly. Call initCheckout, passing in clientSecret. initCheckout returns a promise that resolves to a Checkout instance.

The checkout object acts as the foundation of your checkout page, and contains data from the Checkout Session and methods to update the Session.

The object returned by actions.getSession() contains your pricing information. We recommend reading and displaying the total, and lineItems from the session in your UI.

This lets you turn on new features with minimal code changes. For example, adding manual currency prices requires no UI changes if you display the total.

const clientSecret = fetch('/create-checkout-session', {method: 'POST'})
  .then((response) => response.json())
  .then((json) => json.checkoutSessionClientSecret);
const checkout = stripe.initCheckout({clientSecret});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
  const session = loadActionsResult.actions.getSession();
  const checkoutContainer = document.getElementById('checkout-container');
  checkoutContainer.append(JSON.stringify(session.lineItems, null, 2));
  checkoutContainer.append(document.createElement('br'));
  checkoutContainer.append(`Total: ${session.total.total.amount}`);
}
<div id="checkout-container"></div>

React

Create clientSecret as a Promise<string> | string containing the client secret returned by your server.

Wrap your application with the CheckoutProvider component, passing in clientSecret and the stripe instance.

Use the useCheckout hook in your components to get the Checkout object, which contains data from the Checkout Session and methods to update the Session.

Use the Checkout object as the container for your prices. We recommend reading and displaying the total and lineItems from the Checkout object in your UI.

This lets you enable features with minimal code changes. For example, adding manual currency prices requires no UI changes if you display the total.

import React from 'react';
import {CheckoutProvider} from '@stripe/react-stripe-js/checkout';
import CheckoutForm from './CheckoutForm';

const clientSecret = fetch('/create-checkout-session', {method: 'POST'})
  .then((response) => response.json())
  .then((json) => json.checkoutSessionClientSecret);

const App = () => {
  return (
    <CheckoutProvider
      stripe={stripe}options={{clientSecret}}
    >
      <CheckoutForm />
    </CheckoutProvider>
  );
};

export default App;
import React from 'react';
import {useCheckout} from '@stripe/react-stripe-js/checkout';

const CheckoutForm = () => {const checkoutState = useCheckout();
  switch (checkoutState.type) {
    case "loading": return <div>Loading ...</div>;
    case "error": return <div>Error: {checkoutState.error.message}</div>;
    case "success":
      return (
        <pre>{JSON.stringify(checkoutState.checkout.lineItems, null, 2)}
          // A formatted total amount
          Total: {checkoutState.checkout.total.total.amount}
        </pre>
      );
  }
};

Collect customer email [Client-side]

HTML + JS

If you already pass in an existing customer_email or Customer with a valid email set when creating the Checkout Session, you can skip this step.

If you implement your own email validation, you can pass in the validated email on checkout.confirm and skip this step.

Create an email input to collect your customers email address. Call updateEmail when your customer finishes the input to validate and save the email address.

Depending on the design of your checkout form, you can call updateEmail in the following ways:

  • Directly before submitting the payment. You can also call updateEmail to validate earlier, such as on input blur.
  • Before transitioning to the next step, such as clicking a Save button, if your form includes multiple steps.
<input type="text" id="email" />
<div id="email-errors"></div>
const checkout = stripe.initCheckout({clientSecret});
const loadActionsResult = await checkout.loadActions();

if (loadActionsResult.type === 'success') {
  const {actions} = loadActionsResult;
  const emailInput = document.getElementById('email');
  const emailErrors = document.getElementById('email-errors');

  emailInput.addEventListener('input', () => {
    // Clear any validation errors
    emailErrors.textContent = '';
  });

  emailInput.addEventListener('blur', () => {
    const newEmail = emailInput.value;actions.updateEmail(newEmail).then((result) => {
      if (result.error) {
        emailErrors.textContent = result.error.message;
      }
    });
  });
}

React

If you already pass in an existing customer_email or Customer with a valid email set when creating the Checkout Session, you can skip this step.

If you implement your own email validation, you can pass in the validated email on confirm and skip this step.

Create a component to collect your customers email address. Call updateEmail when your customer finishes the input to validate and save the email address.

Depending on the design of your checkout form, you can call updateEmail in the following ways:

  • Directly before submitting the payment. You can also call updateEmail to validate earlier, such as on input blur.
  • Before transitioning to the next step, such as clicking a Save button, if your form includes multiple steps.
import React from 'react';
import {useCheckout} from '@stripe/react-stripe-js/checkout';

const EmailInput = () => {
  const checkoutState = useCheckout();
  const [email, setEmail] = React.useState('');
  const [error, setError] = React.useState(null);
  
  if (checkoutState.type === 'loading') {
    return (
      <div>Loading...</div>
    );
  } else if (checkoutState.type === 'error') {
    return (
      <div>Error: {checkoutState.error.message}</div>
    );
  }

  const handleBlur = () => {checkoutState.checkoutActions.updateEmail(email).then((result) => {
      if (result.error) {
        setError(result.error);
      }
    })
  };

  const handleChange = (e) => {
    setError(null);
    setEmail(e.target.value);
  };
  return (
    <div>
      <input
        type="text"
        value={email}
        onChange={handleChange}
        onBlur={handleBlur}
      />
      {error && <div>{error.message}</div>}
    </div>
  );
};

export default EmailInput;

Collect payment details [Client-side]

Collect payment details on the client with the Payment Element. The Payment Element is a prebuilt UI component that simplifies collecting payment details for a variety of payment methods.

HTML + JS

First, create a container DOM element to mount the Payment Element. Then create an instance of the Payment Element using checkout.createPaymentElement and mount it by calling element.mount, providing either a CSS selector or the container DOM element.

<div id="payment-element"></div>
const paymentElement = checkout.createPaymentElement();
paymentElement.mount('#payment-element');

See the Stripe.js docs to view the supported options.

You can customize the appearance of all Elements by passing elementsOptions.appearance when initializing Checkout on the front end.

React

Mount the Payment Element component within the CheckoutProvider.

import React from 'react';
import {PaymentElement, useCheckout} from '@stripe/react-stripe-js/checkout';

const CheckoutForm = () => {
  const checkoutState = useCheckout();
  return (
    <form>
      <PaymentElement options={{layout: 'accordion'}}/>
    </form>
  )
};

export default CheckoutForm;

See the Stripe.js docs to view the supported options.

You can customize the appearance of all Elements by passing elementsOptions.appearance to the CheckoutProvider.

Submit the payment [Client-side]

HTML + JS

Render a Pay button that calls confirm from the checkout instance to submit the payment.

<button id="pay-button">Pay</button>
<div id="confirm-errors"></div>
const checkout = stripe.initCheckout({clientSecret});
const loadActionsResult = await checkout.loadActions();

if (loadActionsResult.type === 'success') {
  const {actions} = loadActionsResult;
  const button = document.getElementById('pay-button');
  const errors = document.getElementById('confirm-errors');
  button.addEventListener('click', () => {
    // Clear any validation errors
    errors.textContent = '';

    actions.confirm().then((result) => {
      if (result.type === 'error') {
        errors.textContent = result.error.message;
      }
    });
  });
}

React

Render a Pay button that calls confirm from useCheckout to submit the payment.

import React from 'react';
import {useCheckout} from '@stripe/react-stripe-js/checkout';

const PayButton = () => {
  const checkoutState = useCheckout();
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  const handleClick = () => {
    setLoading(true);
    confirm().then((result) => {
      if (result.type === 'error') {
        setError(result.error)
      }
      setLoading(false);
    })
  };

  return (
    <div>
      <button disabled={loading} onClick={handleClick}>
        Pay
      </button>
      {error && <div>{error.message}</div>}
    </div>
  )
};

export default PayButton;

Test your integration

  1. Navigate to your checkout page.
  2. Fill out the payment details with a payment method from the following table. For card payments:
    • Enter any future date for card expiry.
    • Enter any 3-digit number for CVC.
    • Enter any billing postal code.
  3. Submit the payment to Stripe.
  4. Go to the Dashboard and look for the payment on the Transactions page. If your payment succeeded, youll see it in that list.
  5. Click your payment to see more details, like billing information and the list of purchased items. You can use this information to fulfill the order.

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Wallets

Payment method Scenario How to test
Alipay Your customer successfully pays with a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.

Bank redirects

Payment method Scenario How to test
BECS Direct Debit Your customer successfully pays with BECS Direct Debit. Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status 3 minutes later.
BECS Direct Debit Your customers payment fails with an account_closed error code. Fill out the form using the account number 111111113 and BSB 000000.
Bancontact, EPS, iDEAL, and Przelewy24 Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

Vouchers

Payment method Scenario How to test
Boleto, OXXO Your customer pays with a Boleto or OXXO voucher. Select Boleto or OXXO as the payment method and submit the payment. Close the dialog after it appears.

See Testing for additional information to test your integration.

Optional: Create products and prices

You can set up your Checkout Session to accept tips and donations, or sell pay-what-you-want products and services.

Before you create a Checkout Session, you can create Products (Products represent what your business sells—whether that's a good or a service) and Prices (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) upfront. Use products to represent different physical goods or levels of service, and Prices (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) to represent each products pricing.

For example, you can create a T-shirt as a product with a price of 20 USD. This allows you to update and add prices without needing to change the details of your underlying products. You can either create products and prices with the Stripe Dashboard or API. Learn more about how products and prices work.

API

The API only requires a name to create a Product. Checkout displays the product name, description, and images that you supply.

curl https://api.stripe.com/v1/products \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d name=T-shirt
stripe products create  \
  --name=T-shirt
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

product = client.v1.products.create({name: 'T-shirt'})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
product = client.v1.products.create({"name": "T-shirt"})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$product = $stripe->products->create(['name' => 'T-shirt']);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

ProductCreateParams params = ProductCreateParams.builder().setName("T-shirt").build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Product product = client.v1().products().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const product = await stripe.products.create({
  name: 'T-shirt',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.ProductCreateParams{Name: stripe.String("T-shirt")}
result, err := sc.V1Products.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new ProductCreateOptions { Name = "T-shirt" };
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Products;
Product product = service.Create(options);

Next, create a Price to define how much to charge for your product. This includes how much the product costs and what currency to use.

curl https://api.stripe.com/v1/prices \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d product="{{PRODUCT_ID}}" \
  -d unit_amount=2000 \
  -d currency=usd
stripe prices create  \
  --product="{{PRODUCT_ID}}" \
  --unit-amount=2000 \
  --currency=usd
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

price = client.v1.prices.create({
  product: '{{PRODUCT_ID}}',
  unit_amount: 2000,
  currency: 'usd',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
price = client.v1.prices.create({
  "product": "{{PRODUCT_ID}}",
  "unit_amount": 2000,
  "currency": "usd",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$price = $stripe->prices->create([
  'product' => '{{PRODUCT_ID}}',
  'unit_amount' => 2000,
  'currency' => 'usd',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

PriceCreateParams params =
  PriceCreateParams.builder()
    .setProduct("{{PRODUCT_ID}}")
    .setUnitAmount(2000L)
    .setCurrency("usd")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Price price = client.v1().prices().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const price = await stripe.prices.create({
  product: '{{PRODUCT_ID}}',
  unit_amount: 2000,
  currency: 'usd',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.PriceCreateParams{
  Product: stripe.String("{{PRODUCT_ID}}"),
  UnitAmount: stripe.Int64(2000),
  Currency: stripe.String(stripe.CurrencyUSD),
}
result, err := sc.V1Prices.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new PriceCreateOptions
{
    Product = "{{PRODUCT_ID}}",
    UnitAmount = 2000,
    Currency = "usd",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Prices;
Price price = service.Create(options);

Dashboard

Copy products created in a sandbox to live mode so that you dont need to re-create them. In the Product detail view in the Dashboard, click Copy to live mode in the upper right corner. You can only do this once for each product created in a sandbox. Subsequent updates to the test product arent reflected for the live product.

Make sure youre in a sandbox by clicking Sandboxes within the Dashboard account picker. Next, define the items you want to sell. To create a new product and price:

  • Navigate to the Products section in the Dashboard.
  • Click Add product.
  • Select One time when setting the price.

Checkout displays the product name, description, and images that you supply.

Each price you create has an ID. When you create a Checkout Session, reference the price ID and quantity. If youre selling in multiple currencies, make your Price multi-currency (A single Price object can support multiple currencies. Each purchase uses one of the supported currencies for the Price, depending on how you use the Price in your integration). Checkout automatically determines the customers local currency and presents that currency if the Price supports it.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d ui_mode=custom \
  -d mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --data-urlencode return_url="https://example.com/return?session_id={CHECKOUT_SESSION_ID}"
stripe checkout sessions create  \
  --ui-mode=custom \
  --mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --return-url="https://example.com/return?session_id={CHECKOUT_SESSION_ID}"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  ui_mode: 'custom',
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "ui_mode": "custom",
  "mode": "payment",
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "return_url": "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'ui_mode' => 'custom',
  'mode' => 'payment',
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'return_url' => 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setUiMode(SessionCreateParams.UiMode.CUSTOM)
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setReturnUrl("https://example.com/return?session_id={CHECKOUT_SESSION_ID}")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  ui_mode: 'custom',
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  UIMode: stripe.String(stripe.CheckoutSessionUIModeCustom),
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  ReturnURL: stripe.String("https://example.com/return?session_id={CHECKOUT_SESSION_ID}"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    UiMode = "custom",
    Mode = "payment",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    ReturnUrl = "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Optional: Prefill customer data [Server-side]

If youve already collected your customers email and want to prefill it in the Checkout Session for them, pass customer_email when creating a Checkout Session.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  --data-urlencode customer_email="customer@example.com" \
  -d ui_mode=custom \
  -d mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --data-urlencode return_url="https://example.com/return?session_id={CHECKOUT_SESSION_ID}"
stripe checkout sessions create  \
  --customer-email="customer@example.com" \
  --ui-mode=custom \
  --mode=payment \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --return-url="https://example.com/return?session_id={CHECKOUT_SESSION_ID}"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  customer_email: 'customer@example.com',
  ui_mode: 'custom',
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "customer_email": "customer@example.com",
  "ui_mode": "custom",
  "mode": "payment",
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "return_url": "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'customer_email' => 'customer@example.com',
  'ui_mode' => 'custom',
  'mode' => 'payment',
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'return_url' => 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .setCustomerEmail("customer@example.com")
    .setUiMode(SessionCreateParams.UiMode.CUSTOM)
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setReturnUrl("https://example.com/return?session_id={CHECKOUT_SESSION_ID}")
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  customer_email: 'customer@example.com',
  ui_mode: 'custom',
  mode: 'payment',
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  CustomerEmail: stripe.String("customer@example.com"),
  UIMode: stripe.String(stripe.CheckoutSessionUIModeCustom),
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  ReturnURL: stripe.String("https://example.com/return?session_id={CHECKOUT_SESSION_ID}"),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    CustomerEmail = "customer@example.com",
    UiMode = "custom",
    Mode = "payment",
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    ReturnUrl = "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

Optional: Save payment method details

Learn how to accept a payment and save your customers payment details for future purchases.

Optional: Listen for Checkout Session changes

Listen for Checkout Session changes

You can listen for changes to the Checkout Session by adding an event listener on the change event with checkout.on.

HTML + JS

checkout = stripe.initCheckout({
  clientSecret: promise,
  elementsOptions: { appearance },
});

checkout.on('change', (session) => {
  // Handle changes to the checkout session
});

React

import React from 'react';
import { useCheckout } from '@stripe/react-stripe-js/checkout';

const CheckoutForm = () => {
  const checkoutState = useCheckout();
  if (checkoutState.type === 'success') {
    checkoutState.checkout.on('change', (session) => {
      // Handle changes to the checkout session
    });
  }
};

Optional: Collect billing and shipping addresses

Collect a billing address

By default, a Checkout Session collects the minimal billing details required for payment through the Payment Element.

Using the Billing Address Element

You can collect complete billing addresses using the Billing Address Element.

First, pass billing_address_collection=required when you create the Checkout Session.

HTML + JS

Create a container DOM element to mount the Billing Address Element. Then create an instance of the Billing Address Element using checkout.createBillingAddressElement and mount it by calling element.mount, providing either a CSS selector or the container DOM element.

<div id="billing-address"></div>
const billingAddressElement = checkout.createBillingAddressElement();
billingAddressElement.mount('#billing-address');

The Billing Address Element supports the following options:

React

Mount the BillingAddressElement component within the CheckoutProvider.

import React from 'react';
import {BillingAddressElement} from '@stripe/react-stripe-js/checkout';

const CheckoutForm = () => {
  return (
    <form>
      <BillingAddressElement/>
    </form>
  )
};

The Billing Address Element supports the following props:

Using a custom form

You can build your own form to collect billing addresses.

  • If your checkout page has a distinct address collection step before confirmation, call updateBillingAddress when your customer submits the address.
  • Otherwise, you can submit the address when your customer clicks the “pay” button by passing billingAddress to confirm.

Collect partial billing addresses

To collect partial billing addresses, such as only the country and postal code, pass billing_address_collection=auto.

When collecting partial billing addresses, you must collect addresses manually. By default, the Payment Element automatically collects the minimal billing details required for payment. To avoid double collection of billing details, pass fields.billingDetails=never when creating the Payment Element. If you only intend to collect a subset of billing details (such as the customers name), pass never for only the fields you intend to collect yourself.

Collect a shipping address

To collect a customers shipping address, pass the shipping_address_collection parameter when you create the Checkout Session.

When you collect a shipping address, you must also specify which countries to allow shipping to. Configure the allowed_countries property with an array of two-letter ISO country codes.

How to use the Shipping Address Element

You can collect complete shipping addresses with the Shipping Address Element.

HTML + JS

Create a container DOM element to mount the Shipping Address Element. Then create an instance of the Shipping Address Element using checkout.createShippingAddressElement and mount it by calling element.mount, providing either a CSS selector or the container DOM element.

<div id="shipping-address"></div>
const shippingAddressElement = checkout.createShippingAddressElement();
shippingAddressElement.mount('#shipping-address');

The Shipping Address Element supports the following options:

React

Mount the ShippingAddressElement component within the CheckoutProvider.

import React from 'react';
import {ShippingAddressElement} from '@stripe/react-stripe-js/checkout';

const CheckoutForm = () => {
  return (
    <form>
      <ShippingAddressElement/>
    </form>
  )
};

The Shipping Address Element supports the following props:

Listen for Checkout Session changes

You can listen for changes to the Checkout Session by adding an event listener to handle address-related changes.

HTML + JS

Use the Session object to render the shipping amount in your checkout form.

<div>
  <h3> Totals </h3>
  <div id="subtotal" ></div>
  <div id="shipping" ></div>
  <div id="total" ></div>
</div>
const checkout = stripe.initCheckout({clientSecret});
const subtotal = document.getElementById('subtotal');
const shipping = document.getElementById('shipping');
const total = document.getElementById('total');

checkout.on('change', (session) => {
  subtotal.textContent = `Subtotal: ${session.total.subtotal.amount}`;
  shipping.textContent = `Shipping: ${session.total.shippingRate.amount}`;
  total.textContent = `Total: ${session.total.total.amount}`;
});

React

Use useCheckout to render the shipping cost in your checkout form.

import React from 'react';
import {useCheckout, ShippingAddressElement} from '@stripe/react-stripe-js/checkout';

const CheckoutForm = () => {
  const checkoutState = useCheckout();

  if (checkoutState.type === 'error') {
    return (
      <div>Error: {checkoutState.error.message}</div>
    );
  }

  return (
    <div>
      <div>
        <form>
          <ShippingAddressElement />
        </form>
      </div>
      <div>
        <h2>Checkout Summary</h2>
        {checkoutState.type === 'success' && (
          <>
            <pre>
              {JSON.stringify(checkoutState.checkout.lineItems, null, 2)}
            </pre>
            <h3>Totals</h3>
            <pre>
              Subtotal: {checkoutState.checkout.total.subtotal.amount}
              Shipping: {checkoutState.checkout.total.shippingRate.amount}
              Total: {checkoutState.checkout.total.total.amount}
            </pre>
          </>
        )}
      </div>
    </div>
  )
};

Use a custom form

You can build your own form to collect shipping addresses.

  • If your checkout page has a distinct address collection step before confirmation, call updateShippingAddress when your customer submits the address.
  • Otherwise, you can submit the address when your customer clicks the “pay” button by passing shippingAddress to confirm.

Optional: Separate authorization and capture [Server-side]

Stripe supports two-step card payments so you can first authorize a card, then capture funds later. When Stripe authorizes a payment, the card issuer guarantees the funds and places a hold for the payment amount on the customers card. You then have a certain amount of time to capture the funds, depending on the card). If you dont capture the payment before the authorization expires, the payment is cancelled and the issuer releases the held funds.

Separating authorization and capture is useful if you need to take additional actions between confirming that a customer is able to pay and collecting their payment. For example, if youre selling stock-limited items, you may need to confirm that an item purchased by your customer using Checkout is still available before capturing their payment and fulfilling the purchase. Accomplish this using the following workflow:

  1. Confirm that Stripe authorized the customers payment method.
  2. Consult your inventory management system to confirm that the item is still available.
  3. Update your inventory management system to indicate that a customer has purchased the item.
  4. Capture the customers payment.
  5. Inform your customer whether their purchase was successful on your confirmation page.

To indicate that you want to separate authorization and capture, you must set the value of payment_intent_data.capture_method to manual when creating the Checkout Session. This instructs Stripe to only authorize the amount on the customers card.

curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  -d mode=payment \
  -d "payment_intent_data[capture_method]"=manual \
  -d return_url={{RETURN_URL}} \
  -d ui_mode=custom
stripe checkout sessions create  \
  -d "line_items[0][price]"={{PRICE_ID}} \
  -d "line_items[0][quantity]"=1 \
  --mode=payment \
  -d "payment_intent_data[capture_method]"=manual \
  --return-url={{RETURN_URL}} \
  --ui-mode=custom
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

session = client.v1.checkout.sessions.create({
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  payment_intent_data: {capture_method: 'manual'},
  return_url: '{{RETURN_URL}}',
  ui_mode: 'custom',
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
session = client.v1.checkout.sessions.create({
  "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}],
  "mode": "payment",
  "payment_intent_data": {"capture_method": "manual"},
  "return_url": "{{RETURN_URL}}",
  "ui_mode": "custom",
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$session = $stripe->checkout->sessions->create([
  'line_items' => [
    [
      'price' => '{{PRICE_ID}}',
      'quantity' => 1,
    ],
  ],
  'mode' => 'payment',
  'payment_intent_data' => ['capture_method' => 'manual'],
  'return_url' => '{{RETURN_URL}}',
  'ui_mode' => 'custom',
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

SessionCreateParams params =
  SessionCreateParams.builder()
    .addLineItem(
      SessionCreateParams.LineItem.builder()
        .setPrice("{{PRICE_ID}}")
        .setQuantity(1L)
        .build()
    )
    .setMode(SessionCreateParams.Mode.PAYMENT)
    .setPaymentIntentData(
      SessionCreateParams.PaymentIntentData.builder()
        .setCaptureMethod(SessionCreateParams.PaymentIntentData.CaptureMethod.MANUAL)
        .build()
    )
    .setReturnUrl("{{RETURN_URL}}")
    .setUiMode(SessionCreateParams.UiMode.CUSTOM)
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
Session session = client.v1().checkout().sessions().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const session = await stripe.checkout.sessions.create({
  line_items: [
    {
      price: '{{PRICE_ID}}',
      quantity: 1,
    },
  ],
  mode: 'payment',
  payment_intent_data: {
    capture_method: 'manual',
  },
  return_url: '{{RETURN_URL}}',
  ui_mode: 'custom',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.CheckoutSessionCreateParams{
  LineItems: []*stripe.CheckoutSessionCreateLineItemParams{
    &stripe.CheckoutSessionCreateLineItemParams{
      Price: stripe.String("{{PRICE_ID}}"),
      Quantity: stripe.Int64(1),
    },
  },
  Mode: stripe.String(stripe.CheckoutSessionModePayment),
  PaymentIntentData: &stripe.CheckoutSessionCreatePaymentIntentDataParams{
    CaptureMethod: stripe.String("manual"),
  },
  ReturnURL: stripe.String("{{RETURN_URL}}"),
  UIMode: stripe.String(stripe.CheckoutSessionUIModeCustom),
}
result, err := sc.V1CheckoutSessions.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new Stripe.Checkout.SessionCreateOptions
{
    LineItems = new List<Stripe.Checkout.SessionLineItemOptions>
    {
        new Stripe.Checkout.SessionLineItemOptions
        {
            Price = "{{PRICE_ID}}",
            Quantity = 1,
        },
    },
    Mode = "payment",
    PaymentIntentData = new Stripe.Checkout.SessionPaymentIntentDataOptions
    {
        CaptureMethod = "manual",
    },
    ReturnUrl = "{{RETURN_URL}}",
    UiMode = "custom",
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.Checkout.Sessions;
Stripe.Checkout.Session session = service.Create(options);

To capture an uncaptured payment, you can use either the Dashboard or the capture endpoint. Programmatically capturing payments requires access to the PaymentIntent created during the Checkout Session, which you can get from the Session object.

Optional: Customer account management [No code]

Let your customers manage their own accounts by sharing a link to your customer portal (The customer portal is a secure, Stripe-hosted page that lets your customers manage their subscriptions and billing details). The customer portal lets customers log in with their email to manage subscriptions, update payment methods, and so on.

Optional: Order fulfillment

Learn how to programmatically get a notification when a customer pays.

See also

Advanced integration

This is a Advanced integration for when platform is web and ui is elements. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=elements.

Build a custom payments integration by embedding UI components on your site, using Stripe Elements. See how this integration compares to Stripes other integration types.

The client-side and server-side code builds a checkout form that accepts various payment methods.

Integration effort

Complexity: 4/5

Integration type

Combine UI components into a custom payment flow

UI customization

CSS-level customization with the Appearance API

Interested in using Stripe Tax, discounts, shipping, or currency conversion?

Stripe has a Payment Element integration that manages tax, discounts, shipping, and currency conversion for you. See build a checkout page to learn more.

Set up Stripe [Server-side]

Use our official libraries to access the Stripe API from your application:

Ruby

# Available as a gem
sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Python

# Install through pip
pip3 install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin:
# https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md
# Specify that version in your requirements.txt file
stripe>=5.0.0

PHP

# Install the PHP library with Composer
composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:30.0.0"
<!--
  For Maven, add the following dependency to your POM and replace 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>30.0.0</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

Node.js

# Install with npm
npm install stripe --save

Go

# Make sure your project is using Go Modules
go mod init
# Install stripe-go
go get -u github.com/stripe/stripe-go/v83
// Then import the package
import (
  "github.com/stripe/stripe-go/v83"
)

.NET

# Install with dotnet
dotnet add package Stripe.net
dotnet restore
# Or install with NuGet
Install-Package Stripe.net

Create a PaymentIntent [Server-side]

If you want to render the Payment Element without first creating a PaymentIntent, see Collect payment details before creating an Intent.

The PaymentIntent object represents your intent to collect payment from a customer and tracks charge attempts and state changes throughout the payment process. A high-level overview of the payments integration this document describes. (See full diagram at https://docs.stripe.com/payments/accept-a-payment)

Create the PaymentIntent

Create a PaymentIntent on your server with an amount and currency. In the latest version of the API, specifying the automatic_payment_methods parameter is optional because Stripe enables its functionality by default. You can manage payment methods from the Dashboard. Stripe handles the return of eligible payment methods based on factors such as the transactions amount, currency, and payment flow.

Stripe uses your payment methods settings to display the payment methods you have enabled. To see how your payment methods appear to customers, enter a transaction ID or set an order amount and currency in the Dashboard. To override payment methods, manually list any that you want to enable using the payment_method_types attribute.

curl https://api.stripe.com/v1/payment_intents \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d amount=1099 \
  -d currency=usd \
  -d "automatic_payment_methods[enabled]"=true
stripe payment_intents create  \
  --amount=1099 \
  --currency=usd \
  -d "automatic_payment_methods[enabled]"=true
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

payment_intent = client.v1.payment_intents.create({
  amount: 1099,
  currency: 'usd',
  automatic_payment_methods: {enabled: true},
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
payment_intent = client.v1.payment_intents.create({
  "amount": 1099,
  "currency": "usd",
  "automatic_payment_methods": {"enabled": True},
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'usd',
  'automatic_payment_methods' => ['enabled' => true],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

PaymentIntentCreateParams params =
  PaymentIntentCreateParams.builder()
    .setAmount(1099L)
    .setCurrency("usd")
    .setAutomaticPaymentMethods(
      PaymentIntentCreateParams.AutomaticPaymentMethods.builder().setEnabled(true).build()
    )
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
PaymentIntent paymentIntent = client.v1().paymentIntents().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const paymentIntent = await stripe.paymentIntents.create({
  amount: 1099,
  currency: 'usd',
  automatic_payment_methods: {
    enabled: true,
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.PaymentIntentCreateParams{
  Amount: stripe.Int64(1099),
  Currency: stripe.String(stripe.CurrencyUSD),
  AutomaticPaymentMethods: &stripe.PaymentIntentCreateAutomaticPaymentMethodsParams{
    Enabled: stripe.Bool(true),
  },
}
result, err := sc.V1PaymentIntents.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new PaymentIntentCreateOptions
{
    Amount = 1099,
    Currency = "usd",
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
        Enabled = true,
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.PaymentIntents;
PaymentIntent paymentIntent = service.Create(options);

Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

Retrieve the client secret

The PaymentIntent includes a client secret (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)) that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side.

Single-page application

Retrieve the client secret from an endpoint on your server, using the browsers fetch function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret:

Ruby

get '/secret' do
  intent = # ... Create or retrieve the PaymentIntent
  {client_secret: intent.client_secret}.to_json
end

Python

from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/secret')
def secret():
  intent = # ... Create or retrieve the PaymentIntent
  return jsonify(client_secret=intent.client_secret)

PHP

<?php
    $intent = # ... Create or retrieve the PaymentIntent
    echo json_encode(array('client_secret' => $intent->client_secret));
?>

Java

import java.util.HashMap;
import java.util.Map;

import com.stripe.model.PaymentIntent;

import com.google.gson.Gson;

import static spark.Spark.get;

public class StripeJavaQuickStart {
  public static void main(String[] args) {
    Gson gson = new Gson();

    get("/secret", (request, response) -> {
      PaymentIntent intent = // ... Fetch or create the PaymentIntent

      Map<String, String> map = new HashMap();
      map.put("client_secret", intent.getClientSecret());

      return map;
    }, gson::toJson);
  }
}

Node.js

const express = require('express');
const app = express();

app.get('/secret', async (req, res) => {
  const intent = // ... Fetch or create the PaymentIntent
  res.json({client_secret: intent.client_secret});
});

app.listen(3000, () => {
  console.log('Running on port 3000');
});

Go

package main

import (
  "encoding/json"
  "net/http"

  stripe "github.com/stripe/stripe-go/v76.0.0"
)

type CheckoutData struct {
  ClientSecret string `json:"client_secret"`
}

func main() {
  http.HandleFunc("/secret", func(w http.ResponseWriter, r *http.Request) {
    intent := // ... Fetch or create the PaymentIntent
    data := CheckoutData{
      ClientSecret: intent.ClientSecret,
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(data)
  })

  http.ListenAndServe(":3000", nil)
}

.NET

using System;
using Microsoft.AspNetCore.Mvc;
using Stripe;

namespace StripeExampleApi.Controllers
{
  [Route("secret")]
  [ApiController]
  public class CheckoutApiController : Controller
  {
    [HttpGet]
    public ActionResult Get()
    {
      var intent = // ... Fetch or create the PaymentIntent
      return Json(new {client_secret = intent.ClientSecret});
    }
  }
}

And then fetch the client secret with JavaScript on the client side:

(async () => {
  const response = await fetch('/secret');
  const {client_secret: clientSecret} = await response.json();
  // Render the form using the clientSecret
})();

Server-side rendering

Pass the client secret to the client from your server. This approach works best if your application generates static content on the server before sending it to the browser.

Add the client_secret in your checkout form. In your server-side code, retrieve the client secret from the PaymentIntent:

Ruby

<form id="payment-form" data-secret="<%= @intent.client_secret %>">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
get '/checkout' do
  @intent = # ... Fetch or create the PaymentIntent
  erb :checkout
end

Python

<form id="payment-form" data-secret="{{ client_secret }}">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
@app.route('/checkout')
def checkout():
  intent = # ... Fetch or create the PaymentIntent
  return render_template('checkout.html', client_secret=intent.client_secret)

PHP

<?php
  $intent = # ... Fetch or create the PaymentIntent;
?>
...
<form id="payment-form" data-secret="<?= $intent->client_secret ?>">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
...

Java

<form id="payment-form" data-secret="{{ client_secret }}">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
import java.util.HashMap;
import java.util.Map;

import com.stripe.model.PaymentIntent;

import spark.ModelAndView;

import static spark.Spark.get;

public class StripeJavaQuickStart {
  public static void main(String[] args) {
    get("/checkout", (request, response) -> {
      PaymentIntent intent = // ... Fetch or create the PaymentIntent

      Map map = new HashMap();
      map.put("client_secret", intent.getClientSecret());

      return new ModelAndView(map, "checkout.hbs");
    }, new HandlebarsTemplateEngine());
  }
}

Node.js

<form id="payment-form" data-secret="{{ client_secret }}">
  <div id="payment-element">
    <!-- Elements will create form elements here -->
  </div>

  <button id="submit">Submit</button>
</form>
const express = require('express');
const expressHandlebars = require('express-handlebars');
const app = express();

app.engine('.hbs', expressHandlebars({ extname: '.hbs' }));
app.set('view engine', '.hbs');
app.set('views', './views');

app.get('/checkout', async (req, res) => {
  const intent = // ... Fetch or create the PaymentIntent
  res.render('checkout', { client_secret: intent.client_secret });
});

app.listen(3000, () => {
  console.log('Running on port 3000');
});

Go

<form id="payment-form" data-secret="{{ .ClientSecret }}">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
package main

import (
  "html/template"
  "net/http"

  stripe "github.com/stripe/stripe-go/v76.0.0"
)

type CheckoutData struct {
  ClientSecret string
}

func main() {
  checkoutTmpl := template.Must(template.ParseFiles("views/checkout.html"))

  http.HandleFunc("/checkout", func(w http.ResponseWriter, r *http.Request) {
    intent := // ... Fetch or create the PaymentIntent
    data := CheckoutData{
      ClientSecret: intent.ClientSecret,
    }
    checkoutTmpl.Execute(w, data)
  })

  http.ListenAndServe(":3000", nil)
}

.NET

<form id="payment-form" data-secret="@ViewData["ClientSecret"]">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
using System;
using Microsoft.AspNetCore.Mvc;
using Stripe;

namespace StripeExampleApi.Controllers
{
  [Route("/[controller]")]
  public class CheckoutApiController : Controller
  {
    public IActionResult Index()
    {
      var intent = // ... Fetch or create the PaymentIntent
      ViewData["ClientSecret"] = intent.ClientSecret;
      return View();
    }
  }
}

Collect payment details [Client-side]

Collect payment details on the client with the Payment Element. The Payment Element is a prebuilt UI component that simplifies collecting payment details for a variety of payment methods.

The Payment Element contains an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing the Payment Element within another iframe because some payment methods require redirecting to another page for payment confirmation.

If you do choose to use an iframe and want to accept Apple Pay or Google Pay, the iframe must have the allow attribute set to equal "payment *".

The checkout page address must start with https:// rather than http:// for your integration to work. You can test your integration without using HTTPS, but remember to enable it when youre ready to accept live payments.

HTML + JS

Set up Stripe.js

The Payment Element is automatically available as a feature of Stripe.js. Include the Stripe.js script on your checkout page by adding it to the head of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Dont include the script in a bundle or host a copy of it yourself.

<head>
  <title>Checkout</title>
  <script src="https://js.stripe.com/clover/stripe.js"></script>
</head>

Create an instance of Stripe with the following JavaScript on your checkout page:

// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = Stripe('<<YOUR_PUBLISHABLE_KEY>>');

Add the Payment Element to your payment page

The Payment Element needs a place to live on your payment page. Create an empty DOM node (container) with a unique ID in your payment form:

<form id="payment-form">
  <div id="payment-element">
    <!-- Elements will create form elements here -->
  </div>
  <button id="submit">Submit</button>
  <div id="error-message">
    <!-- Display error message to your customers here -->
  </div>
</form>

When the previous form loads, create an instance of the Payment Element and mount it to the container DOM node. Pass the client secret from the previous step into options when you create the Elements instance:

Handle the client secret carefully because it can complete the charge. Dont log it, embed it in URLs, or expose it to anyone but the customer.

const options = {
  clientSecret: '{{CLIENT_SECRET}}',
  // Fully customizable with appearance API.
  appearance: {/*...*/},
};

// Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in a previous stepconst elements = stripe.elements(options);

// Create and mount the Payment Element
const paymentElementOptions = { layout: 'accordion'};
const paymentElement = elements.create('payment', paymentElementOptions);
paymentElement.mount('#payment-element');

React

Set up Stripe.js

Install React Stripe.js and the Stripe.js loader from the npm public registry:

npm install --save @stripe/react-stripe-js @stripe/stripe-js

Add and configure the Elements provider to your payment page

To use the Payment Element component, wrap your checkout page component in an Elements provider. Call loadStripe with your publishable key, and pass the returned Promise to the Elements provider. Also pass the client secret from the previous step as options to the Elements provider.

import React from 'react';
import ReactDOM from 'react-dom';
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';

import CheckoutForm from './CheckoutForm';

// Make sure to call `loadStripe` outside of a components render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('<<YOUR_PUBLISHABLE_KEY>>');

function App() {
  const options = {
    // passing the client secret obtained in step 3
    clientSecret: '{{CLIENT_SECRET}}',
    // Fully customizable with appearance API.
    appearance: {/*...*/},
  };

  return (
    <Elements stripe={stripePromise} options={options}>
      <CheckoutForm />
    </Elements>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));

Add the Payment Element component

Use the PaymentElement component to build your form:

import React from 'react';
import {PaymentElement} from '@stripe/react-stripe-js';

const CheckoutForm = () => {
  return (
    <form><PaymentElement />
      <button>Submit</button>
    </form>
  );
};

export default CheckoutForm;

Stripe Elements is a collection of drop-in UI components. To further customize your form or collect different customer information, browse the Elements docs.

The Payment Element renders a dynamic form that allows your customer to pick a payment method. For each payment method, the form automatically asks the customer to fill in all necessary payment details.

Customize appearance

Customize the Payment Element to match the design of your site by passing the appearance object into options when creating the Elements provider.

Collect addresses

By default, the Payment Element only collects the necessary billing address details. Some behavior, such as calculating tax or entering shipping details, requires your customers full address. You can:

  • Use the Address Element to take advantage of autocomplete and localization features to collect your customers full address. This helps ensure the most accurate tax calculation.
  • Collect address details using your own custom form.

Request Apple Pay merchant token

If youve configured your integration to accept Apple Pay payments, we recommend configuring the Apple Pay interface to return a merchant token to enable merchant initiated transactions (MIT). Request the relevant merchant token type in the Payment Element.

Optional: Save and retrieve customer payment methods

You can configure the Payment Element to save your customers payment methods for future use. This section shows you how to integrate the saved payment methods feature, which enables the Payment Element to:

  • Prompt buyers for consent to save a payment method
  • Save payment methods when buyers provide consent
  • Display saved payment methods to buyers for future purchases
  • Automatically update lost or expired cards when buyers replace them The Payment Element and a saved payment method checkbox

Save payment methods. The Payment Element with a Saved payment method selected

Reuse a previously saved payment method.

Enable saving the payment method in the Payment Element

When creating a PaymentIntent on your server, also create a CustomerSession providing the Customer ID and enabling the payment_element component for your session. Configure which saved payment method features you want to enable. For instance, enabling payment_method_save displays a checkbox offering customers to save their payment details for future use.

You can specify setup_future_usage on a PaymentIntent or Checkout Session to override the default behavior for saving payment methods. This ensures that you automatically save the payment method for future use, even if the customer doesnt explicitly choose to save it.

Allowing buyers to remove their saved payment methods by enabling payment_method_remove impacts subscriptions that depend on that payment method. Removing the payment method detaches the PaymentMethod from that Customer.

Ruby


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/create-intent-and-customer-session' do
  intent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'usd',
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {enabled: true},
    customer: {{CUSTOMER_ID}},
  })
  customer_session = Stripe::CustomerSession.create({
    customer: {{CUSTOMER_ID}},
    components: {
      payment_element: {
          enabled: true,
          features: {
            payment_method_redisplay: 'enabled',
            payment_method_save: 'enabled',
            payment_method_save_usage: 'off_session',
            payment_method_remove: 'enabled',
          },
        },
    },
  })
  {
    client_secret: intent.client_secret,
    customer_session_client_secret: customer_session.client_secret
  }.to_json
end

Python


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/create-intent-and-customer-session', methods=['POST'])
def createIntentAndCustomerSession():
  intent = stripe.PaymentIntent.create(
    amount=1099,
    currency='usd',
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods={
      'enabled': True,
    },
    customer={{CUSTOMER_ID}},
  )
  customer_session = stripe.CustomerSession.create(
    customer={{CUSTOMER_ID}},
    components={
      "payment_element": {
        "enabled": True,
        "features": {
          "payment_method_redisplay": "enabled",
          "payment_method_save": "enabled",
          "payment_method_save_usage": "off_session",
          "payment_method_remove": "enabled",
        },
      },
    },
  )
  return jsonify(
    client_secret=intent.client_secret,
    customer_session_client_secret=customer_session.client_secret
  )

PHP


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$intent = $stripe->paymentIntents->create(
  [
    'amount' => 1099,
    'currency' => 'usd',
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    'automatic_payment_methods' => ['enabled' => true],
    'customer' => {{CUSTOMER_ID}},
  ]
);
$customer_session = $stripe->customerSessions->create([
  'customer' => {{CUSTOMER_ID}},
  'components' => [
    'payment_element' => [
      'enabled' => true,
      'features' => [
        'payment_method_redisplay' => 'enabled',
        'payment_method_save' => 'enabled',
        'payment_method_save_usage' => 'off_session',
        'payment_method_remove' => 'enabled',
      ],
    ],
  ],
]);
echo json_encode(array(
  'client_secret' => $intent->client_secret,
  'customer_session_client_secret' => $customer_session->client_secret
));

Node.js


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

app.post('/create-intent-and-customer-session', async (req, res) => {
  const intent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'usd',
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {enabled: true},
    customer: {{CUSTOMER_ID}},
  });
  const customerSession = await stripe.customerSessions.create({
    customer: {{CUSTOMER_ID}},
    components: {
      payment_element: {
        enabled: true,
        features: {
          payment_method_redisplay: 'enabled',
          payment_method_save: 'enabled',
          payment_method_save_usage: 'off_session',
          payment_method_remove: 'enabled',
        },
      },
    },
  });
  res.json({
    client_secret: intent.client_secret,
    customer_session_client_secret: customerSession.client_secret
  });
});

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

post(
  "/create-intent-and-customer-session",
  (request, response) -> {
    response.type("application/json");

    PaymentIntentCreateParams intentParams = PaymentIntentCreateParams.builder()
      .setAmount(1099L)
      .setCurrency("usd")
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      .setAutomaticPaymentMethods(
        PaymentIntentCreateParams.AutomaticPaymentMethods.builder().setEnabled(true).build()
      )
      .setCustomer({{CUSTOMER_ID}})
      .build();

    PaymentIntent paymentIntent = PaymentIntent.create(intentParams);

    CustomerSessionCreateParams csParams = CustomerSessionCreateParams.builder()
      .setCustomer({{CUSTOMER_ID}})
      .setComponents(CustomerSessionCreateParams.Components.builder().build())
      .putExtraParam("components[payment_element][enabled]", true)
      .putExtraParam(
        "components[payment_element][features][payment_method_redisplay]",
        "enabled"
      )
      .putExtraParam(
        "components[payment_element][features][payment_method_save]",
        "enabled"
      )
      .putExtraParam(
        "components[payment_element][features][payment_method_save_usage]",
        "off_session"
      )
      .putExtraParam(
        "components[payment_element][features][payment_method_remove]",
        "enabled"
      )
      .build();

    CustomerSession customerSession = CustomerSession.create(csParams);

    Map<String, Object> responseData = new HashMap<>();
    responseData.put("clientSecret", paymentIntent.getClientSecret());
    responseData.put("customerSessionClientSecret", customerSession.getClientSecret());
    return StripeObject.PRETTY_PRINT_GSON.toJson(responseData);
  }
);

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

type CheckoutData struct {
  ClientSecret string `json:"client_secret"`
  CustomerSessionClientSecret string `json:"customer_session_client_secret"`
}

func main() {
  http.HandleFunc("/create-intent-and-customer-session", func(w http.ResponseWriter, r *http.Request) {
    intentParams := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
        Enabled: stripe.Bool(true),
      },
      Customer: stripe.String({{CUSTOMER_ID}}),
    };
    intent, _ := .New(intentParams);

    csParams := &stripe.CustomerSessionParams{
      Customer: stripe.String({{CUSTOMER_ID}}),
      Components: &stripe.CustomerSessionComponentsParams{},
    }
    csParam.AddExtra("components[payment_element][enabled]", true)
    csParam.AddExtra(
      "components[payment_element][features][payment_method_redisplay]",
      "enabled",
    )
    csParam.AddExtra(
      "components[payment_element][features][payment_method_save]",
      "enabled",
    )
    csParam.AddExtra(
      "components[payment_element][features][payment_method_save_usage]",
      "off_session",
    )
    csParam.AddExtra(
      "components[payment_element][features][payment_method_remove]",
      "enabled",
    )

    customerSession, _ := customersession.New(csParams)
    data := CheckoutData{
      ClientSecret: intent.ClientSecret,
      CustomerSessionClientSecret: customerSession.ClientSecret
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(data)
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

namespace StripeExampleApi.Controllers
{
  [Route("create-intent-and-customer-session")]
  [ApiController]
  public class CheckoutApiController : Controller
  {
    [HttpPost]
    public ActionResult Post()
    {
      var intentOptions = new PaymentIntentCreateOptions
      {
          Amount = 1099,
          Currency = "usd",
          // In the latest version of the API, specifying the `automatic_payment_methods` parameter
          // is optional because Stripe enables its functionality by default.
          AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
          {
              Enabled = true,
          },
          Customer = {{CUSTOMER_ID}},
      };
      var intentService = new PaymentIntentService();
      var intent = intentService.Create(intentOptions);

      var customerSessionOptions = new CustomerSessionCreateOptions
      {
          Customer = {{CUSTOMER_ID}},
          Components = new CustomerSessionComponentsOptions(),
      }
      customerSessionOptions.AddExtraParam("components[payment_element][enabled]", true);
      customerSessionOptions.AddExtraParam(
          "components[payment_element][features][payment_method_redisplay]",
          "enabled");
      customerSessionOptions.AddExtraParam(
          "components[payment_element][features][payment_method_save]",
          "enabled");
      customerSessionOptions.AddExtraParam(
          "components[payment_element][features][payment_method_save_usage]",
          "off_session");
      customerSessionOptions.AddExtraParam(
          "components[payment_element][features][payment_method_remove]",
          "enabled");
      var customerSessionService = new CustomerSessionService();
      var customerSession = customerSessionService.Create(customerSessionOptions);

      return Json(new {
        client_secret = intent.ClientSecret,
        customerSessionClientSecret = customerSession.ClientSecret
      });
    }
  }
}

Your Elements instance uses the CustomerSessions client secret (A client secret is used with your publishable key to authenticate a request for a single object. Each client secret is unique to the object it's associated with) to access that customers saved payment methods. Handle errors properly when you create the CustomerSession. If an error occurs, you dont need to provide the CustomerSession client secret to the Elements instance, as its optional.

Create the Elements instance using the client secrets for both the PaymentIntent and the CustomerSession. Then, use this Elements instance to create a Payment Element.

// Create the CustomerSession and obtain its clientSecret
const res = await fetch("/create-intent-and-customer-session", {
  method: "POST"
});

const {
  customer_session_client_secret: customerSessionClientSecret
} = await res.json();

const elementsOptions = {
  clientSecret: '{{CLIENT_SECRET}}',customerSessionClientSecret,
  // Fully customizable with appearance API.
  appearance: {/*...*/},
};

// Set up Stripe.js and Elements to use in checkout form, passing the client secret
// and CustomerSession's client secret obtained in a previous step
const elements = stripe.elements(elementsOptions);

// Create and mount the Payment Element
const paymentElementOptions = { layout: 'accordion'};
const paymentElement = elements.create('payment', paymentElementOptions);
paymentElement.mount('#payment-element');

When confirming the PaymentIntent, Stripe.js automatically controls setting setup_future_usage on the PaymentIntent and allow_redisplay on the PaymentMethod, depending on whether the customer checked the box to save their payment details.

Enforce CVC recollection

Optionally, specify require_cvc_recollection when creating the PaymentIntent to enforce CVC recollection when a customer is paying with a card.

Detect the selection of a saved payment method

To control dynamic content when a saved payment method is selected, listen to the Payment Element change event, which is populated with the selected payment method.

paymentElement.on('change', function(event) {
  if (event.value.payment_method) {
    // Control dynamic content if a saved payment method is selected
  }
})

Let your customer check out faster by using Link in the Payment Element. You can autofill information for any logged-in customer already using Link, regardless of whether they initially saved their information in Link with another business. The default Payment Element integration includes a Link prompt in the card form. To manage Link in the Payment Element, go to your payment method settings. Authenticate or enroll with Link directly in the Payment Element during checkout

Collect a customer email address for Link authentication or enrollment

Integration options

There are two ways you can integrate Link with the Payment Element. Of these, Stripe recommends passing a customer email address to the Payment Element if available. Remember to consider how your checkout flow works when deciding between these options:

Integration option Checkout flow Description
Pass a customer email address to the Payment Element (Recommended) - Your customer enters their email address before landing on the checkout page (in a previous account creation step, for example).
  • You prefer to use your own email input field. | Programmatically pass a customer email address to the Payment Element. In this scenario, a customer authenticates to Link directly in the payment form instead of a separate UI component. | | Collect a customer email address in the Payment Element | - Your customers can choose to enter their email and authenticate or enroll with Link directly in the Payment Element during checkout.
  • No code change is required. | If a customer hasnt enrolled with Link and they choose a supported payment method in the Payment Element, theyre prompted to save their details using Link. For those who have already enrolled, Link automatically populates their payment information. |

Use defaultValues to pass a customer email address to the Payment Element.

const paymentElement = elements.create('payment', {
  defaultValues: {
    billingDetails: {
      email: 'foo@bar.com',
    }
  },
  // Other options
});

For more information, read how to build a custom checkout page that includes Link.

Optional: Fetch updates from the server [Client-side]

You might want to update attributes on the PaymentIntent after the Payment Element renders, such as the amount (for example, discount codes or shipping costs). You can update the PaymentIntent on your server, then call elements.fetchUpdates to see the new amount reflected in the Payment Element. This example shows you how to create the server endpoint that updates the amount on the PaymentIntent:

Ruby

get '/update' do
  intent = Stripe::PaymentIntent.update(
    '{{PAYMENT_INTENT_ID}}',
    {amount: 1499},
  )
  {status: intent.status}.to_json
end

Python

@app.route('/update')
def secret():
  intent = stripe.PaymentIntent.modify(
    "{{PAYMENT_INTENT_ID}}",
    amount=1499,
  )
  return jsonify(status=intent.status)

PHP

<?php
    $intent = $stripe->paymentIntents->update(
      '{{PAYMENT_INTENT_ID}}',
      ['amount' => 1499]
    );
    echo json_encode(array('status' => $intent->status));
?>

Java

import java.util.HashMap;
import java.util.Map;

import com.stripe.model.PaymentIntent;

import com.google.gson.Gson;

import static spark.Spark.get;

public class StripeJavaQuickStart {
  public static void main(String[] args) {
    Gson gson = new Gson();

    get("/update", (request, response) -> {
      PaymentIntent paymentIntent =
        PaymentIntent.retrieve(
          "{{PAYMENT_INTENT_ID}}"
        );

      Map<String, Object> params = new HashMap<>();
      params.put("amount", 1499);
      PaymentIntent updatedPaymentIntent =
        paymentIntent.update(params);

      Map<String, String> response = new HashMap();
      response.put("status", updatedPaymentIntent.getStatus());

      return map;
    }, gson::toJson);
  }
}

Node.js

app.get('/update', async (req, res) => {
  const intent = await stripe.paymentIntents.update(
    '{{PAYMENT_INTENT_ID}}',
    {amount: 1499}
  );
  res.json({status: intent.status});
});

Go

package main

import (
  "encoding/json"
  "net/http"

  stripe "github.com/stripe/stripe-go/v76.0.0"
)

type UpdateData struct {
  Status string `json:"status"`
}

func main() {
  http.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) {
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1499),
    }
    pi, _ := paymentintent.Update(
      "{{PAYMENT_INTENT_ID}}",
      params,
    )

    data := UpdateData{
      Status: pi.Status,
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(data)
  })

  http.ListenAndServe(":3000", nil)
}

.NET

using System;
using Microsoft.AspNetCore.Mvc;
using Stripe;

namespace StripeExampleApi.Controllers
{
  [Route("update")]
  [ApiController]
  public class CheckoutApiController : Controller
  {
    [HttpPost]
    public ActionResult Post()
    {
      var options = new PaymentIntentUpdateOptions
      {
        Amount = 1499,
      };
      var service = new PaymentIntentService();
      var intent = service.Update(
        "{{PAYMENT_INTENT_ID}}",
        options);
      return Json(new {status = intent.Status});
    }
  }
}

This example demonstrates how to update the UI to reflect these changes on the client side:

(async () => {
  const response = await fetch('/update');
  if (response.status === 'requires_payment_method') {
    const {error} = await elements.fetchUpdates();
  }
})();

Submit the payment to Stripe [Client-side]

Use stripe.confirmPayment to complete the payment using details from the Payment Element. Provide a return_url to this function to indicate where Stripe should redirect the user after they complete the payment. Your user may be first redirected to an intermediate site, like a bank authorization page, before being redirected to the return_url. Card payments immediately redirect to the return_url when a payment is successful.

If you dont want to redirect for card payments after payment completion, you can set redirect to if_required. This only redirects customers that check out with redirect-based payment methods.

HTML + JS

const form = document.getElementById('payment-form');

form.addEventListener('submit', async (event) => {
  event.preventDefault();
const {error} = await stripe.confirmPayment({
    //`Elements` instance that was used to create the Payment Element
    elements,
    confirmParams: {
      return_url: 'https://example.com/order/123/complete',
    },
  });

  if (error) {
    // This point will only be reached if there is an immediate error when
    // confirming the payment. Show error to your customer (for example, payment
    // details incomplete)
    const messageContainer = document.querySelector('#error-message');
    messageContainer.textContent = error.message;
  } else {
    // Your customer will be redirected to your `return_url`. For some payment
    // methods like iDEAL, your customer will be redirected to an intermediate
    // site first to authorize the payment, then redirected to the `return_url`.
  }
});

React

To call stripe.confirmPayment from your payment form component, use the useStripe and useElements hooks.

If you prefer traditional class components over hooks, you can instead use an ElementsConsumer.

import React, {useState} from 'react';
import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js';

const CheckoutForm = () => {
  const stripe = useStripe();
  const elements = useElements();

  const [errorMessage, setErrorMessage] = useState(null);

  const handleSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js hasn't yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }
const {error} = await stripe.confirmPayment({
      //`Elements` instance that was used to create the Payment Element
      elements,
      confirmParams: {
        return_url: 'https://example.com/order/123/complete',
      },
    });


    if (error) {
      // This point will only be reached if there is an immediate error when
      // confirming the payment. Show error to your customer (for example, payment
      // details incomplete)
      setErrorMessage(error.message);
    } else {
      // Your customer will be redirected to your `return_url`. For some payment
      // methods like iDEAL, your customer will be redirected to an intermediate
      // site first to authorize the payment, then redirected to the `return_url`.
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button disabled={!stripe}>Submit</button>
      {/* Show error message to your customers */}
      {errorMessage && <div>{errorMessage}</div>}
    </form>
  )
};

export default CheckoutForm;

Make sure the return_url corresponds to a page on your website that provides the status of the payment. When Stripe redirects the customer to the return_url, we provide the following URL query parameters:

Parameter Description
payment_intent The unique identifier for the PaymentIntent.
payment_intent_client_secret The client secret of the PaymentIntent object.

If you have tooling that tracks the customers browser session, you might need to add the stripe.com domain to the referrer exclude list. Redirects cause some tools to create new sessions, which prevents you from tracking the complete session.

Use one of the query parameters to retrieve the PaymentIntent. Inspect the status of the PaymentIntent to decide what to show your customers. You can also append your own query parameters when providing the return_url, which persist through the redirect process.

HTML + JS


// Initialize Stripe.js using your publishable key
const stripe = Stripe('<<YOUR_PUBLISHABLE_KEY>>');

// Retrieve the "payment_intent_client_secret" query parameter appended to
// your return_url by Stripe.js
const clientSecret = new URLSearchParams(window.location.search).get(
  'payment_intent_client_secret'
);

// Retrieve the PaymentIntent
stripe.retrievePaymentIntent(clientSecret).then(({paymentIntent}) => {
  const message = document.querySelector('#message')

  // Inspect the PaymentIntent `status` to indicate the status of the payment
  // to your customer.
  //
  // Some payment methods will [immediately succeed or fail][0] upon
  // confirmation, while others will first enter a `processing` state.
  //
  // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification
  switch (paymentIntent.status) {
    case 'succeeded':
      message.innerText = 'Success! Payment received.';
      break;

    case 'processing':
      message.innerText = "Payment processing. We'll update you when payment is received.";
      break;

    case 'requires_payment_method':
      message.innerText = 'Payment failed. Please try another payment method.';
      // Redirect your user back to your payment page to attempt collecting
      // payment again
      break;

    default:
      message.innerText = 'Something went wrong.';
      break;
  }
});

React

import React, {useState, useEffect} from 'react';
import {useStripe} from '@stripe/react-stripe-js';

const PaymentStatus = () => {
  const stripe = useStripe();
  const [message, setMessage] = useState(null);

  useEffect(() => {
    if (!stripe) {
      return;
    }

    // Retrieve the "payment_intent_client_secret" query parameter appended to
    // your return_url by Stripe.js
    const clientSecret = new URLSearchParams(window.location.search).get(
      'payment_intent_client_secret'
    );

    // Retrieve the PaymentIntent
    stripe
      .retrievePaymentIntent(clientSecret)
      .then(({paymentIntent}) => {
        // Inspect the PaymentIntent `status` to indicate the status of the payment
        // to your customer.
        //
        // Some payment methods will [immediately succeed or fail][0] upon
        // confirmation, while others will first enter a `processing` state.
        //
        // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification
        switch (paymentIntent.status) {
          case 'succeeded':
            setMessage('Success! Payment received.');
            break;

          case 'processing':
            setMessage("Payment processing. We'll update you when payment is received.");
            break;

          case 'requires_payment_method':
            // Redirect your user back to your payment page to attempt collecting
            // payment again
            setMessage('Payment failed. Please try another payment method.');
            break;

          default:
            setMessage('Something went wrong.');
            break;
        }
      });
  }, [stripe]);


  return message;
};

export default PaymentStatus;

Handle post-payment events [Server-side]

Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard webhook tool or follow the webhook guide to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept different types of payment methods with a single integration.

In addition to handling the payment_intent.succeeded event, we recommend handling these other events when collecting payments with the Payment Element:

Event Description Action
payment_intent.succeeded Sent when a customer successfully completes a payment. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
payment_intent.processing Sent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. Its followed by either a payment_intent.succeeded or payment_intent.payment_failed event in the future. Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete.
payment_intent.payment_failed Sent when a customer attempts a payment, but the payment fails. If a payment transitions from processing to payment_failed, offer the customer another attempt to pay.

Test your integration

To test your custom payments integration:

  1. Create a Payment Intent and retrieve the client secret.
  2. Fill out the payment details with a method from the following table.
    • Enter any future date for card expiry.
    • Enter any 3-digit number for CVC.
    • Enter any billing postal code.
  3. Submit the payment to Stripe. Youre redirected to your return_url.
  4. Go to the Dashboard and look for the payment on the Transactions page. If your payment succeeded, youll see it in that list.
  5. Click your payment to see more details, like billing information and the list of purchased items. You can use this information to fulfill the order.

Learn more about testing your integration.

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Wallets

Payment method Scenario How to test
Alipay Your customer successfully pays with a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.

Bank redirects

Payment method Scenario How to test
BECS Direct Debit Your customer successfully pays with BECS Direct Debit. Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status 3 minutes later.
BECS Direct Debit Your customers payment fails with an account_closed error code. Fill out the form using the account number 111111113 and BSB 000000.
Bancontact, EPS, iDEAL, and Przelewy24 Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

Vouchers

Payment method Scenario How to test
Boleto, OXXO Your customer pays with a Boleto or OXXO voucher. Select Boleto or OXXO as the payment method and submit the payment. Close the dialog after it appears.

See Testing for additional information to test your integration.

Optional: Add more payment methods

The Payment Element supports many payment methods by default. You have to take additional steps to enable and display some payment methods.

Affirm

To begin using Affirm, you must enable it in the Dashboard. When you create a PaymentIntent with the Affirm payment method, you need to include a shipping address. This example suggests passing the shipping information on the client after the customer selects their payment method. Learn more about using Affirm with Stripe.

HTML + JS

const form = document.getElementById('payment-form');

form.addEventListener('submit', async (event) => {
  event.preventDefault();

  const {error} = await stripe.confirmPayment({
    //`Elements` instance that was used to create the Payment Element
    elements,
    confirmParams: {
      return_url: 'https://my-site.com/order/123/complete',shipping: {
        name: 'Jenny Rosen',
        address: {
          line1: '1 Street',
          city: 'Seattle',
          state: 'WA',
          postal_code: '95123',
          country: 'US',
        },
      },

    },
  });

  if (error) {
    // This point is reached if there's an immediate error when
    // confirming the payment. Show error to your customer (e.g., payment
    // details incomplete)
    const messageContainer = document.querySelector('#error-message');
    messageContainer.textContent = error.message;
  } else {
    // Your customer is redirected to your `return_url`. For some payment
    // methods like iDEAL, your customer is redirected to an intermediate
    // site first to authorize the payment, then redirected to the `return_url`.
  }
});

React


import React, {useState} from 'react';
import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js';

const CheckoutForm = () => {
  const stripe = useStripe();
  const elements = useElements();

  const [errorMessage, setErrorMessage] = useState(null);

  const handleSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js hasn't yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    const {error} = await stripe.confirmPayment({
      //`Elements` instance that was used to create the Payment Element
      elements,
      confirmParams: {
        return_url: 'https://my-site.com/order/123/complete',shipping: {
          name: 'Jenny Rosen',
          address: {
            line1: '1 Street',
            city: 'Seattle',
            state: 'WA',
            postal_code: '95123',
            country: 'US',
          },
        },

      },
    });


    if (error) {
      // This point will only be reached if there is an immediate error when
      // confirming the payment. Show error to your customer (e.g., payment
      // details incomplete)
      setErrorMessage(error.message);
    } else {
      // Your customer will be redirected to your `return_url`. For some payment
      // methods like iDEAL, your customer will be redirected to an intermediate
      // site first to authorize the payment, then redirected to the `return_url`.
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button disabled={!stripe}>Submit</button>
      {/* Show error message to your customers */}
      {errorMessage && <div>{errorMessage}</div>}
    </form>
  )
};

export default CheckoutForm;

Test Affirm

Learn how to test different scenarios using the following table:

Scenario How to test
Your customer successfully pays with Affirm. Fill out the form (make sure to include a shipping address) and authenticate the payment.
Your customer fails to authenticate on the Affirm redirect page. Fill out the form and click Fail test payment on the redirect page.

Afterpay (Clearpay)

When you create a PaymentIntent with the Afterpay payment method, you need to include a shipping address. Learn more about using Afterpay with Stripe.

You can manage payment methods from the Dashboard. Stripe handles the return of eligible payment methods based on factors such as the transactions amount, currency, and payment flow. The example below uses the automatic_payment_methods attribute but you can list afterpay_clearpay with payment method types. In the latest version of the API, specifying the automatic_payment_methods parameter is optional because Stripe enables its functionality by default. Regardless of which option you choose, make sure that you enable Afterpay Clearpay in the Dashboard.

curl https://api.stripe.com/v1/payment_intents \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d amount=1099 \
  -d currency=usd \
  -d "automatic_payment_methods[enabled]"=true \
  -d "shipping[name]"="Jenny Rosen" \
  -d "shipping[address][line1]"="1234 Main Street" \
  -d "shipping[address][city]"="San Francisco" \
  -d "shipping[address][state]"=CA \
  -d "shipping[address][country]"=US \
  -d "shipping[address][postal_code]"=94111
stripe payment_intents create  \
  --amount=1099 \
  --currency=usd \
  -d "automatic_payment_methods[enabled]"=true \
  -d "shipping[name]"="Jenny Rosen" \
  -d "shipping[address][line1]"="1234 Main Street" \
  -d "shipping[address][city]"="San Francisco" \
  -d "shipping[address][state]"=CA \
  -d "shipping[address][country]"=US \
  -d "shipping[address][postal_code]"=94111
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")

payment_intent = client.v1.payment_intents.create({
  amount: 1099,
  currency: 'usd',
  automatic_payment_methods: {enabled: true},
  shipping: {
    name: 'Jenny Rosen',
    address: {
      line1: '1234 Main Street',
      city: 'San Francisco',
      state: 'CA',
      country: 'US',
      postal_code: '94111',
    },
  },
})
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
client = StripeClient("<<YOUR_SECRET_KEY>>")

# For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
payment_intent = client.v1.payment_intents.create({
  "amount": 1099,
  "currency": "usd",
  "automatic_payment_methods": {"enabled": True},
  "shipping": {
    "name": "Jenny Rosen",
    "address": {
      "line1": "1234 Main Street",
      "city": "San Francisco",
      "state": "CA",
      "country": "US",
      "postal_code": "94111",
    },
  },
})
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'usd',
  'automatic_payment_methods' => ['enabled' => true],
  'shipping' => [
    'name' => 'Jenny Rosen',
    'address' => [
      'line1' => '1234 Main Street',
      'city' => 'San Francisco',
      'state' => 'CA',
      'country' => 'US',
      'postal_code' => '94111',
    ],
  ],
]);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");

PaymentIntentCreateParams params =
  PaymentIntentCreateParams.builder()
    .setAmount(1099L)
    .setCurrency("usd")
    .setAutomaticPaymentMethods(
      PaymentIntentCreateParams.AutomaticPaymentMethods.builder().setEnabled(true).build()
    )
    .setShipping(
      PaymentIntentCreateParams.Shipping.builder()
        .setName("Jenny Rosen")
        .setAddress(
          PaymentIntentCreateParams.Shipping.Address.builder()
            .setLine1("1234 Main Street")
            .setCity("San Francisco")
            .setState("CA")
            .setCountry("US")
            .setPostalCode("94111")
            .build()
        )
        .build()
    )
    .build();

// For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
PaymentIntent paymentIntent = client.v1().paymentIntents().create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');

const paymentIntent = await stripe.paymentIntents.create({
  amount: 1099,
  currency: 'usd',
  automatic_payment_methods: {
    enabled: true,
  },
  shipping: {
    name: 'Jenny Rosen',
    address: {
      line1: '1234 Main Street',
      city: 'San Francisco',
      state: 'CA',
      country: 'US',
      postal_code: '94111',
    },
  },
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
params := &stripe.PaymentIntentCreateParams{
  Amount: stripe.Int64(1099),
  Currency: stripe.String(stripe.CurrencyUSD),
  AutomaticPaymentMethods: &stripe.PaymentIntentCreateAutomaticPaymentMethodsParams{
    Enabled: stripe.Bool(true),
  },
  Shipping: &stripe.ShippingDetailsParams{
    Name: stripe.String("Jenny Rosen"),
    Address: &stripe.AddressParams{
      Line1: stripe.String("1234 Main Street"),
      City: stripe.String("San Francisco"),
      State: stripe.String("CA"),
      Country: stripe.String("US"),
      PostalCode: stripe.String("94111"),
    },
  },
}
result, err := sc.V1PaymentIntents.Create(context.TODO(), params)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
var options = new PaymentIntentCreateOptions
{
    Amount = 1099,
    Currency = "usd",
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
        Enabled = true,
    },
    Shipping = new ChargeShippingOptions
    {
        Name = "Jenny Rosen",
        Address = new AddressOptions
        {
            Line1 = "1234 Main Street",
            City = "San Francisco",
            State = "CA",
            Country = "US",
            PostalCode = "94111",
        },
    },
};
var client = new StripeClient("<<YOUR_SECRET_KEY>>");
var service = client.V1.PaymentIntents;
PaymentIntent paymentIntent = service.Create(options);

Test Afterpay (Clearpay)

Learn how to test different scenarios using the following table:

Scenario How to test
Your customer successfully pays with Afterpay. Fill out the form (make sure to include a shipping address) and authenticate the payment.
Your customer fails to authenticate on the Afterpay redirect page. Fill out the form and click Fail test payment on the redirect page.

Apple Pay and Google Pay

When you enable card payments, we display Apple Pay and Google Pay for customers whose environment meets the wallet display conditions. To accept payments from these wallets, you must also:

Regional Testing

Stripe Elements doesnt support Google Pay or Apple Pay for Stripe accounts and customers in India. Therefore, you cant test your Google Pay or Apple Pay integration if the testers IP address is in India, even if the Stripe account is based outside India.

Learn more about using Apple Pay and Google Pay with Stripe.

ACH Direct Debit

When using the Payment Element with the ACH Direct Debit payment method, follow these steps:

  1. Create a Customer object.

    curl -X POST https://api.stripe.com/v1/customers \
      -u "<<YOUR_SECRET_KEY>>:"
    
    stripe customers create
    
    # Set your secret key. Remember to switch to your live secret key in production.
    # See your keys here: https://dashboard.stripe.com/apikeys
    client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")
    
    customer = client.v1.customers.create()
    
    # Set your secret key. Remember to switch to your live secret key in production.
    # See your keys here: https://dashboard.stripe.com/apikeys
    client = StripeClient("<<YOUR_SECRET_KEY>>")
    
    # For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
    customer = client.v1.customers.create()
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    $stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');
    
    $customer = $stripe->customers->create([]);
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");
    
    CustomerCreateParams params = CustomerCreateParams.builder().build();
    
    // For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
    Customer customer = client.v1().customers().create(params);
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
    
    const customer = await stripe.customers.create();
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
    params := &stripe.CustomerCreateParams{}
    result, err := sc.V1Customers.Create(context.TODO(), params)
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    var options = new CustomerCreateOptions();
    var client = new StripeClient("<<YOUR_SECRET_KEY>>");
    var service = client.V1.Customers;
    Customer customer = service.Create(options);
    
  2. Specify the customer ID when creating the PaymentIntent.

    curl https://api.stripe.com/v1/payment_intents \
      -u "<<YOUR_SECRET_KEY>>:" \
      -d amount=1099 \
      -d currency=usd \
      -d setup_future_usage=off_session \
      -d customer={{CUSTOMER_ID}} \
      -d "payment_method_types[]"=us_bank_account
    
    stripe payment_intents create  \
      --amount=1099 \
      --currency=usd \
      --setup-future-usage=off_session \
      --customer={{CUSTOMER_ID}} \
      -d "payment_method_types[0]"=us_bank_account
    
    # Set your secret key. Remember to switch to your live secret key in production.
    # See your keys here: https://dashboard.stripe.com/apikeys
    client = Stripe::StripeClient.new("<<YOUR_SECRET_KEY>>")
    
    payment_intent = client.v1.payment_intents.create({
      amount: 1099,
      currency: 'usd',
      setup_future_usage: 'off_session',
      customer: '{{CUSTOMER_ID}}',
      payment_method_types: ['us_bank_account'],
    })
    
    # Set your secret key. Remember to switch to your live secret key in production.
    # See your keys here: https://dashboard.stripe.com/apikeys
    client = StripeClient("<<YOUR_SECRET_KEY>>")
    
    # For SDK versions 12.4.0 or lower, remove '.v1' from the following line.
    payment_intent = client.v1.payment_intents.create({
      "amount": 1099,
      "currency": "usd",
      "setup_future_usage": "off_session",
      "customer": "{{CUSTOMER_ID}}",
      "payment_method_types": ["us_bank_account"],
    })
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    $stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');
    
    $paymentIntent = $stripe->paymentIntents->create([
      'amount' => 1099,
      'currency' => 'usd',
      'setup_future_usage' => 'off_session',
      'customer' => '{{CUSTOMER_ID}}',
      'payment_method_types' => ['us_bank_account'],
    ]);
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    StripeClient client = new StripeClient("<<YOUR_SECRET_KEY>>");
    
    PaymentIntentCreateParams params =
      PaymentIntentCreateParams.builder()
        .setAmount(1099L)
        .setCurrency("usd")
        .setSetupFutureUsage(PaymentIntentCreateParams.SetupFutureUsage.OFF_SESSION)
        .setCustomer("{{CUSTOMER_ID}}")
        .addPaymentMethodType("us_bank_account")
        .build();
    
    // For SDK versions 29.4.0 or lower, remove '.v1()' from the following line.
    PaymentIntent paymentIntent = client.v1().paymentIntents().create(params);
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
    
    const paymentIntent = await stripe.paymentIntents.create({
      amount: 1099,
      currency: 'usd',
      setup_future_usage: 'off_session',
      customer: '{{CUSTOMER_ID}}',
      payment_method_types: ['us_bank_account'],
    });
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    sc := stripe.NewClient("<<YOUR_SECRET_KEY>>")
    params := &stripe.PaymentIntentCreateParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(stripe.CurrencyUSD),
      SetupFutureUsage: stripe.String(stripe.PaymentIntentSetupFutureUsageOffSession),
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      PaymentMethodTypes: []*string{stripe.String("us_bank_account")},
    }
    result, err := sc.V1PaymentIntents.Create(context.TODO(), params)
    
    // Set your secret key. Remember to switch to your live secret key in production.
    // See your keys here: https://dashboard.stripe.com/apikeys
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
        SetupFutureUsage = "off_session",
        Customer = "{{CUSTOMER_ID}}",
        PaymentMethodTypes = new List<string> { "us_bank_account" },
    };
    var client = new StripeClient("<<YOUR_SECRET_KEY>>");
    var service = client.V1.PaymentIntents;
    PaymentIntent paymentIntent = service.Create(options);
    
  3. Select a verification method.

When using the ACH Direct Debit payment method with the Payment Element, you can only select automatic or instant.

Learn more about using ACH Direct Debit with Stripe.

Test ACH Direct Debit

Scenario How to test
Your customer successfully pays with a US bank account using instant verification. Select US bank account and fill out the form. Click the test institution. Follow the instructions on the modal to link your bank account. Click your payment button.
Your customer successfully pays with a US bank account using microdeposits. Select US bank account and fill out the form. Click Enter bank details manually instead. Follow the instructions on the modal to link your bank account. You may use these test account numbers. Click your payment button.
Your customer fails to complete the bank account linking process. Select US bank account and click the test institution or Enter bank details manually instead. Close the modal without completing it.

BLIK

When using the Payment Element with BLIK, the user can close the modal prompting them to authorize the payment in their banking app. This triggers a redirect to your return_url and doesnt return the user to the checkout page. Learn more about using BLIK with Stripe.

To handle users closing the modal, in the server-side handler for your return_url, inspect the Payment Intents status to see if its succeeded or still requires_action (meaning the user has closed the modal without authorizing), dealing with each case as needed.

QR code payment methods

When using the Payment Element with a QR code based payment method (WeChat Pay, PayNow, Pix, PromptPay, Cash App Pay), the user can close the QR code modal. This triggers a redirect to your return_url and doesnt return the user to the checkout page.

To handle users closing QR code modals, at the server-side handler for your return_url, inspect the Payment Intents status to see if its succeeded or still requires_action (meaning the user has closed the modal without paying), dealing with each case as needed.

Alternatively, prevent the automatic redirect to your return_url by passing the advanced optional parameter redirect=if_required, which prevents the redirect when closing a QR code modal.

Cash App Pay

The Payment Element renders a dynamic form differently in desktop web or mobile web since it uses different customer authentication methods. Learn more about using Cash App Pay with Stripe.

Mobile web app element

Cash App Pay is a redirect based payment method in mobile web. It redirects your customer to Cash App in live mode or a test payment page in a test environment. After the payment is complete, theyre redirected to the return_url, regardless of whether you set redirect=if_required or not.

Desktop web app element

Cash App Pay is a QR code payment method in desktop web, where the Payment Element renders a QR code modal. Your customer needs to scan the QR code with a QR code scanning application or the Cash App mobile application.

In live mode, it redirects the customer to the return_url as soon as theyre redirected to the Cash App. In test environments, they can approve or decline the payment before being redirected to the return_url. Customers can also close the QR code modal before completing the payment, which triggers a redirect to your return_url.

Make sure the return_url corresponds to a page on your website to inspect the Payment Intents status. The Payment Intents status can be succeeded, failed, or requires_action (for example, the customer has closed the modal without scanning the QR code).

Alternatively, prevent the automatic redirect to your return_url by passing the advanced optional parameter redirect=if_required, which prevents the redirect when closing a QR code modal.

PayPal

To use PayPal, make sure youre on a registered domain.

Disclose Stripe to your customers

Stripe collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. Youre responsible for disclosing and obtaining all rights and consents necessary for Stripe to use data in these ways. For more information, visit our privacy center.

See also

In-app integration for iOS

This is a In-app integration for iOS for when platform is ios. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=ios.

Integrate Stripes prebuilt payment UI into the checkout of your iOS app with the PaymentSheet class. See our sample integration on GitHub.

Set up Stripe [Server-side] [Client-side]

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

Ruby

# Available as a gem
sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Python

# Install through pip
pip3 install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin:
# https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md
# Specify that version in your requirements.txt file
stripe>=5.0.0

PHP

# Install the PHP library with Composer
composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:30.0.0"
<!--
  For Maven, add the following dependency to your POM and replace 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>30.0.0</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

Node.js

# Install with npm
npm install stripe --save

Go

# Make sure your project is using Go Modules
go mod init
# Install stripe-go
go get -u github.com/stripe/stripe-go/v83
// Then import the package
import (
  "github.com/stripe/stripe-go/v83"
)

.NET

# Install with dotnet
dotnet add package Stripe.net
dotnet restore
# Or install with NuGet
Install-Package Stripe.net

Client-side

The Stripe iOS SDK is open source, fully documented, and compatible with apps supporting iOS 13 or above.

Swift Package Manager

To install the SDK, follow these steps:

  1. In Xcode, select File > Add Package Dependencies… and enter https://github.com/stripe/stripe-ios-spm as the repository URL.
  2. Select the latest version number from our releases page.
  3. Add the StripePaymentSheet product to the target of your app.

CocoaPods

  1. If you havent already, install the latest version of CocoaPods.
  2. If you dont have an existing Podfile, run the following command to create one:
    pod init
    
  3. Add this line to your Podfile:
    pod 'StripePaymentSheet'
    
  4. Run the following command:
    pod install
    
  5. Dont forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.
  6. In the future, to update to the latest version of the SDK, run:
    pod update StripePaymentSheet
    

Carthage

  1. If you havent already, install the latest version of Carthage.
  2. Add this line to your Cartfile:
    github "stripe/stripe-ios"
    
  3. Follow the Carthage installation instructions. Make sure to embed all of the required frameworks listed here.
  4. In the future, to update to the latest version of the SDK, run the following command:
    carthage update stripe-ios --platform ios
    

Manual Framework

  1. Head to our GitHub releases page and download and unzip Stripe.xcframework.zip.
  2. Drag StripePaymentSheet.xcframework to the Embedded Binaries section of the General settings in your Xcode project. Make sure to select Copy items if needed.
  3. Repeat step 2 for all required frameworks listed here.
  4. In the future, to update to the latest version of our SDK, repeat steps 13.

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Enable payment methods

View your payment methods settings and enable the payment methods you want to support. You need at least one payment method enabled to create a PaymentIntent (The Payment Intents API tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods).

By default, Stripe enables cards and other prevalent payment methods that can help you reach more customers, but we recommend turning on additional payment methods that are relevant for your business and customers. See Payment method support for product and payment method support, and our pricing page for fees.

Add an endpoint [Server-side]

Note

To display the PaymentSheet before you create a PaymentIntent, see Collect payment details before creating an Intent.

This integration uses three Stripe API objects:

  1. PaymentIntent: Stripe uses this to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.

  2. (Optional) Customer: To set up a payment method for future payments, you must attach it to a Customer (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments). Create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customers account later.

  3. (Optional) Customer Ephemeral Key: Information on the Customer object is sensitive, and cant be retrieved directly from an app. An Ephemeral Key grants the SDK temporary access to the Customer.

If you never save cards to a Customer and dont allow returning Customers to reuse saved cards, you can omit the Customer and Customer Ephemeral Key objects from your integration.

For security reasons, your app cant create these objects. Instead, add an endpoint on your server that:

  1. Retrieves the Customer, or creates a new one.
  2. Creates an Ephemeral Key for the Customer.
  3. Creates a PaymentIntent with the amount, currency, and customer. You can also optionally include the automatic_payment_methods parameter. Stripe enables its functionality by default in the latest version of the API.
  4. Returns the Payment Intents client secret (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)), the Ephemeral Keys secret, the Customers id, and your publishable key to your app.

The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe pull payment methods from your Dashboard settings or you can list them manually. Regardless of the option you choose, know that the currency passed in the PaymentIntent filters the payment methods shown to the customer. For example, if you pass eur on the PaymentIntent and have OXXO enabled in the Dashboard, OXXO wont be shown to the customer because OXXO doesnt support eur payments.

Unless your integration requires a code-based option for offering payment methods, Stripe recommends the automated option. This is because Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Payment methods that increase conversion and that are most relevant to the currency and customers location are prioritized.

Manage payment methods from the Dashboard

You can fork and deploy an implementation of this endpoint on CodeSandbox for testing.

You can manage payment methods from the Dashboard. Stripe handles the return of eligible payment methods based on factors such as the transactions amount, currency, and payment flow. The PaymentIntent is created using the payment methods you configured in the Dashboard. If you dont want to use the Dashboard or if you want to specify payment methods manually, you can list them using the payment_method_types attribute.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -H "Stripe-Version: 2025-09-30.clover" \
  -H "Stripe-Account: 2025-09-30.clover" \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \
  # In the latest version of the API, specifying the `automatic_payment_methods` parameter
  # is optional because Stripe enables its functionality by default.
  -d "automatic_payment_methods[enabled]"=true \

Ruby

# This example sets up an endpoint using the Sinatra framework.


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods={
      'enabled': True,
    },
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,
  // In the latest version of the API, specifying the `automatic_payment_methods` parameter
  // is optional because Stripe enables its functionality by default.
  'automatic_payment_methods' => [
    'enabled' => 'true',
  ],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

post(
  "/payment-sheet",
  (request, response) -> {
    response.type("application/json");

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
    Customer customer = Customer.create(customerParams);
    
     // Set the connected account ID
    String connectedAccountId = "{{CONNECTED_ACCOUNT_ID}}";

    // Create RequestOptions with the Stripe-Account header
    RequestOptions requestOptions = RequestOptions.builder()
        .setStripeAccount(connectedAccountId)
        .build();

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();

    // Pass the requestOptions when creating the customer
    Customer customer = Customer.create(customerParams, requestOptions);
  });
    EphemeralKeyCreateParams ephemeralKeyParams =
      EphemeralKeyCreateParams.builder()
        .setStripeAccount("{{CONNECTED_ACCOUNT_ID}}")
        .setStripeVersion("2025-09-30.clover")
        .setCustomer(customer.getId())
        .build();

    EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);
    PaymentIntentCreateParams paymentIntentParams =
    PaymentIntentCreateParams.builder()
      .setAmount(1099L)
      .setCurrency("eur")
      .setCustomer(customer.getId())
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      .setAutomaticPaymentMethods(
        PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
          .setEnabled(true)
          .build()
      )
      .build();

    PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

    Map<String, String> responseData = new HashMap();
    responseData.put("paymentIntent", paymentIntent.getClientSecret());
    responseData.put("ephemeralKey", ephemeralKey.getSecret());

    responseData.put("customer", customer.getId());
    responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

    return gson.toJson(responseData);
});

Node.js


const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
      Enabled: stripe.Bool(true),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
      Enabled = true,
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

Listing payment methods manually

You can fork and deploy an implementation of this endpoint on CodeSandbox for testing.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \
  -d "payment_method_types[]"="bancontact" \
  -d "payment_method_types[]"="card" \
  -d "payment_method_types[]"="ideal" \
  -d "payment_method_types[]"="klarna" \
  -d "payment_method_types[]"="sepa_debit" \

Ruby

# This example sets up an endpoint using the Sinatra framework.



post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],
    payment_method_types: ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.


@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],
    payment_method_types=["bancontact", "card", "ideal", "klarna", "sepa_debit"],
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,
  'payment_method_types' => ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java

// This example sets up an endpoint using the Spark framework.

post("/payment-sheet", (request, response) -> {
  response.type("application/json");

  // Use an existing Customer ID if this is a returning customer.
  CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
  Customer customer = Customer.create(customerParams);
  EphemeralKeyCreateParams ephemeralKeyParams =
    EphemeralKeyCreateParams.builder()
      .setStripeVersion("2025-09-30.clover")
      .setCustomer(customer.getId())
      .build();

  EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);

  List<String> paymentMethodTypes = new ArrayList<String>();
  paymentMethodTypes.add("bancontact");
  paymentMethodTypes.add("card");
  paymentMethodTypes.add("ideal");
  paymentMethodTypes.add("klarna");
  paymentMethodTypes.add("sepa_debit");

  PaymentIntentCreateParams paymentIntentParams =
  PaymentIntentCreateParams.builder()
    .setAmount(1099L)
    .setCurrency("eur")
    .setCustomer(customer.getId())
    .addAllPaymentMethodType(paymentMethodTypes)
    .build();

  PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

  Map<String, String> responseData = new HashMap();
  responseData.put("paymentIntent", paymentIntent.getClientSecret());
  responseData.put("ephemeralKey", ephemeralKey.getSecret());
  responseData.put("customer", customer.getId());
  responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

  return gson.toJson(responseData);
});

Node.js


// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,
    payment_method_types: ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),
    PaymentMethodTypes: []*string{
      stripe.String("bancontact"),
      stripe.String("card"),
      stripe.String("ideal"),
      stripe.String("klarna"),
      stripe.String("sepa_debit"),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,
    PaymentMethodTypes = new List<string>
    {
      "bancontact",
      "card",
      "ideal",
      "klarna",
      "sepa_debit",
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

Each payment method needs to support the currency passed in the PaymentIntent and your business needs to be based in one of the countries each payment method supports. See the Payment method integration options page for more details about whats supported.

Collect payment details [Client-side]

To display the mobile Payment Element on your checkout screen, make sure you:

  • Display the products the customer is purchasing along with the total amount
  • Use the Address Element to collect any required shipping information from the customer
  • Add a checkout button to display Stripes UI

UIKit

In your apps checkout screen, fetch the PaymentIntent client secret, Ephemeral Key secret, Customer ID, and publishable key from the endpoint you created in the previous step. Set your publishable key using StripeAPI.shared and initialize PaymentSheet.

iOS (Swift)

import UIKit
import StripePaymentSheet

class CheckoutViewController: UIViewController {
  @IBOutlet weak var checkoutButton: UIButton!
  var paymentSheet: PaymentSheet?
  let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint

  override func viewDidLoad() {
    super.viewDidLoad()

    checkoutButton.addTarget(self, action: #selector(didTapCheckoutButton), for: .touchUpInside)
    checkoutButton.isEnabled = false

    // MARK: Fetch the PaymentIntent client secret, Ephemeral Key secret, Customer ID, and publishable key
    var request = URLRequest(url: backendCheckoutUrl)
    request.httpMethod = "POST"
    let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
      guard let data = data,
            let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
            let customerId = json["customer"] as? String,
            let customerEphemeralKeySecret = json["ephemeralKey"] as? String,
            let paymentIntentClientSecret = json["paymentIntent"] as? String,
            let publishableKey = json["publishableKey"] as? String,
            let self = self else {
        // Handle error
        return
      }
STPAPIClient.shared.publishableKey = publishableKey// MARK: Create a PaymentSheet instance
      var configuration = PaymentSheet.Configuration()
      configuration.merchantDisplayName = "Example, Inc."
      configuration.customer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
      // Set `allowsDelayedPaymentMethods` to true if your business handles
      // delayed notification payment methods like US bank accounts.
      configuration.allowsDelayedPaymentMethods = true
      self.paymentSheet = PaymentSheet(paymentIntentClientSecret:paymentIntentClientSecret, configuration: configuration)

      DispatchQueue.main.async {
        self.checkoutButton.isEnabled = true
      }
    })
    task.resume()
  }

}

When the customer taps the Checkout button, call present to present the PaymentSheet. After the customer completes the payment, Stripe dismisses the PaymentSheet and calls the completion block with PaymentSheetResult.

iOS (Swift)

@objc
func didTapCheckoutButton() {
  // MARK: Start the checkout process
  paymentSheet?.present(from: self) { paymentResult in
    // MARK: Handle the payment result
    switch paymentResult {
    case .completed:
      print("Your order is confirmed")
    case .canceled:
      print("Canceled!")
    case .failed(let error):
      print("Payment failed: \(error)")
    }
  }
}

SwiftUI

Create an ObservableObject model for your checkout screen. This model publishes a PaymentSheet and a PaymentSheetResult.

import StripePaymentSheet
import SwiftUI

class CheckoutViewModel: ObservableObject {
  let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint
  @Published var paymentSheet: PaymentSheet?
  @Published var paymentResult: PaymentSheetResult?
}

In your apps checkout screen, fetch the PaymentIntent client secret, Ephemeral Key secret, Customer ID, and publishable key from the endpoint you created in the previous step. Use StripeAPI.shared to set your publishable key and initialize the PaymentSheet.

import StripePaymentSheet
import SwiftUI

class CheckoutViewModel: ObservableObject {
  let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint
  @Published var paymentSheet: PaymentSheet?
  @Published var paymentResult: PaymentSheetResult?
func preparePaymentSheet() {
    // MARK: Fetch thePaymentIntent and Customer information from the backend
    var request = URLRequest(url: backendCheckoutUrl)
    request.httpMethod = "POST"
    let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
      guard let data = data,
            let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
            let customerId = json["customer"] as? String,
            let customerEphemeralKeySecret = json["ephemeralKey"] as? String,
            letpaymentIntentClientSecret = json["paymentIntent"] as? String,
            let publishableKey = json["publishableKey"] as? String,
            let self = self else {
        // Handle error
        return
      }

      STPAPIClient.shared.publishableKey = publishableKey// MARK: Create a PaymentSheet instance
      var configuration = PaymentSheet.Configuration()
      configuration.merchantDisplayName = "Example, Inc."
      configuration.customer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
      // Set `allowsDelayedPaymentMethods` to true if your business handles
      // delayed notification payment methods like US bank accounts.
      configuration.allowsDelayedPaymentMethods = true

      DispatchQueue.main.async {
        self.paymentSheet = PaymentSheet(paymentIntentClientSecret:paymentIntentClientSecret, configuration: configuration)
      }
    })
    task.resume()
  }
}
struct CheckoutView: View {
  @ObservedObject var model = CheckoutViewModel()

  var body: some View {
    VStack {
      if model.paymentSheet != nil {
        Text("Ready to pay.")
      } else {
        Text("Loading…")
      }
    }.onAppear { model.preparePaymentSheet() }
  }
}

Add a PaymentSheet.PaymentButton to your View. This behaves similarly to a SwiftUI Button, which allows you to customize it by adding a View. When you click the button, it displays the PaymentSheet. After you complete the payment, Stripe dismisses the PaymentSheet and calls the onCompletion handler with a PaymentSheetResult object.

import StripePaymentSheet
import SwiftUI

class CheckoutViewModel: ObservableObject {
  let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint
  @Published var paymentSheet: PaymentSheet?
  @Published var paymentResult: PaymentSheetResult?

  func preparePaymentSheet() {
    // MARK: Fetch the PaymentIntent and Customer information from the backend
    var request = URLRequest(url: backendCheckoutUrl)
    request.httpMethod = "POST"
    let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
      guard let data = data,
            let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
            let customerId = json["customer"] as? String,
            let customerEphemeralKeySecret = json["ephemeralKey"] as? String,
            let paymentIntentClientSecret = json["paymentIntent"] as? String,
            let publishableKey = json["publishableKey"] as? String,
            let self = self else {
        // Handle error
        return
      }

      STPAPIClient.shared.publishableKey = publishableKey
      // MARK: Create a PaymentSheet instance
      var configuration = PaymentSheet.Configuration()
      configuration.merchantDisplayName = "Example, Inc."
      configuration.customer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
      // Set `allowsDelayedPaymentMethods` to true if your business can handle payment methods
      // that complete payment after a delay, like SEPA Debit and Sofort.
      configuration.allowsDelayedPaymentMethods = true

      DispatchQueue.main.async {
        self.paymentSheet = PaymentSheet(paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration)
      }
    })
    task.resume()
  }
func onPaymentCompletion(result: PaymentSheetResult) {
    self.paymentResult = result
  }
}

struct CheckoutView: View {
  @ObservedObject var model = CheckoutViewModel()

  var body: some View {
    VStack {if let paymentSheet = model.paymentSheet {
        PaymentSheet.PaymentButton(
          paymentSheet: paymentSheet,
          onCompletion: model.onPaymentCompletion
        ) {
          Text("Buy")
        }
      } else {
        Text("Loading…")
      }if let result = model.paymentResult {
        switch result {
        case .completed:
          Text("Payment complete")
        case .failed(let error):
          Text("Payment failed: \(error.localizedDescription)")
        case .canceled:
          Text("Payment canceled.")
        }
      }
    }.onAppear { model.preparePaymentSheet() }
  }
}

If PaymentSheetResult is .completed, inform the user (for example, by displaying an order confirmation screen).

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isnt known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

Set up a return URL [Client-side]

The customer might navigate away from your app to authenticate (for example, in Safari or their banking app). To allow them to automatically return to your app after authenticating, configure a custom URL scheme and set up your app delegate to forward the URL to the SDK. Stripe doesnt support universal links.

SceneDelegate

Swift

// This method handles opening custom URL schemes (for example, "your-app://stripe-redirect")
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else {
        return
    }
    let stripeHandled = StripeAPI.handleURLCallback(with: url)
    if (!stripeHandled) {
        // This was not a Stripe url  handle the URL normally as you would
    }
}

AppDelegate

Swift

// This method handles opening custom URL schemes (for example, "your-app://stripe-redirect")
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    let stripeHandled = StripeAPI.handleURLCallback(with: url)
    if (stripeHandled) {
        return true
    } else {
        // This was not a Stripe url  handle the URL normally as you would
    }
    return false
}

SwiftUI

Swift


@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      Text("Hello, world!").onOpenURL { incomingURL in
          let stripeHandled = StripeAPI.handleURLCallback(with: incomingURL)
          if (!stripeHandled) {
            // This was not a Stripe url  handle the URL normally as you would
          }
        }
    }
  }
}

Additionally, set the returnURL on your PaymentSheet.Configuration object to the URL for your app.

var configuration = PaymentSheet.Configuration()
configuration.returnURL = "your-app://stripe-redirect"

Handle post-payment events [Server-side]

Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard webhook tool or follow the webhook guide to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept different types of payment methods with a single integration.

In addition to handling the payment_intent.succeeded event, we recommend handling these other events when collecting payments with the Payment Element:

Event Description Action
payment_intent.succeeded Sent when a customer successfully completes a payment. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
payment_intent.processing Sent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. Its followed by either a payment_intent.succeeded or payment_intent.payment_failed event in the future. Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete.
payment_intent.payment_failed Sent when a customer attempts a payment, but the payment fails. If a payment transitions from processing to payment_failed, offer the customer another attempt to pay.

Test the integration

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Bank redirects

Payment method Scenario How to test
Bancontact, iDEAL Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

See Testing for additional information to test your integration.

Enable Link in your Payment Method settings to allow your customers to securely save and reuse their payment information using Links one-click express checkout button.

Pass your customers email to the Mobile Payment Element

Link authenticates a customer using their email address. Stripe recommends prefilling as much information as possible to streamline the checkout process.

To prefill the customers name, email address, and phone number, supply defaultBillingDetails with your customer information after initializing PaymentSheet.Configuration.

var configuration = PaymentSheet.Configuration()
configuration.defaultBillingDetails.name = "Jenny Rosen"
configuration.defaultBillingDetails.email = "jenny.rosen@example.com"
configuration.defaultBillingDetails.phone = "888-888-8888"

Optional: Enable Apple Pay

If your checkout screen has a dedicated Apple Pay button, follow the Apple Pay guide and use ApplePayContext to collect payment from your Apple Pay button. You can use PaymentSheet to handle other payment method types.

Register for an Apple Merchant ID

Obtain an Apple Merchant ID by registering for a new identifier on the Apple Developer website.

Fill out the form with a description and identifier. Your description is for your own records and you can modify it in the future. Stripe recommends using the name of your app as the identifier (for example, merchant.com.{{YOUR_APP_NAME}}).

Create a new Apple Pay certificate

Create a certificate for your app to encrypt payment data.

Go to the iOS Certificate Settings in the Dashboard, click Add new application, and follow the guide.

Download a Certificate Signing Request (CSR) file to get a secure certificate from Apple that allows you to use Apple Pay.

One CSR file must be used to issue exactly one certificate. If you switch your Apple Merchant ID, you must go to the iOS Certificate Settings in the Dashboard to obtain a new CSR and certificate.

Integrate with Xcode

Add the Apple Pay capability to your app. In Xcode, open your project settings, click the Signing & Capabilities tab, and add the Apple Pay capability. You might be prompted to log in to your developer account at this point. Select the merchant ID you created earlier, and your app is ready to accept Apple Pay.

Enable the Apple Pay capability in Xcode

Add Apple Pay

One-time payment

To add Apple Pay to PaymentSheet, set applePay after initializing PaymentSheet.Configuration with your Apple merchant ID and the country code of your business.

iOS (Swift)

var configuration = PaymentSheet.Configuration()
configuration.applePay = .init(
  merchantId: "merchant.com.your_app_name",
  merchantCountryCode: "US"
)

Recurring payments

To add Apple Pay to PaymentSheet, set applePay after initializing PaymentSheet.Configuration with your Apple merchant ID and the country code of your business.

Per Apples guidelines for recurring payments, you must also set additional attributes on the PKPaymentRequest. Add a handler in ApplePayConfiguration.paymentRequestHandlers to configure the PKPaymentRequest.paymentSummaryItems with the amount you intend to charge (for example, 9.95 USD a month).

You can also adopt merchant tokens by setting the recurringPaymentRequest or automaticReloadPaymentRequest properties on the PKPaymentRequest.

To learn more about how to use recurring payments with Apple Pay, see Apples PassKit documentation.

iOS (Swift)

let customHandlers = PaymentSheet.ApplePayConfiguration.Handlers(
    paymentRequestHandler: { request in
        // PKRecurringPaymentSummaryItem is available on iOS 15 or later
        if #available(iOS 15.0, *) {
            let billing = PKRecurringPaymentSummaryItem(label: "My Subscription", amount: NSDecimalNumber(string: "59.99"))

            // Payment starts today
            billing.startDate = Date()

            // Payment ends in one year
            billing.endDate = Date().addingTimeInterval(60 * 60 * 24 * 365)

            // Pay once a month.
            billing.intervalUnit = .month
            billing.intervalCount = 1

            // recurringPaymentRequest is only available on iOS 16 or later
            if #available(iOS 16.0, *) {
                request.recurringPaymentRequest = PKRecurringPaymentRequest(paymentDescription: "Recurring",
                                                                            regularBilling: billing,
                                                                            managementURL: URL(string: "https://my-backend.example.com/customer-portal")!)
                request.recurringPaymentRequest?.billingAgreement = "You'll be billed $59.99 every month for the next 12 months. To cancel at any time, go to Account and click 'Cancel Membership.'"
            }
            request.paymentSummaryItems = [billing]
            request.currencyCode = "USD"
        } else {
            // On older iOS versions, set alternative summary items.
            request.paymentSummaryItems = [PKPaymentSummaryItem(label: "Monthly plan starting July 1, 2022", amount: NSDecimalNumber(string: "59.99"), type: .final)]
        }
        return request
    }
)
var configuration = PaymentSheet.Configuration()
configuration.applePay = .init(merchantId: "merchant.com.your_app_name",
                                merchantCountryCode: "US",
                                customHandlers: customHandlers)

Order tracking

To add order tracking information in iOS 16 or later, configure an authorizationResultHandler in your PaymentSheet.ApplePayConfiguration.Handlers. Stripe calls your implementation after the payment is complete, but before iOS dismisses the Apple Pay sheet.

In your authorizationResultHandler implementation, fetch the order details from your server for the completed order. Add the details to the provided PKPaymentAuthorizationResult and call the provided completion handler.

To learn more about order tracking, see Apples Wallet Orders documentation.

iOS (Swift)

let customHandlers = PaymentSheet.ApplePayConfiguration.Handlers(
    authorizationResultHandler: { result, completion in
        // Fetch the order details from your service
        MyAPIClient.shared.fetchOrderDetails(orderID: orderID) { myOrderDetails
            result.orderDetails = PKPaymentOrderDetails(
                orderTypeIdentifier: myOrderDetails.orderTypeIdentifier, // "com.myapp.order"
                orderIdentifier: myOrderDetails.orderIdentifier, // "ABC123-AAAA-1111"
                webServiceURL: myOrderDetails.webServiceURL, // "https://my-backend.example.com/apple-order-tracking-backend"
                authenticationToken: myOrderDetails.authenticationToken) // "abc123"
            // Call the completion block on the main queue with your modified PKPaymentAuthorizationResult
            completion(result)
        }
    }
)
var configuration = PaymentSheet.Configuration()
configuration.applePay = .init(merchantId: "merchant.com.your_app_name",
                               merchantCountryCode: "US",
                               customHandlers: customHandlers)

Optional: Enable card scanning

To enable card scanning support, set the NSCameraUsageDescription (Privacy - Camera Usage Description) in the Info.plist of your application, and provide a reason for accessing the camera (for example, “To scan cards”). Devices with iOS 13 or higher support card scanning.

Optional: Enable ACH payments

To enable ACH debit payments include StripeFinancialConnections as a dependency for your app.

The Stripe iOS SDK is open source, fully documented, and compatible with apps supporting iOS 13 or above.

Swift Package Manager

To install the SDK, follow these steps:

  1. In Xcode, select File > Add Package Dependencies… and enter https://github.com/stripe/stripe-ios-spm as the repository URL.
  2. Select the latest version number from our releases page.
  3. Add the StripeFinancialConnections product to the target of your app.

CocoaPods

  1. If you havent already, install the latest version of CocoaPods.
  2. If you dont have an existing Podfile, run the following command to create one:
    pod init
    
  3. Add this line to your Podfile:
    pod 'StripeFinancialConnections'
    
  4. Run the following command:
    pod install
    
  5. Dont forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.
  6. In the future, to update to the latest version of the SDK, run:
    pod update StripeFinancialConnections
    

Carthage

  1. If you havent already, install the latest version of Carthage.
  2. Add this line to your Cartfile:
    github "stripe/stripe-ios"
    
  3. Follow the Carthage installation instructions. Make sure to embed all of the required frameworks listed here.
  4. In the future, to update to the latest version of the SDK, run the following command:
    carthage update stripe-ios --platform ios
    

Manual Framework

  1. Head to our GitHub releases page and download and unzip Stripe.xcframework.zip.
  2. Drag StripeFinancialConnections.xcframework to the Embedded Binaries section of the General settings in your Xcode project. Make sure to select Copy items if needed.
  3. Repeat step 2 for all required frameworks listed here.
  4. In the future, to update to the latest version of our SDK, repeat steps 13.

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Optional: Customize the sheet

All customization is configured through the PaymentSheet.Configuration object.

Appearance

Customize colors, fonts, and so on to match the look and feel of your app by using the appearance API.

Payment method layout

Configure the layout of payment methods in the sheet using paymentMethodLayout. You can display them horizontally, vertically, or let Stripe optimize the layout automatically.

Swift

var configuration = PaymentSheet.Configuration()
configuration.paymentMethodLayout = .automatic

Collect users addresses

Collect local and international shipping or billing addresses from your customers using the Address Element.

Merchant display name

Specify a customer-facing business name by setting merchantDisplayName. By default, this is your apps name.

Swift

var configuration = PaymentSheet.Configuration()
configuration.merchantDisplayName = "My app, Inc."

Dark mode

PaymentSheet automatically adapts to the users system-wide appearance settings (light and dark mode). If your app doesnt support dark mode, you can set style to alwaysLight or alwaysDark mode.

var configuration = PaymentSheet.Configuration()
configuration.style = .alwaysLight

Default billing details

To set default values for billing details collected in the payment sheet, configure the defaultBillingDetails property. The PaymentSheet pre-populates its fields with the values that you provide.

var configuration = PaymentSheet.Configuration()
configuration.defaultBillingDetails.address.country = "US"
configuration.defaultBillingDetails.email = "foo@bar.com"

Billing details collection

Use billingDetailsCollectionConfiguration to specify how you want to collect billing details in the payment sheet.

You can collect your customers name, email, phone number, and address.

If you only want to billing details required by the payment method, set billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod to true. In that case, the PaymentSheet.Configuration.defaultBillingDetails are set as the payment methods billing details.

If you want to collect additional billing details that arent necessarily required by the payment method, set billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod to false. In that case, the billing details collected through the PaymentSheet are set as the payment methods billing details.

var configuration = PaymentSheet.Configuration()
configuration.defaultBillingDetails.email = "foo@bar.com"
configuration.billingDetailsCollectionConfiguration.name = .always
configuration.billingDetailsCollectionConfiguration.email = .never
configuration.billingDetailsCollectionConfiguration.address = .full
configuration.billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod = true

Consult with your legal counsel regarding laws that apply to collecting information. Only collect phone numbers if you need them for the transaction.

Optional: Handle user logout

PaymentSheet stores some information locally to remember whether a user has used Link within an app. To clear the internal state of PaymentSheet, call the PaymentSheet.resetCustomer() method when your user logs out.

import UIKit
import StripePaymentSheet

class MyViewController: UIViewController {

  @objc
  func didTapLogoutButton() {
    PaymentSheet.resetCustomer()
    // Other logout logic required by your app
  }

}

Optional: Complete payment in your UI

You can present the Payment Sheet to only collect payment method details and then later call a confirm method to complete payment in your apps UI. This is useful if you have a custom buy button or require additional steps after you collect payment details.

Complete the payment in your apps UI

UIKit

The following steps walk you through how to complete payment in your apps UI. See our sample integration out on GitHub.

  1. First, initialize PaymentSheet.FlowController instead of PaymentSheet and update your UI with its paymentOption property. This property contains an image and label representing the customers initially selected, default payment method.
PaymentSheet.FlowController.create(paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration) { [weak self] result in
  switch result {
  case .failure(let error):
    print(error)
  case .success(let paymentSheetFlowController):
    self?.paymentSheetFlowController = paymentSheetFlowController
    // Update your UI using paymentSheetFlowController.paymentOption
  }
}
  1. Next, call presentPaymentOptions to collect payment details. When completed, update your UI again with the paymentOption property.
paymentSheetFlowController.presentPaymentOptions(from: self) {
  // Update your UI using paymentSheetFlowController.paymentOption
}
  1. Finally, call confirm.
paymentSheetFlowController.confirm(from: self) { paymentResult in
  // MARK: Handle the payment result
  switch paymentResult {
  case .completed:
    print("Payment complete!")
  case .canceled:
    print("Canceled!")
  case .failed(let error):
    print(error)
  }
}

SwiftUI

The following steps walk you through how to complete payment in your apps UI. See our sample integration out on GitHub.

  1. First, initialize PaymentSheet.FlowController instead of PaymentSheet. Its paymentOption property contains an image and label representing the customers currently selected payment method, which you can use in your UI.
PaymentSheet.FlowController.create(paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration) { [weak self] result in
  switch result {
  case .failure(let error):
    print(error)
  case .success(let paymentSheetFlowController):
    self?.paymentSheetFlowController = paymentSheetFlowController
    // Use the paymentSheetFlowController.paymentOption properties in your UI
    myPaymentMethodLabel = paymentSheetFlowController.paymentOption?.label ?? "Select a payment method"
    myPaymentMethodImage = paymentSheetFlowController.paymentOption?.image ?? UIImage(systemName: "square.and.pencil")!
  }
}
  1. Use PaymentSheet.FlowController.PaymentOptionsButton to wrap the button that presents the sheet to collect payment details. When PaymentSheet.FlowController calls the onSheetDismissed argument, the paymentOption for the PaymentSheet.FlowController instance reflects the currently selected payment method.
PaymentSheet.FlowController.PaymentOptionsButton(
  paymentSheetFlowController: paymentSheetFlowController,
  onSheetDismissed: {
    myPaymentMethodLabel = paymentSheetFlowController.paymentOption?.label ?? "Select a payment method"
    myPaymentMethodImage = paymentSheetFlowController.paymentOption?.image ?? UIImage(systemName: "square.and.pencil")!
  },
  content: {
    /* An example button */
    HStack {
      Text(myPaymentMethodLabel)
      Image(uiImage: myPaymentMethodImage)
    }
  }
)
  1. Use PaymentSheet.FlowController.PaymentOptionsButton to wrap the button that confirms the payment.
PaymentSheet.FlowController.ConfirmButton(
  paymentSheetFlowController: paymentSheetFlowController,
  onCompletion: { result in
    // MARK: Handle the payment result
    switch result {
    case .completed:
      print("Payment complete!")
    case .canceled:
      print("Canceled!")
    case .failed(let error):
      print(error)
    }
  },
  content: {
    /* An example button */
    Text("Pay")
  }
)

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isnt known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

Optional: Enable CVC recollection on confirmation

Legacy

The following instructions for re-collecting the CVC of a saved card during PaymentIntent confirmation assume that your integration includes the following:

  • Creation of PaymentIntents before collecting payment details

Update parameters of the intent creation

To re-collect the CVC when confirming payment, include require_cvc_recollection during the creation of the PaymentIntent.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -H "Stripe-Version: 2025-09-30.clover" \
  -H "Stripe-Account: 2025-09-30.clover" \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \-d "payment_method_options[card][require_cvc_recollection]"=true \
  # In the latest version of the API, specifying the `automatic_payment_methods` parameter
  # is optional because Stripe enables its functionality by default.
  -d "automatic_payment_methods[enabled]"=true \

Ruby

# This example sets up an endpoint using the Sinatra framework.


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],payment_method_options: {
      card: {require_cvc_recollection: true}
    }
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],payment_method_options={
      'card': {
        'require_cvc_recollection': True
      }
    },
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods={
      'enabled': True,
    },
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,'payment_method_options' => [
    'card' => ['require_cvc_recollection' => true]
  ],
  // In the latest version of the API, specifying the `automatic_payment_methods` parameter
  // is optional because Stripe enables its functionality by default.
  'automatic_payment_methods' => [
    'enabled' => 'true',
  ],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

post(
  "/payment-sheet",
  (request, response) -> {
    response.type("application/json");

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
    Customer customer = Customer.create(customerParams);
    
     // Set the connected account ID
    String connectedAccountId = "{{CONNECTED_ACCOUNT_ID}}";

    // Create RequestOptions with the Stripe-Account header
    RequestOptions requestOptions = RequestOptions.builder()
        .setStripeAccount(connectedAccountId)
        .build();

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();

    // Pass the requestOptions when creating the customer
    Customer customer = Customer.create(customerParams, requestOptions);
  });
    EphemeralKeyCreateParams ephemeralKeyParams =
      EphemeralKeyCreateParams.builder()
        .setStripeAccount("{{CONNECTED_ACCOUNT_ID}}")
        .setStripeVersion("2025-09-30.clover")
        .setCustomer(customer.getId())
        .build();

    EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);
    PaymentIntentCreateParams paymentIntentParams =
    PaymentIntentCreateParams.builder()
      .setAmount(1099L)
      .setCurrency("eur")
      .setCustomer(customer.getId()).setPaymentMethodOptions(
        PaymentIntentCreateParams.PaymentMethodOptions.builder()
          .setCard(
            PaymentIntentCreateParams.PaymentMethodOptions.Card.builder()
              .setRequireCvcRecollection(true)
              .build()
          )
          .build()
      )
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      .setAutomaticPaymentMethods(
        PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
          .setEnabled(true)
          .build()
      )
      .build();

    PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

    Map<String, String> responseData = new HashMap();
    responseData.put("paymentIntent", paymentIntent.getClientSecret());
    responseData.put("ephemeralKey", ephemeralKey.getSecret());

    responseData.put("customer", customer.getId());
    responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

    return gson.toJson(responseData);
});

Node.js


const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,payment_method_options: {
      card: {
        require_cvc_recollection: true,
      },
    },
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),PaymentMethodOptions: stripe.PaymentIntentPaymentMethodOptionsParams{
      Card: stripe.PaymentIntentPaymentMethodOptionsCardParams{
        RequireCvcRecollection: stripe.Bool(true),
      }
    },
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
      Enabled: stripe.Bool(true),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions
    {
        Card = new PaymentIntentPaymentMethodOptionsCardOptions
        {
            RequireCvcRecollection = true,
        },
    },
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
      Enabled = true,
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

In-app integration for Android

This is a In-app integration for Android for when platform is android. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=android.

Integrate Stripes prebuilt payment UI into the checkout of your Android app with the PaymentSheet class.

Set up Stripe [Server-side] [Client-side]

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use the official libraries for access to the Stripe API from your server:

Ruby

# Available as a gem
sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Python

# Install through pip
pip3 install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin:
# https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md
# Specify that version in your requirements.txt file
stripe>=5.0.0

PHP

# Install the PHP library with Composer
composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:30.0.0"
<!--
  For Maven, add the following dependency to your POM and replace 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>30.0.0</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

Node.js

# Install with npm
npm install stripe --save

Go

# Make sure your project is using Go Modules
go mod init
# Install stripe-go
go get -u github.com/stripe/stripe-go/v83
// Then import the package
import (
  "github.com/stripe/stripe-go/v83"
)

.NET

# Install with dotnet
dotnet add package Stripe.net
dotnet restore
# Or install with NuGet
Install-Package Stripe.net

Client-side

The Stripe Android SDK is open source and fully documented.

To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

Kotlin

plugins {
    id("com.android.application")
}

android { ... }

dependencies {
  // ...

  // Stripe Android SDK
  implementation("com.stripe:stripe-android:21.29.1")
  // Include the financial connections SDK to support US bank account as a payment method
  implementation("com.stripe:financial-connections:21.29.1")
}

Groovy

apply plugin: 'com.android.application'

android { ... }

dependencies {
  // ...

  // Stripe Android SDK
  implementation 'com.stripe:stripe-android:21.29.1'
  // Include the financial connections SDK to support US bank account as a payment method
  implementation 'com.stripe:financial-connections:21.29.1'
}

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Enable payment methods

View your payment methods settings and enable the payment methods you want to support. You need at least one payment method enabled to create a PaymentIntent (The Payment Intents API tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods).

By default, Stripe enables cards and other prevalent payment methods that can help you reach more customers, but we recommend turning on additional payment methods that are relevant for your business and customers. See Payment method support for product and payment method support, and our pricing page for fees.

Add an endpoint [Server-side]

Note

To display the PaymentSheet before you create a PaymentIntent, see Collect payment details before creating an Intent.

This integration uses three Stripe API objects:

  1. PaymentIntent: Stripe uses this to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.

  2. (Optional) Customer: To set up a payment method for future payments, you must attach it to a Customer (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments). Create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customers account later.

  3. (Optional) Customer Ephemeral Key: Information on the Customer object is sensitive, and cant be retrieved directly from an app. An Ephemeral Key grants the SDK temporary access to the Customer.

If you never save cards to a Customer and dont allow returning Customers to reuse saved cards, you can omit the Customer and Customer Ephemeral Key objects from your integration.

For security reasons, your app cant create these objects. Instead, add an endpoint on your server that:

  1. Retrieves the Customer, or creates a new one.
  2. Creates an Ephemeral Key for the Customer.
  3. Creates a PaymentIntent with the amount, currency, and customer. You can also optionally include the automatic_payment_methods parameter. Stripe enables its functionality by default in the latest version of the API.
  4. Returns the Payment Intents client secret (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)), the Ephemeral Keys secret, the Customers id, and your publishable key to your app.

The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe pull payment methods from your Dashboard settings or you can list them manually. Regardless of the option you choose, know that the currency passed in the PaymentIntent filters the payment methods shown to the customer. For example, if you pass eur on the PaymentIntent and have OXXO enabled in the Dashboard, OXXO wont be shown to the customer because OXXO doesnt support eur payments.

Unless your integration requires a code-based option for offering payment methods, Stripe recommends the automated option. This is because Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Payment methods that increase conversion and that are most relevant to the currency and customers location are prioritized.

Manage payment methods from the Dashboard

You can fork and deploy an implementation of this endpoint on CodeSandbox for testing.

You can manage payment methods from the Dashboard. Stripe handles the return of eligible payment methods based on factors such as the transactions amount, currency, and payment flow. The PaymentIntent is created using the payment methods you configured in the Dashboard. If you dont want to use the Dashboard or if you want to specify payment methods manually, you can list them using the payment_method_types attribute.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -H "Stripe-Version: 2025-09-30.clover" \
  -H "Stripe-Account: 2025-09-30.clover" \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \
  # In the latest version of the API, specifying the `automatic_payment_methods` parameter
  # is optional because Stripe enables its functionality by default.
  -d "automatic_payment_methods[enabled]"=true \

Ruby

# This example sets up an endpoint using the Sinatra framework.


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods={
      'enabled': True,
    },
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,
  // In the latest version of the API, specifying the `automatic_payment_methods` parameter
  // is optional because Stripe enables its functionality by default.
  'automatic_payment_methods' => [
    'enabled' => 'true',
  ],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

post(
  "/payment-sheet",
  (request, response) -> {
    response.type("application/json");

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
    Customer customer = Customer.create(customerParams);
    
     // Set the connected account ID
    String connectedAccountId = "{{CONNECTED_ACCOUNT_ID}}";

    // Create RequestOptions with the Stripe-Account header
    RequestOptions requestOptions = RequestOptions.builder()
        .setStripeAccount(connectedAccountId)
        .build();

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();

    // Pass the requestOptions when creating the customer
    Customer customer = Customer.create(customerParams, requestOptions);
  });
    EphemeralKeyCreateParams ephemeralKeyParams =
      EphemeralKeyCreateParams.builder()
        .setStripeAccount("{{CONNECTED_ACCOUNT_ID}}")
        .setStripeVersion("2025-09-30.clover")
        .setCustomer(customer.getId())
        .build();

    EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);
    PaymentIntentCreateParams paymentIntentParams =
    PaymentIntentCreateParams.builder()
      .setAmount(1099L)
      .setCurrency("eur")
      .setCustomer(customer.getId())
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      .setAutomaticPaymentMethods(
        PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
          .setEnabled(true)
          .build()
      )
      .build();

    PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

    Map<String, String> responseData = new HashMap();
    responseData.put("paymentIntent", paymentIntent.getClientSecret());
    responseData.put("ephemeralKey", ephemeralKey.getSecret());

    responseData.put("customer", customer.getId());
    responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

    return gson.toJson(responseData);
});

Node.js


const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
      Enabled: stripe.Bool(true),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
      Enabled = true,
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

Listing payment methods manually

You can fork and deploy an implementation of this endpoint on CodeSandbox for testing.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \
  -d "payment_method_types[]"="bancontact" \
  -d "payment_method_types[]"="card" \
  -d "payment_method_types[]"="ideal" \
  -d "payment_method_types[]"="klarna" \
  -d "payment_method_types[]"="sepa_debit" \

Ruby

# This example sets up an endpoint using the Sinatra framework.



post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],
    payment_method_types: ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.


@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],
    payment_method_types=["bancontact", "card", "ideal", "klarna", "sepa_debit"],
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,
  'payment_method_types' => ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java

// This example sets up an endpoint using the Spark framework.

post("/payment-sheet", (request, response) -> {
  response.type("application/json");

  // Use an existing Customer ID if this is a returning customer.
  CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
  Customer customer = Customer.create(customerParams);
  EphemeralKeyCreateParams ephemeralKeyParams =
    EphemeralKeyCreateParams.builder()
      .setStripeVersion("2025-09-30.clover")
      .setCustomer(customer.getId())
      .build();

  EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);

  List<String> paymentMethodTypes = new ArrayList<String>();
  paymentMethodTypes.add("bancontact");
  paymentMethodTypes.add("card");
  paymentMethodTypes.add("ideal");
  paymentMethodTypes.add("klarna");
  paymentMethodTypes.add("sepa_debit");

  PaymentIntentCreateParams paymentIntentParams =
  PaymentIntentCreateParams.builder()
    .setAmount(1099L)
    .setCurrency("eur")
    .setCustomer(customer.getId())
    .addAllPaymentMethodType(paymentMethodTypes)
    .build();

  PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

  Map<String, String> responseData = new HashMap();
  responseData.put("paymentIntent", paymentIntent.getClientSecret());
  responseData.put("ephemeralKey", ephemeralKey.getSecret());
  responseData.put("customer", customer.getId());
  responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

  return gson.toJson(responseData);
});

Node.js


// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,
    payment_method_types: ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),
    PaymentMethodTypes: []*string{
      stripe.String("bancontact"),
      stripe.String("card"),
      stripe.String("ideal"),
      stripe.String("klarna"),
      stripe.String("sepa_debit"),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,
    PaymentMethodTypes = new List<string>
    {
      "bancontact",
      "card",
      "ideal",
      "klarna",
      "sepa_debit",
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

Each payment method needs to support the currency passed in the PaymentIntent and your business needs to be based in one of the countries each payment method supports. See the Payment method integration options page for more details about whats supported.

Collect payment details [Client-side]

Before displaying the mobile Payment Element, your checkout page should:

  • Show the products being purchased and the total amount
  • Collect any required shipping information using the Address Element
  • Include a checkout button to present Stripes UI

Jetpack Compose

Initialize a PaymentSheet instance inside onCreate of your checkout Activity, passing a method to handle the result.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

@Composable
fun App() {
  val paymentSheet = remember { PaymentSheet.Builder(::onPaymentSheetResult) }.build()
}

private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
  // implemented in the next steps
}

Next, fetch the PaymentIntent client secret, Ephemeral Key secret, Customer ID, and publishable key from the endpoint you created in the previous step. Set the publishable key using PaymentConfiguration and store the others for use when you present the PaymentSheet.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberimport androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

@Composable
fun App() {
  val paymentSheet = remember { PaymentSheet.Builder(::onPaymentSheetResult) }.build()val context = LocalContext.current
  var customerConfig by remember { mutableStateOf<PaymentSheet.CustomerConfiguration?>(null) }
  varpaymentIntentClientSecret by remember { mutableStateOf<String?>(null) }

  LaunchedEffect(context) {
    // Make a request to your own server and retrieve payment configurations
    val networkResult = ...
    if (networkResult.isSuccess) {paymentIntentClientSecret = networkResult.paymentIntentcustomerConfig = PaymentSheet.CustomerConfiguration(
          id = networkResult.customer,
          ephemeralKeySecret = networkResult.ephemeralKey
        )PaymentConfiguration.init(context, networkResult.publishableKey)}
  }
}

private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
  // implemented in the next steps
}

When the customer taps your checkout button, call presentWithPaymentIntent to present the payment sheet. After the customer completes the payment, the sheet dismisses and the PaymentSheetResultCallback is called with a PaymentSheetResult.

import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

@OptIn(ExperimentalCustomerSessionApi::class)
@Composable
fun App() {
  val paymentSheet = remember { PaymentSheet.Builder(::onPaymentSheetResult) }.build()
  val context = LocalContext.current
  var customerConfig by remember { mutableStateOf<PaymentSheet.CustomerConfiguration?>(null) }
  var paymentIntentClientSecret by remember { mutableStateOf<String?>(null) }

  LaunchedEffect(context) {
    // Make a request to your own server and retrieve payment configurations
    val networkResult = ...
    if (networkResult.isSuccess) {
        paymentIntentClientSecret = networkResult.paymentIntent
        customerConfig = PaymentSheet.CustomerConfiguration(
          id = networkResult.customer,
          ephemeralKeySecret = networkResult.ephemeralKey
        )
        PaymentConfiguration.init(context, networkResult.publishableKey)
    }
  }Button(
    onClick = {
      val currentConfig = customerConfig
      val currentClientSecret =paymentIntentClientSecret

      if (currentConfig != null && currentClientSecret != null) {
        presentPaymentSheet(paymentSheet, currentConfig, currentClientSecret)
      }
    }
  ) {
    Text("Checkout")
  }
}private fun presentPaymentSheet(
  paymentSheet: PaymentSheet,
  customerConfig: PaymentSheet.CustomerConfiguration,paymentIntentClientSecret: String
) {
  paymentSheet.presentWithPaymentIntent(paymentIntentClientSecret,
    PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name")
      .customer(customerConfig)
      // Set `allowsDelayedPaymentMethods` to true if your business handles
      // delayed notification payment methods like US bank accounts.
      .allowsDelayedPaymentMethods(true)
      .build()
  )
}
private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {when(paymentSheetResult) {
    is PaymentSheetResult.Canceled -> {
      print("Canceled")
    }
    is PaymentSheetResult.Failed -> {
      print("Error: ${paymentSheetResult.error}")
    }
    is PaymentSheetResult.Completed -> {
      // Display for example, an order confirmation screen
      print("Completed")
    }
  }
}

Views (Classic)

Initialize a PaymentSheet instance inside onCreate of your checkout Activity, passing a method to handle the result.

Kotlin

import com.stripe.android.paymentsheet.PaymentSheet

class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheet

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    paymentSheet = PaymentSheet.Builder(::onPaymentSheetResult).build(this)
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
    // implemented in the next steps
  }
}

Java

import com.stripe.android.paymentsheet.*;

class CheckoutActivity extends AppCompatActivity {
    PaymentSheet paymentSheet;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        paymentSheet = new PaymentSheet.Builder(this::onPaymentSheetResult).build(this);
    }

    void onPaymentSheetResult(final PaymentSheetResult paymentSheetResult) {
        // implemented in the next steps
    }
}

Next, fetch the PaymentIntent client secret, Ephemeral Key secret, Customer ID, and publishable key from the endpoint you created in the previous step. Set the publishable key using PaymentConfiguration and store the others for use when you present the PaymentSheet.

Kotlin


import com.stripe.android.paymentsheet.PaymentSheet

class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheetlateinit var customerConfig: PaymentSheet.CustomerConfiguration
  lateinit varpaymentIntentClientSecret: String

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    paymentSheet = PaymentSheet.Builder(::onPaymentSheetResult).build(this)lifecycleScope.launch {
      // Make a request to your own server and retrieve payment configurations
      val networkResult = MyBackend.getPaymentConfig()
      if (networkResult.isSuccess) {paymentIntentClientSecret = networkResult.paymentIntentcustomerConfig = PaymentSheet.CustomerConfiguration(
          id = networkResult.customer,
          ephemeralKeySecret = networkResult.ephemeralKey
        )PaymentConfiguration.init(context, networkResult.publishableKey)}
    }
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
    // implemented in the next steps
  }
}

Java


import com.stripe.android.paymentsheet.*;import com.stripe.android.PaymentConfiguration;

class CheckoutActivity extends AppCompatActivity {
  PaymentSheet paymentSheet;StringpaymentIntentClientSecret;
  PaymentSheet.CustomerConfiguration customerConfig;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    paymentSheet = new PaymentSheet.Builder(this::onPaymentSheetResult).build(this);
// Make async request to your own server
    MyBackend.getPaymentConfig(new NetworkCallback() {
      @Override
      public void onSuccess(NetworkResult networkResult) {
        if (networkResult.isSuccess()) {customerConfig = new PaymentSheet.CustomerConfiguration(
            networkResult.getCustomer(),
            networkResult.getEphemeralKey()
          );paymentIntentClientSecret = networkResult.getpaymentIntent();PaymentConfiguration.init(getApplicationContext(), networkResult.getPublishableKey());}
      }

      @Override
      public void onError(Exception error) {
        // Handle error
      }
    });
  }

  void onPaymentSheetResult(final PaymentSheetResult paymentSheetResult) {
    // implemented in the next steps
  }
}

When the customer taps your checkout button, call presentWithPaymentIntent to present the payment sheet. After the customer completes the payment, the sheet dismisses and the PaymentSheetResultCallback is called with a PaymentSheetResult.

Kotlin

// ...
class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheet
  lateinit var customerConfig: PaymentSheet.CustomerConfiguration
  lateinit var paymentIntentClientSecret: String
  // ...fun presentPaymentSheet() {
    paymentSheet.presentWithPaymentIntent(paymentIntentClientSecret,
      PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name")
        .customer(customerConfig)
        // Set `allowsDelayedPaymentMethods` to true if your business handles
        // delayed notification payment methods like US bank accounts.
        .allowsDelayedPaymentMethods(true)
        .build()
    )
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {when(paymentSheetResult) {
      is PaymentSheetResult.Canceled -> {
        print("Canceled")
      }
      is PaymentSheetResult.Failed -> {
        print("Error: ${paymentSheetResult.error}")
      }
      is PaymentSheetResult.Completed -> {
        // Display for example, an order confirmation screen
        print("Completed")
      }
    }
  }
}

Java

// ...
public class CheckoutActivity extends AppCompatActivity {private static final String TAG = "CheckoutActivity";
  private PaymentSheet paymentSheet;
  private String paymentClientSecret;
  PaymentSheet.CustomerConfiguration customerConfig;
  // ...private void presentPaymentSheet() {
    final PaymentSheet.Configuration configuration = new PaymentSheet.Configuration.Builder("Example, Inc.")
        .customer(customerConfig)
        // Set `allowsDelayedPaymentMethods` to true if your business handles payment methods
        // delayed notification payment methods like US bank accounts.
        .allowsDelayedPaymentMethods(true)
        .build();
    paymentSheet.presentWithPaymentIntent(paymentClientSecret,
      configuration
    );
  }

  private void onPaymentSheetResult(
    final PaymentSheetResult paymentSheetResult
  ) {if (paymentSheetResult instanceof PaymentSheetResult.Canceled) {
      Log.d(TAG, "Canceled")
    } else if (paymentSheetResult instanceof PaymentSheetResult.Failed) {
      Log.e(TAG, "Got error: ", ((PaymentSheetResult.Failed) paymentSheetResult).getError());
    } else if (paymentSheetResult instanceof PaymentSheetResult.Completed) {
      // Display for example, an order confirmation screen
      Log.d(TAG, "Completed")
    }
  }
}

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isnt known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

Handle post-payment events [Server-side]

Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard webhook tool or follow the webhook guide to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept different types of payment methods with a single integration.

In addition to handling the payment_intent.succeeded event, we recommend handling these other events when collecting payments with the Payment Element:

Event Description Action
payment_intent.succeeded Sent when a customer successfully completes a payment. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
payment_intent.processing Sent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. Its followed by either a payment_intent.succeeded or payment_intent.payment_failed event in the future. Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete.
payment_intent.payment_failed Sent when a customer attempts a payment, but the payment fails. If a payment transitions from processing to payment_failed, offer the customer another attempt to pay.

Test the integration

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Bank redirects

Payment method Scenario How to test
Bancontact, iDEAL Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

See Testing for additional information to test your integration.

Enable Link in your Payment Method settings to allow your customers to securely save and reuse their payment information using Links one-click express checkout button.

Pass your customers email to the Mobile Payment Element

Link authenticates a customer using their email address. Stripe recommends prefilling as much information as possible to streamline the checkout process.

To prefill the customers name, email address, and phone number, supply defaultBillingDetails with your customer information when initializing PaymentSheet.Configuration.

Kotlin

val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Example, Inc.")
  .defaultBillingDetails(
    PaymentSheet.BillingDetails(
        name = "Jenny Rosen",
        email = "jenny.rosen@example.com",
        phone = "888-888-8888"
    )
  )
  .build()

Java

PaymentSheet.BillingDetails billingDetails =
  new PaymentSheet.BillingDetails.Builder()
    .name("Jenny Rosen")
    .email("jenny.rosen@example.com")
    .phone("888-888-8888")
    .build();
PaymentSheet.Configuration configuration =
  new PaymentSheet.Configuration.Builder(...)
    .defaultBillingDetails(billingDetails)
    .build();

Optional: Enable Google Pay

Set up your integration

To use Google Pay, first enable the Google Pay API by adding the following to the <application> tag of your AndroidManifest.xml:

<application>
  ...
  <meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
</application>

For more details, see Google Pays Set up Google Pay API for Android.

Add Google Pay

To add Google Pay to your integration, pass a PaymentSheet.GooglePayConfiguration with your Google Pay environment (production or test) and the country code of your business when initializing PaymentSheet.Configuration.

Kotlin

val googlePayConfiguration = PaymentSheet.GooglePayConfiguration(
  environment = PaymentSheet.GooglePayConfiguration.Environment.Test,
  countryCode = "US",
  currencyCode = "USD" // Required for Setup Intents, optional for Payment Intents
)
val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name")
  .googlePay(googlePayConfiguration)
  .build()

Java


final PaymentSheet.GooglePayConfiguration googlePayConfiguration =
  new PaymentSheet.GooglePayConfiguration(
    PaymentSheet.GooglePayConfiguration.Environment.Test,
    "US"
  );

 final PaymentSheet.Configuration configuration = // ...
 configuration.setGooglePay(googlePayConfiguration);
);

Test Google Pay

Google allows you to make test payments through their Test card suite. The test suite supports using stripe test cards.

You can test Google Pay using a physical Android device. Make sure you have a device in a country where google pay is supported and log in to a Google account on your test device with a real card saved to Google Wallet.

Optional: Enable card scanning

To enable card scanning support, add stripecardscan to the dependencies block of your app/build.gradle file:

Groovy

apply plugin: 'com.android.application'

android { ... }

dependencies {
  // ...

  // StripeCardScan
  implementation 'com.stripe:stripecardscan:21.29.1'
}

Kotlin

plugins {
    id("com.android.application")
}

android { ... }

dependencies {
  // ...

  // StripeCardScan
  implementation("com.stripe:stripecardscan:21.29.1")
}

Optional: Enable ACH payments

To enable ACH debit payments include Financial Connections as a dependency for your app.

The Stripe Android SDK is open source and fully documented.

To install the SDK, add financial-connections to the dependencies block of your app/build.gradle file:

Kotlin

plugins {
    id("com.android.application")
}

android { ... }

dependencies {
  // ...

  // Financial Connections Android SDK
  implementation("com.stripe:financial-connections:21.29.1")
}

Groovy

apply plugin: 'com.android.application'

android { ... }

dependencies {
  // ...

  // Financial Connections Android SDK
  implementation 'com.stripe:financial-connections:21.29.1'
}

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Optional: Customize the sheet

All customization is configured using the PaymentSheet.Configuration object.

Appearance

Customize colors, fonts, and more to match the look and feel of your app by using the appearance API.

Payment method layout

Configure the layout of payment methods in the sheet using paymentMethodLayout. You can display them horizontally, vertically, or let Stripe optimize the layout automatically.

Kotlin

PaymentSheet.Configuration.Builder("Example, Inc.")
  .paymentMethodLayout(PaymentSheet.PaymentMethodLayout.Automatic)
  .build()

Java

new PaymentSheet.Configuration.Builder("Example, Inc.")
  .paymentMethodLayout(PaymentSheet.PaymentMethodLayout.Automatic)
  .build();

Collect users addresses

Collect local and international shipping or billing addresses from your customers using the Address Element.

Business display name

Specify a customer-facing business name by setting merchantDisplayName. By default, this is your apps name.

Kotlin

PaymentSheet.Configuration.Builder(
  merchantDisplayName = "My app, Inc."
).build()

Java

new PaymentSheet.Configuration.Builder("My app, Inc.")
  .build();

Dark mode

By default, PaymentSheet automatically adapts to the users system-wide appearance settings (light and dark mode). You can change this by setting light or dark mode on your app:

Kotlin

// force dark
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
// force light
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)

Java

// force dark
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
// force light
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);

Default billing details

To set default values for billing details collected in the payment sheet, configure the defaultBillingDetails property. The PaymentSheet pre-populates its fields with the values that you provide.

Kotlin

val address = PaymentSheet.Address(country = "US")
val billingDetails = PaymentSheet.BillingDetails(
  address = address,
  email = "foo@bar.com"
)
val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Merchant, Inc.")
  .defaultBillingDetails(billingDetails)
  .build()

Java

PaymentSheet.Address address =
  new PaymentSheet.Address.Builder()
    .country("US")
    .build();
PaymentSheet.BillingDetails billingDetails =
  new PaymentSheet.BillingDetails.Builder()
    .address(address)
    .email("foo@bar.com")
    .build();
PaymentSheet.Configuration configuration =
  new PaymentSheet.Configuration.Builder("Merchant, Inc.")
    .defaultBillingDetails(billingDetails)
    .build();

Configure collection of billing details

Use BillingDetailsCollectionConfiguration to specify how you want to collect billing details in the PaymentSheet.

You can collect your customers name, email, phone number, and address.

If you want to attach default billing details to the PaymentMethod object even when those fields arent collected in the UI, set billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod to true.

Kotlin

val billingDetails = PaymentSheet.BillingDetails(
  email = "foo@bar.com"
)
val billingDetailsCollectionConfiguration = BillingDetailsCollectionConfiguration(
  attachDefaultsToPaymentMethod = true,
  name = BillingDetailsCollectionConfiguration.CollectionMode.Always,
  email = BillingDetailsCollectionConfiguration.CollectionMode.Never,
  address = BillingDetailsCollectionConfiguration.AddressCollectionMode.Full,
)
val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Merchant, Inc.")
  .defaultBillingDetails(billingDetails)
  .billingDetailsCollectionConfiguration(billingDetailsCollectionConfiguration)
  .build()

Java

PaymentSheet.BillingDetails billingDetails =
  new PaymentSheet.BillingDetails.Builder()
    .email("foo@bar.com")
    .build();
BillingDetailsCollectionConfiguration billingDetailsCollectionConfiguration = new BillingDetailsCollectionConfiguration(
  /* name */ BillingDetailsCollectionConfiguration.CollectionMode.Always,
  /* email */ BillingDetailsCollectionConfiguration.CollectionMode.Never,
  /* phone */ BillingDetailsCollectionConfiguration.CollectionMode.Automatic,
  /* address */ BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic,
  /* attachDefaultsToPaymentMethod */ true
)
PaymentSheet.Configuration configuration =
  new PaymentSheet.Configuration.Builder("Merchant, Inc.")
    .defaultBillingDetails(billingDetails)
    .billingDetailsCollectionConfiguration(billingDetailsCollectionConfiguration)
    .build();

Consult with your legal counsel regarding laws that apply to collecting information. Only collect phone numbers if you need them for the transaction.

Optional: Handle user logout

PaymentSheet stores some information locally to remember whether a user has used Link within an app. To clear the internal state of PaymentSheet, call the PaymentSheet.resetCustomer() method when your user logs out.

Kotlin

class MyActivity: Activity {

  fun onLogoutButtonClicked() {
    PaymentSheet.resetCustomer(this)
    // Other logout logic required by your app
  }
}

Java

import com.stripe.android.paymentsheet.*;

class MyActivity extends Activity {

  void onLogoutButtonClicked() {
    PaymentSheet.resetCustomer(this)
    // Other logout logic required by your app
  }
}

Optional: Complete payment in your UI

You can present Payment Sheet to only collect payment method details and complete the payment back in your apps UI. This is useful if you have a custom buy button or require additional steps after payment details are collected.

A sample integration is available on our GitHub.

  1. First, initialize PaymentSheet.FlowController instead of PaymentSheet using one of the Builder methods.

Android (Kotlin)

class CheckoutActivity : AppCompatActivity() {
  private lateinit var flowController: PaymentSheet.FlowController

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    flowController = PaymentSheet.FlowController.Builder(
      paymentResultCallback = ::onPaymentSheetResult,
      paymentOptionCallback = ::onPaymentOption,
    ).build(this)
  }
}

Android (Java)

public class CheckoutActivity extends AppCompatActivity {
  private PaymentSheet.FlowController flowController;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final PaymentOptionCallback paymentOptionCallback = paymentOption -> {
      onPaymentOption(paymentOption);
    };

    final PaymentSheetResultCallback paymentSheetResultCallback = paymentSheetResult -> {
      onPaymentSheetResult(paymentSheetResult);
    };

    flowController = new PaymentSheet.FlowController.Builder(
      paymentSheetResultCallback,
      paymentOptionCallback
    ).build(this);
  }
}
  1. Next, call configureWithPaymentIntent with the Stripe object keys fetched from your backend and update your UI in the callback using getPaymentOption(). This contains an image and label representing the customers currently selected payment method.

Android (Kotlin)

flowController.configureWithPaymentIntent(
  paymentIntentClientSecret = paymentIntentClientSecret,
  configuration = PaymentSheet.Configuration.Builder("Example, Inc.")
    .customer(PaymentSheet.CustomerConfiguration(
      id = customerId,
      ephemeralKeySecret = ephemeralKeySecret
    ))
    .build()
) { isReady, error ->
  if (isReady) {
    // Update your UI using `flowController.getPaymentOption()`
  } else {
    // handle FlowController configuration failure
  }
}

Android (Java)

flowController.configureWithPaymentIntent(
  paymentIntentClientSecret,
  new PaymentSheet.Configuration.Builder("Example, Inc.")
    .customer(new PaymentSheet.CustomerConfiguration(
      customerId,
      ephemeralKeySecret
    ))
    .build(),
  (success, error) -> {
    if (success) {
      // Update your UI using `flowController.getPaymentOption()`
    } else {
      // handle FlowController configuration failure
    }
  }
);
  1. Next, call presentPaymentOptions to collect payment details. When the customer finishes, the sheet is dismissed and calls the paymentOptionCallback passed earlier in create. Implement this method to update your UI with the returned paymentOption.

Android (Kotlin)

// ...
  flowController.presentPaymentOptions()
// ...
  private fun onPaymentOption(paymentOption: PaymentOption?) {
    if (paymentOption != null) {
      paymentMethodButton.text = paymentOption.label
      paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
        paymentOption.drawableResourceId,
        0,
        0,
        0
      )
    } else {
      paymentMethodButton.text = "Select"
      paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
        null,
        null,
        null,
        null
      )
    }
  }

Android (Java)

// ...
    flowController.presentPaymentOptions());
// ...
  private void onPaymentOption(
    @Nullable PaymentOption paymentOption
  ) {
    if (paymentOption != null) {
      paymentMethodButton.setText(paymentOption.getLabel());
      paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
        paymentOption.getDrawableResourceId(),
        0,
        0,
        0
      );
    } else {
      paymentMethodButton.setText("Select");
      paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
        null,
        null,
        null,
        null
      );
    }
  }

  private void onCheckout() {
    // see below
  }
}
  1. Finally, call confirm to complete the payment. When the customer finishes, the sheet is dismissed and calls the paymentResultCallback passed earlier in create.

Android (Kotlin)

  // ...
    flowController.confirmPayment()
  // ...

  private fun onPaymentSheetResult(
    paymentSheetResult: PaymentSheetResult
  ) {
    when (paymentSheetResult) {
      is PaymentSheetResult.Canceled -> {
        // Payment canceled
      }
      is PaymentSheetResult.Failed -> {
        // Payment Failed. See logcat for details or inspect paymentSheetResult.error
      }
      is PaymentSheetResult.Completed -> {
        // Payment Complete
      }
    }
  }

Android (Java)

  // ...
    flowController.confirmPayment();
  // ...

  private void onPaymentSheetResult(
    final PaymentSheetResult paymentSheetResult
  ) {
    if (paymentSheetResult instanceof PaymentSheetResult.Canceled) {
      // Payment Canceled
    } else if (paymentSheetResult instanceof PaymentSheetResult.Failed) {
      // Payment Failed. See logcat for details or inspect paymentSheetResult.getError()
    } else if (paymentSheetResult instanceof PaymentSheetResult.Completed) {
      // Payment Complete
    }
  }

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isnt known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

Optional: Enable CVC recollection on confirmation

Legacy

The following instructions for re-collecting the CVC of a saved card during PaymentIntent confirmation assume that your integration includes the following:

  • Creation of PaymentIntents before collecting payment details

Update parameters of the intent creation

To re-collect the CVC when confirming payment, include require_cvc_recollection during the creation of the PaymentIntent.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -H "Stripe-Version: 2025-09-30.clover" \
  -H "Stripe-Account: 2025-09-30.clover" \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \-d "payment_method_options[card][require_cvc_recollection]"=true \
  # In the latest version of the API, specifying the `automatic_payment_methods` parameter
  # is optional because Stripe enables its functionality by default.
  -d "automatic_payment_methods[enabled]"=true \

Ruby

# This example sets up an endpoint using the Sinatra framework.


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],payment_method_options: {
      card: {require_cvc_recollection: true}
    }
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],payment_method_options={
      'card': {
        'require_cvc_recollection': True
      }
    },
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods={
      'enabled': True,
    },
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,'payment_method_options' => [
    'card' => ['require_cvc_recollection' => true]
  ],
  // In the latest version of the API, specifying the `automatic_payment_methods` parameter
  // is optional because Stripe enables its functionality by default.
  'automatic_payment_methods' => [
    'enabled' => 'true',
  ],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

post(
  "/payment-sheet",
  (request, response) -> {
    response.type("application/json");

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
    Customer customer = Customer.create(customerParams);
    
     // Set the connected account ID
    String connectedAccountId = "{{CONNECTED_ACCOUNT_ID}}";

    // Create RequestOptions with the Stripe-Account header
    RequestOptions requestOptions = RequestOptions.builder()
        .setStripeAccount(connectedAccountId)
        .build();

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();

    // Pass the requestOptions when creating the customer
    Customer customer = Customer.create(customerParams, requestOptions);
  });
    EphemeralKeyCreateParams ephemeralKeyParams =
      EphemeralKeyCreateParams.builder()
        .setStripeAccount("{{CONNECTED_ACCOUNT_ID}}")
        .setStripeVersion("2025-09-30.clover")
        .setCustomer(customer.getId())
        .build();

    EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);
    PaymentIntentCreateParams paymentIntentParams =
    PaymentIntentCreateParams.builder()
      .setAmount(1099L)
      .setCurrency("eur")
      .setCustomer(customer.getId()).setPaymentMethodOptions(
        PaymentIntentCreateParams.PaymentMethodOptions.builder()
          .setCard(
            PaymentIntentCreateParams.PaymentMethodOptions.Card.builder()
              .setRequireCvcRecollection(true)
              .build()
          )
          .build()
      )
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      .setAutomaticPaymentMethods(
        PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
          .setEnabled(true)
          .build()
      )
      .build();

    PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

    Map<String, String> responseData = new HashMap();
    responseData.put("paymentIntent", paymentIntent.getClientSecret());
    responseData.put("ephemeralKey", ephemeralKey.getSecret());

    responseData.put("customer", customer.getId());
    responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

    return gson.toJson(responseData);
});

Node.js


const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,payment_method_options: {
      card: {
        require_cvc_recollection: true,
      },
    },
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),PaymentMethodOptions: stripe.PaymentIntentPaymentMethodOptionsParams{
      Card: stripe.PaymentIntentPaymentMethodOptionsCardParams{
        RequireCvcRecollection: stripe.Bool(true),
      }
    },
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
      Enabled: stripe.Bool(true),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions
    {
        Card = new PaymentIntentPaymentMethodOptionsCardOptions
        {
            RequireCvcRecollection = true,
        },
    },
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
      Enabled = true,
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

In-app integration for React Native

This is a In-app integration for React Native for when platform is react-native. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=react-native.

This integration combines all of the steps required to pay—collecting payment details and confirming the payment—into a single sheet that displays on top of your app.

Set up Stripe [Server-side] [Client-side]

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use the official libraries for access to the Stripe API from your server:

Ruby

# Available as a gem
sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Python

# Install through pip
pip3 install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin:
# https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md
# Specify that version in your requirements.txt file
stripe>=5.0.0

PHP

# Install the PHP library with Composer
composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases

Java

/*
  For Gradle, add the following dependency to your build.gradle and replace 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:30.0.0"
<!--
  For Maven, add the following dependency to your POM and replace 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>30.0.0</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

Node.js

# Install with npm
npm install stripe --save

Go

# Make sure your project is using Go Modules
go mod init
# Install stripe-go
go get -u github.com/stripe/stripe-go/v83
// Then import the package
import (
  "github.com/stripe/stripe-go/v83"
)

.NET

# Install with dotnet
dotnet add package Stripe.net
dotnet restore
# Or install with NuGet
Install-Package Stripe.net

Client-side

The React Native SDK is open source and fully documented. Internally, it uses the native iOS and Android SDKs. To install Stripes React Native SDK, run one of the following commands in your projects directory (depending on which package manager you use):

yarn

yarn add @stripe/stripe-react-native

npm

npm install @stripe/stripe-react-native

Next, install some other necessary dependencies:

  • For iOS, go to the ios directory and run pod install to ensure that you also install the required native dependencies.
  • For Android, there are no more dependencies to install.

We recommend following the official TypeScript guide to add TypeScript support.

Stripe initialization

To initialize Stripe in your React Native app, either wrap your payment screen with the StripeProvider component, or use the initStripe initialization method. Only the API publishable key in publishableKey is required. The following example shows how to initialize Stripe using the StripeProvider component.

import { useState, useEffect } from 'react';
import { StripeProvider } from '@stripe/stripe-react-native';

function App() {
  const [publishableKey, setPublishableKey] = useState('');

  const fetchPublishableKey = async () => {
    const key = await fetchKey(); // fetch key from your server here
    setPublishableKey(key);
  };

  useEffect(() => {
    fetchPublishableKey();
  }, []);

  return (
    <StripeProvider
      publishableKey={publishableKey}
      merchantIdentifier="merchant.identifier" // required for Apple Pay
      urlScheme="your-url-scheme" // required for 3D Secure and bank redirects
    >
      {/* Your app code here */}
    </StripeProvider>
  );
}

Use your API test keys while you test and develop, and your live mode keys when you publish your app.

Enable payment methods

View your payment methods settings and enable the payment methods you want to support. You need at least one payment method enabled to create a PaymentIntent (The Payment Intents API tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods).

By default, Stripe enables cards and other prevalent payment methods that can help you reach more customers, but we recommend turning on additional payment methods that are relevant for your business and customers. See Payment method support for product and payment method support, and our pricing page for fees.

Add an endpoint [Server-side]

Note

To display the PaymentSheet before you create a PaymentIntent, see Collect payment details before creating an Intent.

This integration uses three Stripe API objects:

  1. PaymentIntent: Stripe uses this to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.

  2. (Optional) Customer: To set up a payment method for future payments, you must attach it to a Customer (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments). Create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customers account later.

  3. (Optional) Customer Ephemeral Key: Information on the Customer object is sensitive, and cant be retrieved directly from an app. An Ephemeral Key grants the SDK temporary access to the Customer.

If you never save cards to a Customer and dont allow returning Customers to reuse saved cards, you can omit the Customer and Customer Ephemeral Key objects from your integration.

For security reasons, your app cant create these objects. Instead, add an endpoint on your server that:

  1. Retrieves the Customer, or creates a new one.
  2. Creates an Ephemeral Key for the Customer.
  3. Creates a PaymentIntent with the amount, currency, and customer. You can also optionally include the automatic_payment_methods parameter. Stripe enables its functionality by default in the latest version of the API.
  4. Returns the Payment Intents client secret (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)), the Ephemeral Keys secret, the Customers id, and your publishable key to your app.

The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe pull payment methods from your Dashboard settings or you can list them manually. Regardless of the option you choose, know that the currency passed in the PaymentIntent filters the payment methods shown to the customer. For example, if you pass eur on the PaymentIntent and have OXXO enabled in the Dashboard, OXXO wont be shown to the customer because OXXO doesnt support eur payments.

Unless your integration requires a code-based option for offering payment methods, Stripe recommends the automated option. This is because Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Payment methods that increase conversion and that are most relevant to the currency and customers location are prioritized.

Manage payment methods from the Dashboard

You can fork and deploy an implementation of this endpoint on CodeSandbox for testing.

You can manage payment methods from the Dashboard. Stripe handles the return of eligible payment methods based on factors such as the transactions amount, currency, and payment flow. The PaymentIntent is created using the payment methods you configured in the Dashboard. If you dont want to use the Dashboard or if you want to specify payment methods manually, you can list them using the payment_method_types attribute.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -H "Stripe-Version: 2025-09-30.clover" \
  -H "Stripe-Account: 2025-09-30.clover" \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \
  # In the latest version of the API, specifying the `automatic_payment_methods` parameter
  # is optional because Stripe enables its functionality by default.
  -d "automatic_payment_methods[enabled]"=true \

Ruby

# This example sets up an endpoint using the Sinatra framework.


# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
Stripe.api_key = '<<YOUR_SECRET_KEY>>'

post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.

# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://dashboard.stripe.com/apikeys
stripe.api_key = '<<YOUR_SECRET_KEY>>'

@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],
    # In the latest version of the API, specifying the `automatic_payment_methods` parameter
    # is optional because Stripe enables its functionality by default.
    automatic_payment_methods={
      'enabled': True,
    },
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,
  // In the latest version of the API, specifying the `automatic_payment_methods` parameter
  // is optional because Stripe enables its functionality by default.
  'automatic_payment_methods' => [
    'enabled' => 'true',
  ],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = "<<YOUR_SECRET_KEY>>";

post(
  "/payment-sheet",
  (request, response) -> {
    response.type("application/json");

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
    Customer customer = Customer.create(customerParams);
    
     // Set the connected account ID
    String connectedAccountId = "{{CONNECTED_ACCOUNT_ID}}";

    // Create RequestOptions with the Stripe-Account header
    RequestOptions requestOptions = RequestOptions.builder()
        .setStripeAccount(connectedAccountId)
        .build();

    // Use an existing Customer ID if this is a returning customer.
    CustomerCreateParams customerParams = CustomerCreateParams.builder().build();

    // Pass the requestOptions when creating the customer
    Customer customer = Customer.create(customerParams, requestOptions);
  });
    EphemeralKeyCreateParams ephemeralKeyParams =
      EphemeralKeyCreateParams.builder()
        .setStripeAccount("{{CONNECTED_ACCOUNT_ID}}")
        .setStripeVersion("2025-09-30.clover")
        .setCustomer(customer.getId())
        .build();

    EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);
    PaymentIntentCreateParams paymentIntentParams =
    PaymentIntentCreateParams.builder()
      .setAmount(1099L)
      .setCurrency("eur")
      .setCustomer(customer.getId())
      // In the latest version of the API, specifying the `automatic_payment_methods` parameter
      // is optional because Stripe enables its functionality by default.
      .setAutomaticPaymentMethods(
        PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
          .setEnabled(true)
          .build()
      )
      .build();

    PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

    Map<String, String> responseData = new HashMap();
    responseData.put("paymentIntent", paymentIntent.getClientSecret());
    responseData.put("ephemeralKey", ephemeralKey.getSecret());

    responseData.put("customer", customer.getId());
    responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

    return gson.toJson(responseData);
});

Node.js


const stripe = require('stripe')('<<YOUR_SECRET_KEY>>');
// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{
      Enabled: stripe.Bool(true),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter
    // is optional because Stripe enables its functionality by default.
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
      Enabled = true,
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

Listing payment methods manually

You can fork and deploy an implementation of this endpoint on CodeSandbox for testing.

curl

# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an Ephemeral Key for the Customer
curl https://api.stripe.com/v1/ephemeral_keys \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \

# Create a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \
  -d "payment_method_types[]"="bancontact" \
  -d "payment_method_types[]"="card" \
  -d "payment_method_types[]"="ideal" \
  -d "payment_method_types[]"="klarna" \
  -d "payment_method_types[]"="sepa_debit" \

Ruby

# This example sets up an endpoint using the Sinatra framework.



post '/payment-sheet' do
  # Use an existing Customer ID if this is a returning customer
  customer = Stripe::Customer.create({stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  ephemeralKey = Stripe::EphemeralKey.create({
    customer: customer['id'],
  }, {stripe_version: '2025-09-30.clover', stripe_account: '{{CONNECTED_ACCOUNT_ID}}'})
  paymentIntent = Stripe::PaymentIntent.create({
    amount: 1099,
    currency: 'eur',
    customer: customer['id'],
    payment_method_types: ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
  })
  {
    paymentIntent: paymentIntent['client_secret'],
    ephemeralKey: ephemeralKey['secret'],
    customer: customer['id'],
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  }.to_json
end

Python

# This example sets up an endpoint using the Flask framework.
# Watch this video to get started: https://youtu.be/7Ul1vfmsDck.


@app.route('/payment-sheet', methods=['POST'])
def payment_sheet():
  # Use an existing Customer ID if this is a returning customer
  customer = stripe.Customer.create(stripe_account="{{CONNECTED_ACCOUNT_ID}}")
  ephemeralKey = stripe.EphemeralKey.create(
    customer=customer['id'],
    stripe_account="{{CONNECTED_ACCOUNT_ID}}",
    stripe_version='2025-09-30.clover',
  )

  paymentIntent = stripe.PaymentIntent.create(
    amount=1099,
    currency='eur',
    customer=customer['id'],
    payment_method_types=["bancontact", "card", "ideal", "klarna", "sepa_debit"],
  )
  return jsonify(paymentIntent=paymentIntent.client_secret,
                 ephemeralKey=ephemeralKey.secret,
                 customer=customer.id,
                 publishableKey='<<YOUR_PUBLISHABLE_KEY>>')

PHP

<?php
require 'vendor/autoload.php';
$stripe = new \Stripe\StripeClient('<<YOUR_SECRET_KEY>>');

// Use an existing Customer ID if this is a returning customer.
$customer = $stripe->customers->create(['stripe_account => '{{CONNECTED_ACCOUNT_ID}}']);
$ephemeralKey = $stripe->ephemeralKeys->create([
  'customer' => $customer->id,
], [
  'stripe_account' => '{{CONNECTED_ACCOUNT_ID}}',
  'stripe_version' => '2025-09-30.clover',
]);

$paymentIntent = $stripe->paymentIntents->create([
  'amount' => 1099,
  'currency' => 'eur',
  'customer' => $customer->id,
  'payment_method_types' => ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
]);

echo json_encode(
  [
    'paymentIntent' => $paymentIntent->client_secret,
    'ephemeralKey' => $ephemeralKey->secret,
    'customer' => $customer->id,
    'publishableKey' => '<<YOUR_PUBLISHABLE_KEY>>'
  ]
);
http_response_code(200);

Java

// This example sets up an endpoint using the Spark framework.

post("/payment-sheet", (request, response) -> {
  response.type("application/json");

  // Use an existing Customer ID if this is a returning customer.
  CustomerCreateParams customerParams = CustomerCreateParams.builder().build();
  Customer customer = Customer.create(customerParams);
  EphemeralKeyCreateParams ephemeralKeyParams =
    EphemeralKeyCreateParams.builder()
      .setStripeVersion("2025-09-30.clover")
      .setCustomer(customer.getId())
      .build();

  EphemeralKey ephemeralKey = EphemeralKey.create(ephemeralKeyParams);

  List<String> paymentMethodTypes = new ArrayList<String>();
  paymentMethodTypes.add("bancontact");
  paymentMethodTypes.add("card");
  paymentMethodTypes.add("ideal");
  paymentMethodTypes.add("klarna");
  paymentMethodTypes.add("sepa_debit");

  PaymentIntentCreateParams paymentIntentParams =
  PaymentIntentCreateParams.builder()
    .setAmount(1099L)
    .setCurrency("eur")
    .setCustomer(customer.getId())
    .addAllPaymentMethodType(paymentMethodTypes)
    .build();

  PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams);

  Map<String, String> responseData = new HashMap();
  responseData.put("paymentIntent", paymentIntent.getClientSecret());
  responseData.put("ephemeralKey", ephemeralKey.getSecret());
  responseData.put("customer", customer.getId());
  responseData.put("publishableKey", "<<YOUR_PUBLISHABLE_KEY>>");

  return gson.toJson(responseData);
});

Node.js


// This example sets up an endpoint using the Express framework.

app.post('/payment-sheet', async (req, res) => {
  // Use an existing Customer ID if this is a returning customer.
  const customer = await stripe.customers.create(stripeAccount:'{{CONNECTED_ACCOUNT_ID}}');
  const ephemeralKey = await stripe.ephemeralKeys.create(
    {customer: customer.id},
    {
      stripeAccount: '{{CONNECTED_ACCOUNT_ID}}',
      apiVersion: '2025-09-30.clover'
    }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'eur',
    customer: customer.id,
    payment_method_types: ['bancontact', 'card', 'ideal', 'klarna', 'sepa_debit'],
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
    publishableKey: '<<YOUR_PUBLISHABLE_KEY>>'
  });
});

Go


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "<<YOUR_SECRET_KEY>>"

func handlePaymentSheet(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
    return
  }

  // Use an existing Customer ID if this is a returning customer.
  cparams := &stripe.CustomerParams{}
  stripe.SetStripeAccount(cparams.ExtraParams, "{{CONNECTED_ACCOUNT_ID}}")
  c, _ := customer.New(cparams)

  ekparams := &stripe.EphemeralKeyParams{
    Customer: stripe.String(c.ID),
    StripeAccount: stripe.String("{{CONNECTED_ACCOUNT_ID}}")
    StripeVersion: stripe.String("2025-09-30.clover"),
  }
  ek, _ := ephemeralKey.New(ekparams)

  piparams := &stripe.PaymentIntentParams{
    Amount:   stripe.Int64(1099),
    Currency: stripe.String(string(stripe.CurrencyEUR)),
    Customer: stripe.String(c.ID),
    PaymentMethodTypes: []*string{
      stripe.String("bancontact"),
      stripe.String("card"),
      stripe.String("ideal"),
      stripe.String("klarna"),
      stripe.String("sepa_debit"),
    },
  }
  pi, _ := paymentintent.New(piparams)

  writeJSON(w, struct {
    PaymentIntent  string `json:"paymentIntent"`
    EphemeralKey   string `json:"ephemeralKey"`
    Customer       string `json:"customer"`
    PublishableKey string `json:"publishableKey"`
  }{
    PaymentIntent: pi.ClientSecret,
    EphemeralKey: ek.Secret,
    Customer: c.ID,
    PublishableKey: "<<YOUR_PUBLISHABLE_KEY>>",
  })
}

.NET


// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
StripeConfiguration.ApiKey = "<<YOUR_SECRET_KEY>>";

[HttpPost("payment-sheet")]
public ActionResult<PaymentSheetCreateResponse> CreatePaymentSheet([FromBody] CreatePaymentSheetRequest req)
{
  // Use an existing Customer ID if this is a returning customer.
  var customerOptions = new CustomerCreateOptions();
  var customerService = new CustomerService();
  var requestOptions = new RequestOptions
{
    StripeAccount = "{{CONNECTED_ACCOUNT_ID}}"
};
  var customer = customerService.Create(customerOptions, requestOptions);
  var ephemeralKeyOptions = new EphemeralKeyCreateOptions
  {
    Customer = customer.Id,
    StipeAccount = "{{CONNECTED_ACCOUNT_ID}}",
    StripeVersion = "2025-09-30.clover",
  };
  var ephemeralKeyService = new EphemeralKeyService();
  var ephemeralKey = ephemeralKeyService.Create(ephemeralKeyOptions);

  var paymentIntentOptions = new PaymentIntentCreateOptions
  {
    Amount = 1099,
    Currency = "eur",
    Customer = customer.Id,
    PaymentMethodTypes = new List<string>
    {
      "bancontact",
      "card",
      "ideal",
      "klarna",
      "sepa_debit",
    },
  };
  var paymentIntentService = new PaymentIntentService();
  PaymentIntent paymentIntent = paymentIntentService.Create(paymentIntentOptions);

  return new PaymentSheetCreateResponse
  {
    PaymentIntent = paymentIntent.ClientSecret,
    EphemeralKey = ephemeralKey.Secret,

    Customer = customer.Id,
    PublishableKey = "<<YOUR_PUBLISHABLE_KEY>>",
  };
}

Each payment method needs to support the currency passed in the PaymentIntent and your business needs to be based in one of the countries each payment method supports. See the Payment method integration options page for more details about whats supported.

Collect payment details [Client-side]

Before displaying the mobile Payment Element, your checkout page should:

  • Show the products being purchased and the total amount
  • Collect any required shipping information
  • Include a checkout button to present Stripes UI

In the checkout of your app, make a network request to the backend endpoint you created in the previous step and call initPaymentSheet from the useStripe hook.


export default function CheckoutScreen() {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();
  const [loading, setLoading] = useState(false);

  const fetchPaymentSheetParams = async () => {
    const response = await fetch(`${API_URL}/payment-sheet`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
    });
    const { paymentIntent, ephemeralKey, customer } = await response.json();

    return {
      paymentIntent,
      ephemeralKey,
      customer,
    };
  };

  const initializePaymentSheet = async () => {
    const {
      paymentIntent,
      ephemeralKey,
      customer,
    } = await fetchPaymentSheetParams();

    const { error } = await initPaymentSheet({
      merchantDisplayName: "Example, Inc.",
      customerId: customer,
      customerEphemeralKeySecret: ephemeralKey,
      paymentIntentClientSecret: paymentIntent,
      // Set `allowsDelayedPaymentMethods` to true if your business can handle payment
      //methods that complete payment after a delay, like SEPA Debit and Sofort.
      allowsDelayedPaymentMethods: true,
      defaultBillingDetails: {
        name: 'Jane Doe',
      }
    });
    if (!error) {
      setLoading(true);
    }
  };

  const openPaymentSheet = async () => {
    // see below
  };

  useEffect(() => {
    initializePaymentSheet();
  }, []);

  return (
    <Screen>
      <Button
        variant="primary"
        disabled={!loading}
        title="Checkout"
        onPress={openPaymentSheet}
      />
    </Screen>
  );
}

When your customer taps the Checkout button, call presentPaymentSheet() to open the sheet. After the customer completes the payment, the sheet is dismissed and the promise resolves with an optional StripeError<PaymentSheetError>.

export default function CheckoutScreen() {
  // continued from above

  const openPaymentSheet = async () => {
    const { error } = await presentPaymentSheet();

    if (error) {
      Alert.alert(`Error code: ${error.code}`, error.message);
    } else {
      Alert.alert('Success', 'Your order is confirmed!');
    }
  };

  return (
    <Screen>
      <Button
        variant="primary"
        disabled={!loading}
        title="Checkout"
        onPress={openPaymentSheet}
      />
    </Screen>
  );
}

If there is no error, inform the user theyre done (for example, by displaying an order confirmation screen).

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isnt known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

Set up a return URL (iOS only) [Client-side]

When a customer exits your app (for example to authenticate in Safari or their banking app), provide a way for them to automatically return to your app. Many payment method types require a return URL. If you dont provide one, we cant present payment methods that require a return URL to your users, even if youve enabled them.

To provide a return URL:

  1. Register a custom URL. Universal links arent supported.
  2. Configure your custom URL.
  3. Set up your root component to forward the URL to the Stripe SDK as shown below.

If youre using Expo, set your scheme in the app.json file.

import { useEffect, useCallback } from 'react';
import { Linking } from 'react-native';
import { useStripe } from '@stripe/stripe-react-native';

export default function MyApp() {
  const { handleURLCallback } = useStripe();

  const handleDeepLink = useCallback(
    async (url: string | null) => {
      if (url) {
        const stripeHandled = await handleURLCallback(url);
        if (stripeHandled) {
          // This was a Stripe URL - you can return or add extra handling here as you see fit
        } else {
          // This was NOT a Stripe URL  handle as you normally would
        }
      }
    },
    [handleURLCallback]
  );

  useEffect(() => {
    const getUrlAsync = async () => {
      const initialUrl = await Linking.getInitialURL();
      handleDeepLink(initialUrl);
    };

    getUrlAsync();

    const deepLinkListener = Linking.addEventListener(
      'url',
      (event: { url: string }) => {
        handleDeepLink(event.url);
      }
    );

    return () => deepLinkListener.remove();
  }, [handleDeepLink]);

  return (
    <View>
      <AwesomeAppComponent />
    </View>
  );
}

Additionally, set the returnURL when you call the initPaymentSheet method:

await initPaymentSheet({
  ...
  returnURL: 'your-app://stripe-redirect',
  ...
});

For more information on native URL schemes, refer to the Android and iOS docs.

Handle post-payment events

Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard webhook tool or follow the webhook guide to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept different types of payment methods with a single integration.

In addition to handling the payment_intent.succeeded event, we recommend handling these other events when collecting payments with the Payment Element:

Event Description Action
payment_intent.succeeded Sent when a customer successfully completes a payment. Send the customer an order confirmation and fulfill (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order.
payment_intent.processing Sent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. Its followed by either a payment_intent.succeeded or payment_intent.payment_failed event in the future. Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete.
payment_intent.payment_failed Sent when a customer attempts a payment, but the payment fails. If a payment transitions from processing to payment_failed, offer the customer another attempt to pay.

Test the integration

Cards

Card number Scenario How to test
4242424242424242 The card payment succeeds and doesnt require authentication. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000002500003155 The card payment requires authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
4000000000009995 The card is declined with a decline code like insufficient_funds. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.
6205500000000000004 The UnionPay card has a variable length of 13-19 digits. Fill out the credit card form using the credit card number with any expiration, CVC, and postal code.

Bank redirects

Payment method Scenario How to test
Bancontact, iDEAL Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
Pay by Bank Your customer successfully pays with a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Complete test payment on the redirect page.
Pay by Bank Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. Choose the payment method, fill out the required details, and confirm the payment. Then click Fail test payment on the redirect page.
BLIK BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didnt respond in time). Use email patterns to simulate the different failures.

Bank debits

Payment method Scenario How to test
SEPA Direct Debit Your customer successfully pays with SEPA Direct Debit. Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct Debit Your customers payment intent status transitions from processing to requires_payment_method. Fill out the form using the account number AT861904300235473202.

See Testing for additional information to test your integration.

Enable Link in your Payment Method settings to allow your customers to securely save and reuse their payment information using Links one-click express checkout button.

Pass your customers email to the Mobile Payment Element

Link authenticates a customer using their email address. Stripe recommends prefilling as much information as possible to streamline the checkout process.

To prefill the customers name, email address, and phone number, supply defaultBillingDetails with your customer information to initPaymentSheet.

await initPaymentSheet({
  ...
  defaultBillingDetails: {
    name: 'Jenny Rosen',
    email: 'jenny.rosen@example.com',
    phone: '888-888-8888',
  },
});

Optional: Enable Apple Pay

Register for an Apple Merchant ID

Obtain an Apple Merchant ID by registering for a new identifier on the Apple Developer website.

Fill out the form with a description and identifier. Your description is for your own records and you can modify it in the future. Stripe recommends using the name of your app as the identifier (for example, merchant.com.{{YOUR_APP_NAME}}).

Create a new Apple Pay certificate

Create a certificate for your app to encrypt payment data.

Go to the iOS Certificate Settings in the Dashboard, click Add new application, and follow the guide.

Download a Certificate Signing Request (CSR) file to get a secure certificate from Apple that allows you to use Apple Pay.

One CSR file must be used to issue exactly one certificate. If you switch your Apple Merchant ID, you must go to the iOS Certificate Settings in the Dashboard to obtain a new CSR and certificate.

Integrate with Xcode

Add the Apple Pay capability to your app. In Xcode, open your project settings, click the Signing & Capabilities tab, and add the Apple Pay capability. You might be prompted to log in to your developer account at this point. Select the merchant ID you created earlier, and your app is ready to accept Apple Pay.

Enable the Apple Pay capability in Xcode

Add Apple Pay

One-time payment

Pass your merchant ID when you create StripeProvider:

import { StripeProvider } from '@stripe/stripe-react-native';

function App() {
  return (
    <StripeProvider
      publishableKey="<<YOUR_PUBLISHABLE_KEY>>"
      merchantIdentifier="MERCHANT_ID"
    >
      // Your app code here
    </StripeProvider>
  );
}

When you call initPaymentSheet, pass in your ApplePayParams:

await initPaymentSheet({
  // ...
  applePay: {
    merchantCountryCode: 'US',
  },
});

Recurring payments

When you call initPaymentSheet, pass in an ApplePayParams with merchantCountryCode set to the country code of your business.

In accordance with Apples guidelines for recurring payments, you must also set a cardItems that includes a RecurringCartSummaryItem with the amount you intend to charge (for example, “$59.95 a month”).

You can also adopt merchant tokens by setting the request with its type set to PaymentRequestType.Recurring

To learn more about how to use recurring payments with Apple Pay, see Apples PassKit documentation.

iOS (React Native)

const initializePaymentSheet = async () => {
  const recurringSummaryItem = {
    label: 'My Subscription',
    amount: '59.99',
    paymentType: 'Recurring',
    intervalCount: 1,
    intervalUnit: 'month',
    // Payment starts today
    startDate: new Date().getTime() / 1000,

    // Payment ends in one year
    endDate: new Date().getTime() / 1000 + 60 * 60 * 24 * 365,
  };

  const {error} = await initPaymentSheet({
    // ...
    applePay: {
      merchantCountryCode: 'US',
      cartItems: [recurringSummaryItem],
      request: {
        type: PaymentRequestType.Recurring,
        description: 'Recurring',
        managementUrl: 'https://my-backend.example.com/customer-portal',
        billing: recurringSummaryItem,
        billingAgreement:
          "You'll be billed $59.99 every month for the next 12 months. To cancel at any time, go to Account and click 'Cancel Membership.'",
      },
    },
  });
};

Order tracking

To add order tracking information in iOS 16 or later, configure a setOrderTracking callback function. Stripe calls your implementation after the payment is complete, but before iOS dismisses the Apple Pay sheet.

In your implementation of setOrderTracking callback function, fetch the order details from your server for the completed order, and pass the details to the provided completion function.

To learn more about order tracking, see Apples Wallet Orders documentation.

iOS (React Native)

await initPaymentSheet({
  // ...
  applePay: {
    // ...
    setOrderTracking: async complete => {
      const apiEndpoint =
        Platform.OS === 'ios'
          ? 'http://localhost:4242'
          : 'http://10.0.2.2:4567';
      const response = await fetch(
        `${apiEndpoint}/retrieve-order?orderId=${orderId}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        },
      );
      if (response.status === 200) {
        const orderDetails = await response.json();
        // orderDetails should include orderIdentifier, orderTypeIdentifier,
        // authenticationToken and webServiceUrl
        complete(orderDetails);
      }
    },
  },
});

Optional: Enable Google Pay

Set up your integration

To use Google Pay, first enable the Google Pay API by adding the following to the <application> tag of your AndroidManifest.xml:

<application>
  ...
  <meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
</application>

For more details, see Google Pays Set up Google Pay API for Android.

Add Google Pay

When you initialize PaymentSheet, set merchantCountryCode to the country code of your business and set googlePay to true.

You can also use the test environment by passing the testEnv parameter. You can only test Google Pay on a physical Android device. Follow the React Native docs to test your application on a physical device.

const { error, paymentOption } = await initPaymentSheet({
  // ...
  googlePay: {
    merchantCountryCode: 'US',
    testEnv: true, // use test environment
  },
});

Optional: Enable card scanning (iOS only) [Client-side]

To enable card scanning support, set the NSCameraUsageDescription (Privacy - Camera Usage Description) in the Info.plist of your application, and provide a reason for accessing the camera (for example, “To scan cards”). Devices with iOS 13 or higher support card scanning.

Optional: Customize the sheet [Client-side]

All customization is configured using initPaymentSheet.

Appearance

Customize colors, fonts, and so on to match the look and feel of your app by using the appearance API.

Merchant display name

Specify a customer-facing business name by setting merchantDisplayName. By default, this is your apps name.

await initPaymentSheet({
  // ...
  merchantDisplayName: 'Example Inc.',
});

Dark mode

By default, PaymentSheet automatically adapts to the users system-wide appearance settings (light and dark mode). You can change this by setting the style property to alwaysLight or alwaysDark mode on iOS.

await initPaymentSheet({
  // ...
  style: 'alwaysDark',
});

On Android, set light or dark mode on your app:

// force dark
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
// force light
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)

Default billing details

To set default values for billing details collected in the PaymentSheet, configure the defaultBillingDetails property. The PaymentSheet pre-populates its fields with the values that you provide.

await initPaymentSheet({
  // ...
  defaultBillingDetails: {
      email: 'foo@bar.com',
      address: {
        country: 'US',
      },
  },
});

Collect billing details

Use billingDetailsCollectionConfiguration to specify how you want to collect billing details in the PaymentSheet.

You can collect your customers name, email, phone number, and address.

If you dont intend to collect the values that the payment method requires, you must do the following:

  1. Attach the values that arent collected by PaymentSheet to the defaultBillingDetails property.
  2. Set billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod to true.
await initPaymentSheet({
  // ...
  defaultBillingDetails: {
    email: 'foo@bar.com',
  }
  billingDetailsCollectionConfiguration: {
    name: PaymentSheet.CollectionMode.ALWAYS,
    email: PaymentSheet.CollectionMode.NEVER,
    address: PaymentSheet.AddressCollectionMode.FULL,
    attachDefaultsToPaymentMethod: true
  },
});

Consult with your legal counsel regarding laws that apply to collecting information. Only collect phone numbers if you need them for the transaction.

Optional: Handle user logout

PaymentSheet stores some information locally to remember whether a user has used Link within an app. To clear the internal state of PaymentSheet, call the resetPaymentSheetCustomer() method when your user logs out.

export default function CheckoutScreen() {
  // continued from above
  const { initPaymentSheet, presentPaymentSheet, resetPaymentSheetCustomer } = useStripe();

  const logout = async () => {
    await resetPaymentSheetCustomer();
  };

  return (
    <Screen>
      <Button
        title="Checkout"
        onPress={openPaymentSheet}
      />
      <Button
        title="Checkout"
        onPress={logout}
      />
    </Screen>
  );
}

Optional: Complete payment in your UI

You can present Payment Sheet to only collect payment method details and then later call a confirm method to complete payment in your apps UI. This is useful if you have a custom buy button or require additional steps after payment details are collected.

A sample integration is available on our GitHub.

  1. First, call initPaymentSheet and pass customFlow: true. initPaymentSheet resolves with an initial payment option containing an image and label representing the customers payment method. Update your UI with these details.
const {
  initPaymentSheet,
  presentPaymentSheet,
  confirmPaymentSheetPayment,
} = useStripe()

const { error, paymentOption } = await initPaymentSheet({
  customerId: customer,
  customerEphemeralKeySecret: ephemeralKey,
  paymentIntentClientSecret: paymentIntent,
  customFlow: true,
  merchantDisplayName: 'Example Inc.',
});
// Update your UI with paymentOption
  1. Use presentPaymentSheet to collect payment details. When the customer finishes, the sheet dismisses itself and resolves the promise. Update your UI with the selected payment method details.
const { error, paymentOption } = await presentPaymentSheet();
  1. Use confirmPaymentSheetPayment to confirm the payment. This resolves with the result of the payment.
const { error } = await confirmPaymentSheetPayment();

if (error) {
  Alert.alert(`Error code: ${error.code}`, error.message);
} else {
  Alert.alert(
    'Success',
    'Your order is confirmed!'
  );
}

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isnt known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

Plugins

This is a Plugins for when platform is plugins. View the full page at https://docs.stripe.com/payments/accept-a-payment?platform=plugins.

Collect Stripe payments in whichever publishing or e-commerce platform you use, with a Stripe plugin created by our partners. The Stripe developer community uses Stripes APIs to create plugins and extensions.

If you use a third-party platform to build and maintain a website, you can add Stripe payments with a plugin.

All plugins on this page are ready for Strong Customer Authentication (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase) (SCA).

Get started

Check out our full list of partners for a solution to your use case.