# Set up future card payments
Use manual server-side confirmation or present payment methods separately.
# Stripe-hosted page
> This is a Stripe-hosted page for when platform is web and payment-ui is stripe-hosted. View the full page at https://docs.stripe.com/payments/save-and-reuse-cards-only?platform=web&payment-ui=stripe-hosted.
> We recommend that you follow the [Set up future payments](https://docs.stripe.com/payments/checkout/save-and-reuse.md) guide. Only use this guide if you need to use manual server-side confirmation or your integration requires presenting payment methods separately. If you’ve already integrated with Elements, see the [Payment Element migration guide](https://docs.stripe.com/payments/payment-element/migration.md).
To collect customer payment details that you can reuse later, use Checkout’s setup mode. Setup mode uses the [Setup Intents API](https://docs.stripe.com/api/setup_intents.md) to create [Payment Methods](https://docs.stripe.com/api/payment_methods.md).
Check out our [full, working sample on GitHub](https://github.com/stripe-samples/checkout-remember-me-with-twilio-verify).
## Set up Stripe [Server-side]
First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register).
Use our official libraries to access the Stripe API from your application:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
## Create a Checkout Session [Client-side] [Server-side]
Add a checkout button to your website that calls a server-side endpoint to create a Checkout Session.
```html
Checkout
```
To create a setup mode Session, use the `mode` parameter with a value of `setup` when creating the Session. You can optionally specify the [customer](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer) parameter to automatically attach the created payment method to an existing customer. Checkout uses [Dynamic payment methods](https://docs.stripe.com/payments/payment-methods/dynamic-payment-methods.md) by default, which requires you to pass the [currency](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-currency) parameter when using `setup` mode.
Append the `{CHECKOUT_SESSION_ID}` template variable to the `success_url` to get access to the Session ID after your customer successfully completes a Checkout Session. After creating the Checkout Session, redirect your customer to the [URL](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-url) returned in the response.
```curl
curl https://api.stripe.com/v1/checkout/sessions \
-u "<>:" \
-d mode=setup \
-d currency=usd \
-d "customer={{CUSTOMER_ID}}" \
--data-urlencode "success_url=https://example.com/success?session_id={CHECKOUT_SESSION_ID}"
```
### 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](https://dashboard.stripe.com/settings/payment_methods). 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](https://dashboard.stripe.com/settings/payment_methods). By default, Apple Pay is enabled and Google Pay is disabled. However, in some cases Stripe filters them out even when they’re enabled. We filter Google Pay if you [enable automatic tax](https://docs.stripe.com/tax/checkout.md) without collecting a shipping address.
Checkout’s Stripe-hosted pages don’t need integration changes to enable Apple Pay or Google Pay. Stripe handles these payments the same way as other card payments.
## Retrieve the Checkout Session [Server-side]
After a customer successfully completes their Checkout Session, you need to retrieve the Session object. There are two ways to do this:
- **Asynchronously**: Handle `checkout.session.completed` *webhooks* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests), which contain a Session object. Learn more about [setting up webhooks](https://docs.stripe.com/webhooks.md).
- **Synchronously**: Obtain the Session ID from the `success_url` when a user redirects back to your site. Use the Session ID to [retrieve](https://docs.stripe.com/api/checkout/sessions/retrieve.md) the Session object.
```curl
curl https://api.stripe.com/v1/checkout/sessions/cs_test_MlZAaTXUMHjWZ7DcXjusJnDU4MxPalbtL5eYrmS2GKxqscDtpJq8QM0k \
-u "<>:"
```
The right choice depends on your tolerance for dropoff, as customers may not always reach the `success_url` after a successful payment. It’s possible for them close their browser tab before the redirect occurs. Handling webhooks prevents your integration from being susceptible to this form of dropoff.
After you have retrieved the Session object, get the value of the `setup_intent` key, which is the ID for the SetupIntent created during the Checkout Session. A [SetupIntent](https://docs.stripe.com/payments/setup-intents.md) is an object used to set up the customer’s bank account information for future payments.
Example `checkout.session.completed` payload:
```json
{
"id": "evt_1Ep24XHssDVaQm2PpwS19Yt0",
"object": "event",
"api_version": "2019-03-14",
"created": 1561420781,
"data": {
"object": {
"id": "cs_test_MlZAaTXUMHjWZ7DcXjusJnDU4MxPalbtL5eYrmS2GKxqscDtpJq8QM0k",
"object": "checkout.session",
"billing_address_collection": null,
"client_reference_id": null,
"customer": "",
"customer_email": null,
"display_items": [],
"mode": "setup","setup_intent": "seti_1EzVO3HssDVaQm2PJjXHmLlM",
"submit_type": null,
"subscription": null,
"success_url": "https://example.com/success"
}
},
"livemode": false,
"pending_webhooks": 1,
"request": {
"id": null,
"idempotency_key": null
},
"type": "checkout.session.completed"
}
```
Note the `setup_intent` ID for the next step.
## Retrieve the SetupIntent [Server-side]
Using the `setup_intent` ID, [retrieve](https://docs.stripe.com/api/setup_intents/retrieve.md) the SetupIntent object. The returned object contains a `payment_method` ID that you can attach to a customer in the next step.
```curl
curl https://api.stripe.com/v1/setup_intents/seti_1EzVO3HssDVaQm2PJjXHmLlM \
-u "<>:"
```
> If you’re requesting this information synchronously from the Stripe API (as opposed to handling webhooks), you can combine the previous step with this step by [expanding](https://docs.stripe.com/api/expanding_objects.md) the SetupIntent object in the request to the /v1/checkout/session endpoint. Doing this prevents you from having to make two network requests to access the newly created PaymentMethod ID.
## Charge the payment method later [Server-side]
If you didn’t create the Checkout Session with an existing customer, use the ID of the PaymentMethod to [attach](https://docs.stripe.com/api/payment_methods/attach.md) the *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) 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). After you attach the PaymentMethod to a customer, you can make an *off-session* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information) payment using a [PaymentIntent](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method):
- Set [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the Customer and [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the ID of the PaymentMethod.
- Set [off_session](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-off_session) to `true` to indicate that the customer isn’t in your checkout flow during a payment attempt and can’t fulfill an authentication request made by a partner, such as a card issuer, bank, or other payment institution. If, during your checkout flow, a partner requests authentication, Stripe requests exemptions using customer information from a previous *on-session* (A payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method) transaction. If the conditions for exemption aren’t met, the PaymentIntent might throw an error.
- Set the value of the PaymentIntent’s [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to `true`, which causes confirmation to occur immediately when you create the PaymentIntent.
```curl
curl https://api.stripe.com/v1/payment_intents \
-u "<>:" \
-d amount=1099 \
-d currency=usd \
-d "customer={{CUSTOMER_ID}}" \
-d "payment_method={{PAYMENTMETHOD_ID}}" \
-d off_session=true \
-d confirm=true
```
When a payment attempt fails, the request also fails with a 402 HTTP status code and the status of the PaymentIntent is *requires\_payment\_method* (This status appears as "requires_source" in API versions before 2019-02-11). Notify your customer to return to your application (for example, by sending an email or in-app notification) and direct your customer to a new Checkout Session to select another payment method.
```curl
curl https://api.stripe.com/v1/checkout/sessions \
-u "<>:" \
-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]=1099" \
-d "line_items[0][quantity]=1" \
-d mode=payment \
--data-urlencode "success_url=https://example.com/success?session_id={CHECKOUT_SESSION_ID}"
```
# Advanced integration
> This is a Advanced integration for when platform is web and payment-ui is direct-api. View the full page at https://docs.stripe.com/payments/save-and-reuse-cards-only?platform=web&payment-ui=direct-api.
> We recommend that you follow the [Set up future payments](https://docs.stripe.com/payments/save-and-reuse.md) guide. Only use this guide if you need to use manual server-side confirmation or your integration requires presenting payment methods separately. If you’ve already integrated with Elements, see the [Payment Element migration guide](https://docs.stripe.com/payments/payment-element/migration.md).
The [Setup Intents API](https://docs.stripe.com/api/setup_intents.md) lets you save a customer’s card without an initial payment. This is helpful if you want to onboard customers now, set them up for payments, and charge them in the future—when they’re offline.
Use this integration to set up recurring payments or to create one-time payments with a final amount determined later, often after the customer receives your service.
## Set up Stripe [Server-side]
Use our official libraries for access to the Stripe API from your application:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
## Create a Customer before setup [Server-side]
To set a card up 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. Customer objects allow for reusing payment methods and tracking across multiple payments.
> #### Compare Customers v1 and Accounts v2 references
>
> If your Connect platform uses [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer), use our [guide](https://docs.stripe.com/connect/use-accounts-as-customers.md) to replace `Customer` and event references in your code with the equivalent Accounts v2 API references.
```curl
curl https://api.stripe.com/v1/customers \
-u "<>:" \
-d "name=Jenny Rosen" \
--data-urlencode "email=jennyrosen@example.com"
```
Successful creation returns the [Customer](https://docs.stripe.com/api/customers/object.md) object. You can inspect the object for the customer `id` and store the value in your database for later retrieval.
You can find these customers in the [Customers](https://dashboard.stripe.com/customers) page in the Dashboard.
## Create a SetupIntent [Server-side]
A [SetupIntent](https://docs.stripe.com/api/setup_intents.md) is an object that represents your intent to set up a customer’s card for future payments.
The `SetupIntent` object contains a [client secret](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-client_secret), a unique key that you need to pass to Stripe.js on the client side to collect card details. The client secret lets you perform certain actions on the client, such as confirming the setup and updating payment method details, while hiding sensitive information like `customer`. You can use the client secret to validate and authenticate card details using the credit card networks. The client secret is sensitive—don’t log it, embed it in URLs, or expose it to anyone but the customer.
If your application uses server-side rendering, use your template framework to embed the client secret in the page using a [data attribute](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) or a hidden HTML element.
#### curl
```bash
curl https://api.stripe.com/v1/setup_intents \
-u <>: \
-d "customer"="{{CUSTOMER_ID}}"
```
If you only plan on using the card for future payments when your customer is present during the checkout flow, set the [usage](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-usage) parameter to *on\_session* (A payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method) to optimize authorization rates.
## Collect card details [Client-side]
The *Setup Intents API* (The Setup Intents API lets you build dynamic flows for collecting payment method details for future payments. It tracks the lifecycle of a payment setup flow and can trigger additional authentication steps if required by law or by the payment method) is fully integrated with [Stripe.js](https://docs.stripe.com/payments/elements.md), which lets you use the Elements UI library to securely collect card details on the client side.
> When saving card details to use for future *off-session payments* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information), especially in Europe because of regulations around card reuse, [get permission to save a card](https://docs.stripe.com/strong-customer-authentication.md#sca-enforcement). Include some text in your checkout flow to inform your user how you intend to use the card.
#### HTML + JS
To get started with Elements, include the following script on your checkout page. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. don’t include the script in a bundle or host a copy of it yourself.
```html
```
To best leverage Stripe’s [advanced fraud functionality](https://docs.stripe.com/radar.md), include this script on every page on your site, not just the checkout page. Including the script on every page [allows Stripe to detect suspicious behavior](https://docs.stripe.com/disputes/prevention/advanced-fraud-detection.md) that may be indicative of fraud as users browse your website.
### Add Elements to your page
Next, create an instance of the [Stripe object](https://docs.stripe.com/js.md#stripe-function), providing your publishable [API key](https://docs.stripe.com/keys.md) as the first parameter. After, create an instance of the [Elements object](https://docs.stripe.com/js.md#stripe-elements) and use it to mount a `card` element in the DOM.
The `card` Element simplifies the payment form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card details. Otherwise, combine `cardNumber`, `cardExpiry`, and `cardCvc` Elements for a flexible, multi-input card form.
See [example payment forms](https://stripe.com/payments/elements/examples) created with Elements on GitHub.
> Always collect a postal code to increase card acceptance rates and reduce fraud.
>
> The [single line Card Element](https://docs.stripe.com/js/element/other_element?type=card) automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with split Elements ([Card Number](https://docs.stripe.com/js/element/other_element?type=cardNumber), [Expiry](https://docs.stripe.com/js/element/other_element?type=cardExpiry), [CVC](https://docs.stripe.com/js/element/other_element?type=cardCvc)), add a separate input field for the customer’s postal code.
```javascript
const stripe = Stripe('<>');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
```
A Stripe Element contains an iframe that securely sends the payment information to Stripe over an HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.
You can test your integration without using HTTPS. [Enable it](https://docs.stripe.com/security/guide.md#tls) when you’re ready to accept live payments.
### Confirm the SetupIntent
To complete the setup, retrieve the client secret from the SetupIntent created in the previous step and use [stripe.confirmCardSetup](https://docs.stripe.com/js/setup_intents/confirm_card_setup) and the Card element to complete the setup. When the setup completes successfully, the value of the returned SetupIntent’s `status` property is `succeeded`.
```javascript
const cardholderName = document.getElementById('cardholder-name');
const setupForm = document.getElementById('setup-form');
const clientSecret = setupForm.dataset.secret;
setupForm.addEventListener('submit', async (ev) => {
ev.preventDefault();
const {setupIntent, error} = await stripe.confirmCardSetup(
clientSecret,
{
payment_method: {
card: cardElement,
billing_details: {
name: cardholderName.value,
},
},
}
);
if (error) {
// Display error.message in your UI.
} else {
if (setupIntent.status === 'succeeded') {
// The setup has succeeded. Display a success message. Send
// setupIntent.payment_method to your server to save the card to a Customer
}
}
});
```
#### React
#### npm
Install [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) and the [Stripe.js loader](https://www.npmjs.com/package/@stripe/stripe-js) from the npm public registry.
```bash
npm install --save @stripe/react-stripe-js @stripe/stripe-js
```
#### umd
We also provide a UMD build for sites that don’t use npm or modules.
Include the Stripe.js script, which exports a global `Stripe` function, and the UMD build of React Stripe.js, which exports a global `ReactStripe` object. Always load the Stripe.js script directly from **js.stripe.com** to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.
```html
```
> The [demo in CodeSandbox](https://codesandbox.io/s/react-stripe-official-q1loc?fontsize=14&hidenavigation=1&theme=dark) lets you try out React Stripe.js without having to create a new project.
### Add Stripe.js and Elements to your page
To use Element components, wrap your checkout page component in an [Elements provider](https://docs.stripe.com/sdks/stripejs-react.md#elements-provider). Call `loadStripe` with your publishable key and pass the returned `Promise` to the `Elements` provider.
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';
import CardSetupForm from './CardSetupForm';
// Make sure to call `loadStripe` outside of a component's render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('<>');
function App() {
return (
);
};
ReactDOM.render(, document.getElementById('root'));
```
### Add and configure a CardElement component
Use individual Element components, such as `CardElement`, to build your form.
#### JSX
```jsx
/**
* Use the CSS tab above to style your Element's container.
*/
import React from 'react';
import {CardElement} from '@stripe/react-stripe-js';
import './Styles.css'
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: "#32325d",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#aab7c4",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
};
function CardSection() {
return (
);
};
export default CardSection;
```
Elements are completely customizable. You can [style Elements](https://docs.stripe.com/js/elements_object/create_element?type=card#elements_create-options) to match the look and feel of your site, providing seamless checkout for your customers. It’s also possible to style various input states, for example when the Element has focus.
The `CardElement` simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card and billing details. Otherwise, combine `CardNumberElement`, `CardExpiryElement`, and `CardCvcElement` elements for a flexible, multi-input card form.
> Always collect a postal code to increase card acceptance rates and reduce fraud.
>
> The [single line Card Element](https://docs.stripe.com/js/element/other_element?type=card) automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with split Elements ([Card Number](https://docs.stripe.com/js/element/other_element?type=cardNumber), [Expiry](https://docs.stripe.com/js/element/other_element?type=cardExpiry), [CVC](https://docs.stripe.com/js/element/other_element?type=cardCvc)), add a separate input field for the customer’s postal code.
### Confirm the SetupIntent
To complete the setup, retrieve the client secret from the SetupIntent created in [step three](https://docs.stripe.com/payments/save-and-reuse-cards-only.md#web-create-setup-intent) and use [stripe.confirmCardSetup](https://docs.stripe.com/js/setup_intents/confirm_card_setup) and the Card element to complete the setup. When the setup completes successfully, the value of the returned SetupIntent’s `status` property is `succeeded`.
To call `stripe.confirmCardSetup` from your payment form component, use the [useStripe](https://docs.stripe.com/sdks/stripejs-react.md#usestripe-hook) and [useElements](https://docs.stripe.com/sdks/stripejs-react.md#useelements-hook) hooks. If you prefer traditional class components over hooks, you can instead use an [ElementsConsumer](https://docs.stripe.com/sdks/stripejs-react.md#elements-consumer).
#### Hooks
```jsx
import React from 'react';
import {useStripe, useElements, CardElement} from '@stripe/react-stripe-js';
import CardSection from './CardSection';
export default function CardSetupForm() {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const result = await stripe.confirmCardSetup('{{CLIENT_SECRET}}', {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Jenny Rosen',
},
}
});
if (result.error) {
// Display result.error.message in your UI.
} else {
// The setup has succeeded. Display a success message and send
// result.setupIntent.payment_method to your server to save the
// card to a Customer
}
};
return (
);
}
```
#### Class Components
```jsx
import React from 'react';
import {ElementsConsumer, CardElement} from '@stripe/react-stripe-js';
import CardSection from './CardSection';
class CardSetupForm extends React.Component {
handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
const {stripe, elements} = this.props;
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const result = await stripe.confirmCardSetup('{{CLIENT_SECRET}}', {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Jenny Rosen',
},
}
});
if (result.error) {
// Display result.error.message in your UI.
} else {
// The setup has succeeded. Display a success message and send
// result.setupIntent.payment_method to your server to save the
// card to a Customer
}
};
render() {
return (
);
}
}
export default function InjectedCardSetupForm() {
return (
{({stripe, elements}) => (
)}
);
}
```
> `stripe.confirmCardSetup` may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.
The SetupIntent verifies that the card information your customer is using is valid on the network. However, automatic verification isn’t always performed. For details on this, see [Check if a card is valid without a charge](https://support.stripe.com/questions/check-if-a-card-is-valid-without-a-charge). Also, don’t maintain long-lived, unhandled SetupIntents as they may no longer be valid when you call [stripe.confirmCardSetup](https://docs.stripe.com/js.md#stripe-confirm-card-setup).
You now have a flow to collect card details and handle any authentication requests. Use the test card `4000 0025 0000 3155`, along with any CVC, postal code, and future expiration date to test the authentication process.
When saving cards to charge later, it’s important to get customer permission up front. Add text in your checkout flow that references the terms of the payment, for example:
> I authorise [your business name] to send instructions to the financial institution that issued my card to take payments from my card account in accordance with the terms of my agreement with you.
When the SetupIntent succeeds, the resulting *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) ID (in `result.setupIntent.payment_method`) will be saved to the provided *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments).
## Charge the saved card later [Server-side]
When you’re ready to charge your customer off-session, use the Customer and PaymentMethod IDs to create a PaymentIntent. To find a card to charge, [list](https://docs.stripe.com/api/payment_methods/list.md) the PaymentMethods associated with your Customer.
```curl
curl -G https://api.stripe.com/v1/payment_methods \
-u "<>:" \
-d "customer={{CUSTOMER_ID}}" \
-d type=card
```
When you have the Customer and PaymentMethod IDs, create a PaymentIntent with the amount and currency of the payment. Set a few other parameters to make the off-session payment:
- Set [off_session](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-off_session) to `true` to indicate that the customer isn’t in your checkout flow during this payment attempt. This causes the PaymentIntent to throw an error if authentication is required.
- Set the value of the PaymentIntent’s [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to `true`, which causes confirmation to occur immediately when the PaymentIntent is created.
- Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the ID of the PaymentMethod and [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the Customer.
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d amount=1099 \
-d currency=usd \-d customer="{{CUSTOMER_ID}}" \
-d payment_method="{{PAYMENT_METHOD_ID}}" \
-d off_session=true \
-d confirm=true
```
When a payment attempt fails, the request also fails with a 402 HTTP status code and the status of the PaymentIntent is *requires\_payment\_method* (This status appears as "requires_source" in API versions before 2019-02-11). You need to notify your customer to return to your application (for example, by sending an email or in-app notification) to complete the payment. Check the code of the [Error](https://docs.stripe.com/api/errors/handling.md) raised by the Stripe API library or check the [last_payment_error.decline_code](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-last_payment_error-decline_code) on the PaymentIntent to inspect why the card issuer declined the payment.
If the payment failed due to an [authentication_required](https://docs.stripe.com/declines/codes.md) decline code, use the declined PaymentIntent’s client secret and [payment method](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-last_payment_error-payment_method) with confirmCardPayment to allow the customer to authenticate the payment.
```javascript
// Pass the failed PaymentIntent to your client from your server
stripe.confirmCardPayment(intent.client_secret, {
payment_method: intent.last_payment_error.payment_method.id
}).then(function(result) {
if (result.error) {
// Show error to your customer
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
// The payment is complete!
}
}
});
```
> `stripe.confirmCardPayment` might take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.
If the payment failed for other reasons, such as insufficient funds on the card, send your customer to a payment page to enter a new card. You can reuse the existing PaymentIntent to attempt the payment again with the new card details.
## Test the integration
By this point you should have an integration that:
1. Collects and saves card details without charging the customer by using a SetupIntent
1. Charges the card off-session and has a recovery flow to handle declines and authentication requests
There are several test cards you can use to make sure this integration is ready for production. Use them with any CVC, postal code, and future expiration date.
| Number | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 4242424242424242 | Succeeds and immediately processes the payment. |
| 4000002500003155 | Requires authentication for the initial purchase, but succeeds for subsequent payments (including off-session ones) as long as the card is setup with `setup_future_usage`. |
| 4000002760003184 | Requires authentication for the initial purchase, and fails for subsequent payments (including off-session ones) with an `authentication_required` decline code. |
| 4000008260003178 | Requires authentication for the initial purchase, but fails for subsequent payments (including off-session ones) with an `insufficient_funds` decline code. |
| 4000000000009995 | Always fails (including the initial purchase) with a decline code of `insufficient_funds`. |
See the full list of [test cards](https://docs.stripe.com/testing.md).
# iOS
> This is a iOS for when platform is ios. View the full page at https://docs.stripe.com/payments/save-and-reuse-cards-only?platform=ios.
> We recommend that you follow the [Set up future payments](https://docs.stripe.com/payments/mobile/set-up-future-payments.md) guide. Only use this guide if you need to use manual server-side confirmation or your integration requires presenting payment methods separately. If you’ve already integrated with Elements, see the [Payment Element migration guide](https://docs.stripe.com/payments/payment-element/migration.md).
The [Setup Intents API](https://docs.stripe.com/api/setup_intents.md) lets you save a customer’s card without an initial payment. This is helpful if you want to onboard customers now, set them up for payments, and charge them in the future—when they’re offline.
Use this integration to set up recurring payments or to create one-time payments with a final amount determined later, often after the customer receives your service.
> The steps in this guide are fully implemented on GitHub. Clone the [repo](https://github.com/stripe-samples/mobile-saving-card-without-payment) and follow the [instructions](https://github.com/stripe-samples/mobile-saving-card-without-payment#how-to-run) to run the demo app.
## Set up Stripe
First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register).
### 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
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
### Client-side
The [Stripe iOS SDK](https://github.com/stripe/stripe-ios) is open source, [fully documented](https://stripe.dev/stripe-ios/index.html), 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.
1. Select the latest version number from our [releases page](https://github.com/stripe/stripe-ios/releases).
1. Add the **StripePaymentsUI** product to the [target of your app](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).
#### CocoaPods
1. If you haven’t already, install the latest version of [CocoaPods](https://guides.cocoapods.org/using/getting-started.html).
1. If you don’t have an existing [Podfile](https://guides.cocoapods.org/syntax/podfile.html), run the following command to create one:
```bash
pod init
```
1. Add this line to your `Podfile`:
```podfile
pod 'StripePaymentsUI'
```
1. Run the following command:
```bash
pod install
```
1. Don’t forget to use the `.xcworkspace` file to open your project in Xcode, instead of the `.xcodeproj` file, from here on out.
1. In the future, to update to the latest version of the SDK, run:
```bash
pod update StripePaymentsUI
```
#### Carthage
1. If you haven’t already, install the latest version of [Carthage](https://github.com/Carthage/Carthage#installing-carthage).
1. Add this line to your `Cartfile`:
```cartfile
github "stripe/stripe-ios"
```
1. Follow the [Carthage installation instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos). Make sure to embed all of the required frameworks listed [here](https://github.com/stripe/stripe-ios/tree/master/StripePaymentsUI/README.md#manual-linking).
1. In the future, to update to the latest version of the SDK, run the following command:
```bash
carthage update stripe-ios --platform ios
```
#### Manual Framework
1. Head to our [GitHub releases page](https://github.com/stripe/stripe-ios/releases/latest) and download and unzip **Stripe.xcframework.zip**.
1. Drag **StripePaymentsUI.xcframework** to the **Embedded Binaries** section of the **General** settings in your Xcode project. Make sure to select **Copy items if needed**.
1. Repeat step 2 for all required frameworks listed [here](https://github.com/stripe/stripe-ios/tree/master/StripePaymentsUI/README.md#manual-linking).
1. In the future, to update to the latest version of our SDK, repeat steps 1–3.
> For details on the latest SDK release and past versions, see the [Releases](https://github.com/stripe/stripe-ios/releases) page on GitHub. To receive notifications when a new release is published, [watch releases](https://help.github.com/en/articles/watching-and-unwatching-releases-for-a-repository#watching-releases-for-a-repository) for the repository.
Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/test/apikeys) on app start. This enables your app to make requests to the Stripe API.
#### Swift
```swift
import UIKitimportStripePaymentsUI
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {StripeAPI.defaultPublishableKey = "<>"
// do any other necessary launch configuration
return true
}
}
```
> Use your [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app.
## Create a Customer before setup [Server-side]
To set a card up 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. Customer objects allow for reusing payment methods and tracking across multiple payments.
> #### Compare Customers v1 and Accounts v2 references
>
> If your Connect platform uses [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer), use our [guide](https://docs.stripe.com/connect/use-accounts-as-customers.md) to replace `Customer` and event references in your code with the equivalent Accounts v2 API references.
```curl
curl https://api.stripe.com/v1/customers \
-u "<>:" \
-d "name=Jenny Rosen" \
--data-urlencode "email=jennyrosen@example.com"
```
Successful creation returns the [Customer](https://docs.stripe.com/api/customers/object.md) object. You can inspect the object for the customer `id` and store the value in your database for later retrieval.
You can find these customers in the [Customers](https://dashboard.stripe.com/customers) page in the Dashboard.
## Create a SetupIntent [Server-side]
A [SetupIntent](https://docs.stripe.com/api/setup_intents.md) is an object that represents your intent to set up a payment method for future payments.
The SetupIntent object contains a [client secret](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-client_secret), a unique key that you pass to your app. The client secret lets you perform certain actions on the client, such as confirming the setup and updating payment method details, while hiding sensitive information like `customer`. You can use the client secret to validate and authenticate card details using the credit card networks. The client secret is sensitive—don’t log it, embed it in URLs, or expose it to anyone but the customer.
### Server-side
On your server, make an endpoint that creates a SetupIntent and returns its client secret to your app.
#### curl
```bash
curl https://api.stripe.com/v1/setup_intents/ \
-u <>: \
-d "customer"="{{CUSTOMER_ID}}"
```
If you only plan on using the card for future payments when your customer is present during the checkout flow, set the [usage](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-usage) parameter to *on\_session* (A payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method) to improve authorization rates.
### Client-side
On the client, request a SetupIntent from your server.
#### Swift
```swift
import StripePaymentsUI
class CheckoutViewController: UIViewController {
var setupIntentClientSecret: String?
func startCheckout() {
// Request a SetupIntent from your server and store its client secret
// Click View full sample to see a complete implementation
}
}
```
## Collect card details [Client-side]
When the customer submits the payment form, collect card details from the customer using [STPPaymentCardTextField](https://stripe.dev/stripe-ios/stripe-payments-ui/Classes/STPPaymentCardTextField.html), a drop-in UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code.

> When saving card details to use for future off-session payments, especially in Europe because of regulations around card reuse, [get permission to save a card](https://docs.stripe.com/strong-customer-authentication.md#sca-enforcement). Include some text in your checkout flow to inform your user how you intend to use the card.
Pass the collected information into new [STPPaymentMethodCardParams](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentMethodCardParams.html) and [STPPaymentMethodBillingDetails](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentMethodBillingDetails.html) instances to create an `STPSetupIntentConfirmParams` instance.
#### Swift
```swift
class CheckoutViewController: UIViewController {
lazy var cardTextField: STPPaymentCardTextField = {
let cardTextField = STPPaymentCardTextField()
return cardTextField
}()
func pay() {
// Collect card details
let paymentMethodParams = cardTextField.paymentMethodParams
// Create SetupIntent confirm parameters with the above
let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: setupIntentClientSecret)
setupIntentParams.paymentMethodParams = paymentMethodParams
// ...continued in next step
}
}
```
To complete the setup, pass the `STPSetupIntentConfirmParams` object to the [confirmSetupIntent](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentHandler.html#/c:objc\(cs\)STPPaymentHandler\(im\)confirmSetupIntent:withAuthenticationContext:completion:) method on `STPPaymentHandler` [sharedManager](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentHandler.html#/c:objc\(cs\)STPPaymentHandler\(cm\)sharedHandler).
If the customer must perform additional steps to complete the payment, such as authentication, `STPPaymentHandler` presents view controllers using the [STPAuthenticationContext](https://stripe.dev/stripe-ios/stripe-payments/Protocols/STPAuthenticationContext.html) passed in and walks them through that process. See [Supporting 3D Secure Authentication on iOS](https://docs.stripe.com/payments/3d-secure.md?platform=ios) to learn more.
#### Swift
```swift
class CheckoutViewController: UIViewController {
// ...
func pay() {
// ...
// Complete the setup
let paymentHandler = STPPaymentHandler.shared()
paymentHandler.confirmSetupIntent(withParams: setupIntentParams, authenticationContext: self) { status, setupIntent, error in
switch (status) {
case .failed:
// Setup failed
break
case .canceled:
// Setup canceled
break
case .succeeded:
// Setup succeeded
break
@unknown default:
fatalError()
break
}
}
}
}
extension CheckoutViewController: STPAuthenticationContext {
func authenticationPresentingViewController() -> UIViewController {
return self
}
}
```
The SetupIntent verifies that the card information your customer is using is valid on the network. However, automatic verification isn’t always performed. For details on this, see [Check if a card is valid without a charge](https://support.stripe.com/questions/check-if-a-card-is-valid-without-a-charge). Also, don’t maintain long-lived, unhandled SetupIntents as they may no longer be valid when you call `confirmSetupIntent`.
You now have a flow to collect card details and handle any authentication requests. Use the test card `4000 0025 0000 3155`, along with any CVC, postal code, and future expiration date to test the authentication process.
When the SetupIntent succeeds, the resulting PaymentMethod ID (in `setupIntent.paymentMethodID`) will be saved to the provided Customer.
## Charge the saved card later [Server-side]
When you’re ready to charge your customer off-session, use the Customer and PaymentMethod IDs to create a PaymentIntent. To find a card to charge, [list](https://docs.stripe.com/api/payment_methods/list.md) the PaymentMethods associated with your Customer.
```curl
curl -G https://api.stripe.com/v1/payment_methods \
-u "<>:" \
-d "customer={{CUSTOMER_ID}}" \
-d type=card
```
When you have the Customer and PaymentMethod IDs, create a PaymentIntent with the amount and currency of the payment. Set a few other parameters to make the off-session payment:
- Set [off_session](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-off_session) to `true` to indicate that the customer isn’t in your checkout flow during this payment attempt. This causes the PaymentIntent to throw an error if authentication is required.
- Set the value of the PaymentIntent’s [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to `true`, which causes confirmation to occur immediately when the PaymentIntent is created.
- Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the ID of the PaymentMethod and [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the Customer.
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d amount=1099 \
-d currency=usd \-d customer="{{CUSTOMER_ID}}" \
-d payment_method="{{PAYMENT_METHOD_ID}}" \
-d off_session=true \
-d confirm=true
```
Inspect the [status property](https://docs.stripe.com/payments/paymentintents/lifecycle.md) of the PaymentIntent to confirm that the payment completed successfully. If the payment attempt succeeded, the PaymentIntent’s status is `succeeded` and the *off-session payment* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information) is complete.
### Start a recovery flow
If the PaymentIntent has any other status, the payment didn’t succeed and the request fails. Notify your customer to return to your application (for example, by email, text, push notification) to complete the payment. We recommend creating a recovery flow in your app that shows why the payment failed initially and lets your customer retry.
In your recovery flow, retrieve the PaymentIntent through its *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)). Check the PaymentIntent’s [lastPaymentError](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentIntent.html#/c:@M@StripePayments@objc\(cs\)STPPaymentIntent\(py\)lastPaymentError) to inspect why the payment attempt failed. For card errors, you can show the user the last payment error’s [message](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-last_payment_error-message). Otherwise, you can show a generic failure message.
#### Swift
```swift
func startRecoveryFlow(clientSecret: String) {
// Retrieve the PaymentIntent
STPAPIClient.shared.retrievePaymentIntent(withClientSecret: clientSecret) { (paymentIntent, error) in
guard error == nil, let lastPaymentError = paymentIntent?.lastPaymentError else {
// Handle error (for example, allow your customer to retry)
return
}
var failureReason = "Payment failed, try again." // Default to a generic error message
if lastPaymentError.type == .card {
failureReason = lastPaymentError.message
}
// Display the failure reason to your customer
// ...
}
}
```
### Let your customer try again
Give the customer the option to [update](https://docs.stripe.com/api/payment_methods/update.md) or [remove](https://docs.stripe.com/api/payment_methods/detach.md) their saved card and try payment again in your recovery flow. Follow the same steps you did to accept their initial payment with one difference—*confirm* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) the original, failed PaymentIntent by reusing its [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) instead of creating a new one.
If the payment failed because it requires authentication, try again with the existing *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) instead of creating a new one.
#### Swift
```swift
func startRecoveryFlow(clientSecret: String) {
// ...continued from previous step
// Reuse the existing PaymentIntent's client secret
let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
if paymentIntent.lastPaymentError.code == STPPaymentIntentLastPaymentErrorCodeAuthenticationFailure {
// Payment failed because authentication is required, reuse the PaymentMethod
paymentIntentParams.paymentMethodId = paymentIntent.lastPaymentError.paymentMethod.stripeId
} else {
// Collect a new PaymentMethod from the customer...
}
// Submit the payment...
}
```
## Test the integration
By this point you should have an integration that:
1. Collects and saves card details without charging the customer by using a SetupIntent
1. Charges the card off-session and has a recovery flow to handle declines and authentication requests
There are several test cards you can use to make sure this integration is ready for production. Use them with any CVC, postal code, and future expiration date.
| Number | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 4242424242424242 | Succeeds and immediately processes the payment. |
| 4000002500003155 | Requires authentication for the initial purchase, but succeeds for subsequent payments (including off-session ones) as long as the card is setup with `setup_future_usage`. |
| 4000002760003184 | Requires authentication for the initial purchase, and fails for subsequent payments (including off-session ones) with an `authentication_required` decline code. |
| 4000008260003178 | Requires authentication for the initial purchase, but fails for subsequent payments (including off-session ones) with an `insufficient_funds` decline code. |
| 4000000000009995 | Always fails (including the initial purchase) with a decline code of `insufficient_funds`. |
See the full list of [test cards](https://docs.stripe.com/testing.md).
# Android
> This is a Android for when platform is android. View the full page at https://docs.stripe.com/payments/save-and-reuse-cards-only?platform=android.
> We recommend that you follow the [Set up future payments](https://docs.stripe.com/payments/mobile/set-up-future-payments.md) guide. Only use this guide if you need to use manual server-side confirmation or your integration requires presenting payment methods separately. If you’ve already integrated with Elements, see the [Payment Element migration guide](https://docs.stripe.com/payments/payment-element/migration.md).
The [Setup Intents API](https://docs.stripe.com/api/setup_intents.md) lets you save a customer’s card without an initial payment. This is helpful if you want to onboard customers now, set them up for payments, and charge them in the future—when they’re offline.
Use this integration to set up recurring payments or to create one-time payments with a final amount determined later, often after the customer receives your service.
> The steps in this guide are fully implemented on GitHub. Clone the [repo](https://github.com/stripe-samples/mobile-saving-card-without-payment) and follow the [instructions](https://github.com/stripe-samples/mobile-saving-card-without-payment#how-to-run) to run the demo app.
## Set up Stripe
First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register).
### 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
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
### Client-side
The [Stripe Android SDK](https://github.com/stripe/stripe-android) is open source and [fully documented](https://stripe.dev/stripe-android/).
To install the SDK, add `stripe-android` to the `dependencies` block of your [app/build.gradle](https://developer.android.com/studio/build/dependencies) file:
#### Kotlin
```kotlin
plugins {
id("com.android.application")
}
android { ... }
dependencies {
// ...
// Stripe Android SDK
implementation("com.stripe:stripe-android:23.2.0")
// Include the financial connections SDK to support US bank account as a payment method
implementation("com.stripe:financial-connections:23.2.0")
}
```
> For details on the latest SDK release and past versions, see the [Releases](https://github.com/stripe/stripe-android/releases) page on GitHub. To receive notifications when a new release is published, [watch releases for the repository](https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/configuring-notifications#configuring-your-watch-settings-for-an-individual-repository).
Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/apikeys) so that it can make requests to the Stripe API, such as in your `Application` subclass:
#### Kotlin
```kotlin
import com.stripe.android.PaymentConfiguration
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
PaymentConfiguration.init(
applicationContext,
"<>"
)
}
}
```
> Use your [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app.
## Create a Customer before setup [Server-side]
To set a card up 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. Customer objects allow for reusing payment methods and tracking across multiple payments.
> #### Compare Customers v1 and Accounts v2 references
>
> If your Connect platform uses [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer), use our [guide](https://docs.stripe.com/connect/use-accounts-as-customers.md) to replace `Customer` and event references in your code with the equivalent Accounts v2 API references.
```curl
curl https://api.stripe.com/v1/customers \
-u "<>:" \
-d "name=Jenny Rosen" \
--data-urlencode "email=jennyrosen@example.com"
```
Successful creation returns the [Customer](https://docs.stripe.com/api/customers/object.md) object. You can inspect the object for the customer `id` and store the value in your database for later retrieval.
You can find these customers in the [Customers](https://dashboard.stripe.com/customers) page in the Dashboard.
## Create a SetupIntent [Server-side]
A [SetupIntent](https://docs.stripe.com/api/setup_intents.md) is an object that represents your intent to set up a payment method for future payments.
The SetupIntent object contains a [client secret](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-client_secret), a unique key that you pass to your app. The client secret lets you perform certain actions on the client, such as confirming the setup and updating payment method details, while hiding sensitive information like `customer`. You can use the client secret to validate and authenticate card details using the credit card networks. The client secret is sensitive—don’t log it, embed it in URLs, or expose it to anyone but the customer.
### Server-side
On your server, make an endpoint that creates a SetupIntent and returns its client secret to your app.
#### curl
```bash
curl https://api.stripe.com/v1/setup_intents/ \
-u <>: \
-d "customer"="{{CUSTOMER_ID}}"
```
If you only plan on using the card for future payments when your customer is present during the checkout flow, set the [usage](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-usage) parameter to *on\_session* (A payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method) to improve authorization rates.
### Client-side
On the client, request a SetupIntent from your server.
#### Kotlin
```kotlin
class CheckoutActivity : AppCompatActivity() {
private lateinit var setupIntentClientSecret: String
private fun loadPage() {
// Request a SetupIntent from your server and store its client secret
// Click View full sample to see a complete implementation
}
}
```
## Collect card details [Client-side]
When the customer submits the payment form, collect their card details using [CardInputWidget](https://stripe.dev/stripe-android/payments-core/com.stripe.android.view/-card-input-widget/index.html), a drop-in UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code.

> When saving card details to use for future off-session payments, especially in Europe because of regulations around card reuse, [get permission to save a card](https://docs.stripe.com/strong-customer-authentication.md#sca-enforcement). Include some text in your checkout flow to inform your user how you intend to use the card.
Call the `getPaymentMethodCard` method to retrieve the card details. Pass the collected information into new [PaymentMethodCreateParams](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-payment-method-create-params/index.html) and `PaymentMethod.BillingDetails` instances to create a `ConfirmSetupIntentParams` instance.
#### Kotlin
```kotlin
// Collect card details
val cardInputWidget =
findViewById(R.id.cardInputWidget)
val paymentMethodCard = cardInputWidget.paymentMethodCard
// Later, you will need to attach the PaymentMethod to the Customer it belongs to.
// This example collects the customer's email to know which customer the PaymentMethod belongs to, but your app might use an account id, session cookie, and so on.
val emailInput = findViewById(R.id.emailInput)
val billingDetails = PaymentMethod.BillingDetails.Builder()
.setEmail((emailInput.text ?: "").toString())
.build()
// Create SetupIntent confirm parameters with the above
if (paymentMethodCard != null) {
val paymentMethodParams = PaymentMethodCreateParams
.create(paymentMethodCard, billingDetails, null)
val confirmParams = ConfirmSetupIntentParams
.create(paymentMethodParams, setupIntentClientSecret)
lifecycleScope.launch {
paymentLauncher.confirm(confirmParams)
}
}
```
To complete the setup, pass the `SetupIntentParams` object with the current Activity to [PaymentLauncher confirm](https://stripe.dev/stripe-android/payments-core/com.stripe.android.payments.paymentlauncher/-payment-launcher/index.html#74063765%2FFunctions%2F-1622557690). The SetupIntent verifies that the card information your customer is using is valid on the network. However, automatic verification isn’t always performed. For details on this, see [Check if a card is valid without a charge](https://support.stripe.com/questions/check-if-a-card-is-valid-without-a-charge). Also, don’t maintain long-lived, unhandled SetupIntents as they may no longer be valid when you call `PaymentLauncher#confirm()`.
Some payment methods require [additional authentication steps](https://docs.stripe.com/payments/payment-intents/verifying-status.md#next-actions) to complete a payment. The SDK manages the payment confirmation and authentication flow, which might involve presenting additional screens required for authentication. For more information on *3D Secure* (3D Secure (3DS) provides an additional layer of authentication for credit card transactions that protects businesses from liability for fraudulent card payments) authentication and customizing the authentication experience, see [Supporting 3D Secure Authentication on Android](https://docs.stripe.com/payments/3d-secure.md?platform=android).
The result of the flow returns to your calling Activity through the callback provided, here `onPaymentResult`. The [PaymentResult](https://stripe.dev/stripe-android/payments-core/com.stripe.android.payments.paymentlauncher/-payment-result/index.html) returned by the [PaymentLauncher](https://stripe.dev/stripe-android/payments-core/com.stripe.android.payments.paymentlauncher/-payment-launcher/index.html) has these types:
- `Completed` - confirmation or authentication succeeded
- `Canceled` - the customer canceled required authentication
- `Failed` - the authentication attempt failed, a reason is provided by the [throwable](https://stripe.dev/stripe-android/payments-core/com.stripe.android.payments.paymentlauncher/-payment-result/-failed/index.html#257609069%2FProperties%2F-1622557690)
#### Kotlin
```kotlin
private fun onPaymentResult(paymentResult: PaymentResult) {
var title = ""
var message = ""
var restartDemo = false
when (paymentResult) {
is PaymentResult.Completed -> {
title = "Setup Completed"
restartDemo = true
}
is PaymentResult.Canceled -> {
title = "Setup Canceled"
}
is PaymentResult.Failed -> {
title = "Setup Failed"
message = paymentResult.throwable.message!!
}
}
displayAlert(title, message, restartDemo)
}
```
You now have a flow to collect card details and handle any authentication requests. Use the test card `4000 0025 0000 3155`, along with any CVC, postal code, and future expiration date to test the authentication process.
When the SetupIntent succeeds, the resulting PaymentMethod ID (in `setupIntent.getPaymentMethodId()`) will be saved to the provided Customer.
## Charge the saved card later [Server-side]
When you’re ready to charge your customer off-session, use the Customer and PaymentMethod IDs to create a PaymentIntent. To find a card to charge, [list](https://docs.stripe.com/api/payment_methods/list.md) the PaymentMethods associated with your Customer.
```curl
curl -G https://api.stripe.com/v1/payment_methods \
-u "<>:" \
-d "customer={{CUSTOMER_ID}}" \
-d type=card
```
When you have the Customer and PaymentMethod IDs, create a PaymentIntent with the amount and currency of the payment. Set a few other parameters to make the off-session payment:
- Set [off_session](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-off_session) to `true` to indicate that the customer isn’t in your checkout flow during this payment attempt. This causes the PaymentIntent to throw an error if authentication is required.
- Set the value of the PaymentIntent’s [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to `true`, which causes confirmation to occur immediately when the PaymentIntent is created.
- Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the ID of the PaymentMethod and [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the Customer.
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d amount=1099 \
-d currency=usd \-d customer="{{CUSTOMER_ID}}" \
-d payment_method="{{PAYMENT_METHOD_ID}}" \
-d off_session=true \
-d confirm=true
```
Inspect the [status property](https://docs.stripe.com/payments/paymentintents/lifecycle.md) of the PaymentIntent to confirm that the payment completed successfully. If the payment attempt succeeded, the PaymentIntent’s status is `succeeded` and the *off-session payment* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information) is complete.
### Start a recovery flow
If the PaymentIntent has any other status, the payment didn’t succeed and the request fails. Notify your customer to return to your application (for example, by email, text, push notification) to complete the payment. We recommend creating a recovery flow in your app that shows why the payment failed initially and lets your customer retry.
In your recovery flow, retrieve the PaymentIntent through its *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)). Check the PaymentIntent’s [lastPaymentError](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-payment-intent/index.html#com.stripe.android.model/PaymentIntent/lastPaymentError/#/PointingToDeclaration/) to inspect why the payment attempt failed. For card errors, you can show the user the last payment error’s [message](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-last_payment_error-message). Otherwise, you can show a generic failure message.
#### Kotlin
```kotlin
fun startRecoveryFlow(clientSecret: String) {
val stripe = Stripe(
applicationContext,
PaymentConfiguration.getInstance(applicationContext).publishableKey
)
lifecycleScope.launch {
runCatching {
stripe.retrievePaymentIntent(clientSecret)
}.fold(
onSuccess = { paymentIntent ->
var failureReason =
"Payment failed, try again" // Default to a generic error message
paymentIntent.lastPaymentError?.let { lastPaymentError ->
if (lastPaymentError.type == PaymentIntent.Error.Type.CardError) {
lastPaymentError.message?.let { errorMessage ->
failureReason = errorMessage
// Display the failure reason to your customer
}
}
}
},
onFailure = {
// Handle error
}
)
}
}
```
### Let your customer try again
Give the customer the option to [update](https://docs.stripe.com/api/payment_methods/update.md) or [remove](https://docs.stripe.com/api/payment_methods/detach.md) their saved card and try payment again in your recovery flow. Follow the same steps you did to accept their initial payment with one difference—*confirm* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) the original, failed PaymentIntent by reusing its [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) instead of creating a new one.
If the payment failed because it requires authentication, try again with the existing *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) instead of creating a new one.
#### Kotlin
```kotlin
fun startRecoveryFlow(clientSecret: String) {
// ...continued from previous step
val paymentConfiguration = PaymentConfiguration.getInstance(applicationContext)
val paymentLauncher = PaymentLauncher.Companion.create(
this,
paymentConfiguration.publishableKey,
paymentConfiguration.stripeAccountId,
::onPaymentResult
)
val lastPaymentError = paymentIntent.lastPaymentError
val lastPaymentMethodId = lastPaymentError.paymentMethod?.id
if (lastPaymentError?.code == "authentication_required" && lastPaymentMethodId != null) {
// Payment failed because authentication is required, reuse the PaymentMethod
val paymentIntentParams =
ConfirmPaymentIntentParams.createWithPaymentMethodId(
lastPaymentMethodId,
clientSecret // Reuse the existing PaymentIntent
)
// Submit the payment...
paymentLauncher.confirm(paymentIntentParams)
} else {
// Collect a new PaymentMethod from the customer...
}
}
```
## Test the integration
By this point you should have an integration that:
1. Collects and saves card details without charging the customer by using a SetupIntent
1. Charges the card off-session and has a recovery flow to handle declines and authentication requests
There are several test cards you can use to make sure this integration is ready for production. Use them with any CVC, postal code, and future expiration date.
| Number | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 4242424242424242 | Succeeds and immediately processes the payment. |
| 4000002500003155 | Requires authentication for the initial purchase, but succeeds for subsequent payments (including off-session ones) as long as the card is setup with `setup_future_usage`. |
| 4000002760003184 | Requires authentication for the initial purchase, and fails for subsequent payments (including off-session ones) with an `authentication_required` decline code. |
| 4000008260003178 | Requires authentication for the initial purchase, but fails for subsequent payments (including off-session ones) with an `insufficient_funds` decline code. |
| 4000000000009995 | Always fails (including the initial purchase) with a decline code of `insufficient_funds`. |
See the full list of [test cards](https://docs.stripe.com/testing.md).
# React Native
> This is a React Native for when platform is react-native. View the full page at https://docs.stripe.com/payments/save-and-reuse-cards-only?platform=react-native.
> We recommend that you follow the [Set up future payments](https://docs.stripe.com/payments/mobile/set-up-future-payments.md) guide. Only use this guide if you need to use manual server-side confirmation or your integration requires presenting payment methods separately. If you’ve already integrated with Elements, see the [Payment Element migration guide](https://docs.stripe.com/payments/payment-element/migration.md).
The [Setup Intents API](https://docs.stripe.com/api/setup_intents.md) lets you save a customer’s card without an initial payment. This is helpful if you want to onboard customers now, set them up for payments, and charge them in the future—when they’re offline.
Use this integration to set up recurring payments or to create one-time payments with a final amount determined later, often after the customer receives your service.
## 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
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
### Client-side
The [React Native SDK](https://github.com/stripe/stripe-react-native) is open source and fully documented. Internally, it uses the [native iOS](https://github.com/stripe/stripe-ios) and [Android](https://github.com/stripe/stripe-android) SDKs. To install Stripe’s React Native SDK, run one of the following commands in your project’s directory (depending on which package manager you use):
#### yarn
```bash
yarn add @stripe/stripe-react-native
```
#### npm
```bash
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](https://reactnative.dev/docs/typescript#adding-typescript-to-an-existing-project) 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](https://docs.stripe.com/keys.md#obtain-api-keys) in `publishableKey` is required. The following example shows how to initialize Stripe using the `StripeProvider` component.
```jsx
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 (
{/* Your app code here */}
);
}
```
> Use your API [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app.
## Create a Customer before setup [Server-side]
To set a card up 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. Customer objects allow for reusing payment methods and tracking across multiple payments.
> #### Compare Customers v1 and Accounts v2 references
>
> If your Connect platform uses [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer), use our [guide](https://docs.stripe.com/connect/use-accounts-as-customers.md) to replace `Customer` and event references in your code with the equivalent Accounts v2 API references.
```curl
curl https://api.stripe.com/v1/customers \
-u "<>:" \
-d "name=Jenny Rosen" \
--data-urlencode "email=jennyrosen@example.com"
```
Successful creation returns the [Customer](https://docs.stripe.com/api/customers/object.md) object. You can inspect the object for the customer `id` and store the value in your database for later retrieval.
You can find these customers in the [Customers](https://dashboard.stripe.com/customers) page in the Dashboard.
## Create a SetupIntent [Server-side]
A [SetupIntent](https://docs.stripe.com/api/setup_intents.md) is an object that represents your intent to set up a payment method for future payments. The SetupIntent object contains a [client secret](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-client_secret), a unique key that you pass to your app.
The client secret lets you perform certain actions on the client, such as confirming the setup and updating payment method details, while hiding sensitive information like `customer`. You can use the client secret to validate and authenticate card details using the credit card networks. The client secret is sensitive—don’t log it, embed it in URLs, or expose it to anyone but the customer.
### Server-side
On your server, make an endpoint that creates a SetupIntent and returns its client secret to your app.
#### curl
```bash
curl https://api.stripe.com/v1/setup_intents/ \
-u <>: \
-d "customer"="{{CUSTOMER_ID}}"
```
If you only plan on using the card for future payments when your customer is present during the checkout flow, set the [usage](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-usage) parameter to *on\_session* (A payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method) to improve authorization rates.
## Collect card details [Client-side]
Securely collect card information on the client with `CardField`, a UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code.

Add the `CardField` component to your payment screen to securely collect card details from your customers. Use the `onCardChange` callback to inspect non-sensitive information about the card, like the brand, and whether the details are complete.
```javascript
import { CardField, useStripe } from '@stripe/stripe-react-native';
function PaymentScreen() {
// ...
return (
{
console.log('cardDetails', cardDetails);
}}
onFocus={(focusedField) => {
console.log('focusField', focusedField);
}}
/>
);
}
```
> When saving card details to use for future off-session payments, especially in Europe because of regulations around card reuse, [get permission to save a card](https://docs.stripe.com/strong-customer-authentication.md#sca-enforcement). Include text in your checkout flow to inform your customer how you intend to use the card.
To complete the setup, pass the customer card and billing information to `confirmSetupIntent`. You can access this method using either the `useConfirmSetupIntent` or `useStripe` hook.
```javascript
function PaymentScreen() {
// ...
const { confirmSetupIntent, loading } = useConfirmSetupIntent();
// ...
const handlePayPress = async () => {
// Gather the customer's billing information (for example, email)
const billingDetails: BillingDetails = {
email: 'jenny.rosen@example.com',
};
// Create a setup intent on the backend
const clientSecret = await createSetupIntentOnBackend();
const { setupIntent, error } = await confirmSetupIntent(clientSecret, {
paymentMethodType: 'Card',
paymentMethodData: {
billingDetails,
}
});
if (error) {
//Handle the error
}
// ...
};
return (
// ...
);
}
```
Some payment methods require [additional authentication steps](https://docs.stripe.com/payments/payment-intents/verifying-status.md#next-actions) to complete a payment. The SDK manages the payment confirmation and authentication flow, which might involve presenting additional screens required for authentication. To test the authentication process, use the test card `4000 0025 0000 3155` along with any CVC, postal code, and future expiration date.
When the `SetupIntent` succeeds, the resulting PaymentMethod ID (in `setupIntent.paymentMethodID`) is saved to the provided `Customer`.
## Charge the saved card later [Server-side]
When you’re ready to charge your customer off-session, use the Customer and PaymentMethod IDs to create a PaymentIntent. To find a card to charge, [list](https://docs.stripe.com/api/payment_methods/list.md) the PaymentMethods associated with your Customer.
```curl
curl -G https://api.stripe.com/v1/payment_methods \
-u "<>:" \
-d "customer={{CUSTOMER_ID}}" \
-d type=card
```
When you have the Customer and PaymentMethod IDs, create a PaymentIntent with the amount and currency of the payment. Set a few other parameters to make the off-session payment:
- Set [off_session](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-off_session) to `true` to indicate that the customer isn’t in your checkout flow during this payment attempt. This causes the PaymentIntent to throw an error if authentication is required.
- Set the value of the PaymentIntent’s [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to `true`, which causes confirmation to occur immediately when the PaymentIntent is created.
- Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the ID of the PaymentMethod and [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the Customer.
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d amount=1099 \
-d currency=usd \-d customer="{{CUSTOMER_ID}}" \
-d payment_method="{{PAYMENT_METHOD_ID}}" \
-d off_session=true \
-d confirm=true
```
### Start a recovery flow
If the PaymentIntent has any other status, the payment didn’t succeed and the request fails. Notify your customer to return to your application (for example, by email, text, push notification) to complete the payment. We recommend creating a recovery flow in your app that shows why the payment failed initially and lets your customer retry it.
In your recovery flow, retrieve the PaymentIntent using its *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)). Check the PaymentIntent’s `lastPaymentError` to inspect why the payment attempt failed. For card errors, you can show the user the last [message](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-last_payment_error-message) for the payment error. Otherwise, you can show a generic failure message.
```javascript
function PaymentScreen() {
// ...
const {retrievePaymentIntent} = useStripe();
// ...
const handleRecoveryFlow = async () => {
const {paymentIntent, error} = await retrievePaymentIntent(clientSecret);
if (error) {
Alert.alert(`Error: ${error.code}`, error.message);
} else if (paymentIntent) {
// Default to a generic error message
let failureReason = 'Payment failed, try again.';
if (paymentIntent.lastPaymentError.type === 'Card') {
failureReason = paymentIntent.lastPaymentError.message;
}
}
};
return (
// ...
);
}
```
### Let your customer try again
Give the customer the option to [update](https://docs.stripe.com/api/payment_methods/update.md) or [remove](https://docs.stripe.com/api/payment_methods/detach.md) their saved card and try payment again in your recovery flow. Follow the same steps you did to accept their initial payment with one difference—*confirm* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) the original, failed PaymentIntent by reusing its [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) instead of creating a new one.
If the payment failed because it requires authentication, try again with the existing PaymentMethod instead of creating a new one.
```javascript
function PaymentScreen() {
// ...
const {retrievePaymentIntent} = useStripe();
// ...
const handleRecoveryFlow = async () => {
const {paymentIntent, error} = await retrievePaymentIntent(clientSecret);
if (error) {
Alert.alert(`Error: ${error.code}`, error.message);
} else if (paymentIntent) {
// Default to a generic error message
let failureReason = 'Payment failed, try again.';
if (paymentIntent.lastPaymentError.type === 'Card') {
failureReason = paymentIntent.lastPaymentError.message;
}
// If the last payment error is authentication_required, let the customer
// complete the payment without asking them to reenter their details.
if (paymentIntent.lastPaymentError?.code === 'authentication_required') {
// Let the customer complete the payment with the existing PaymentMethod
const {error} = await confirmPayment(paymentIntent.clientSecret, {
paymentMethodType: 'Card',
paymentMethodData: {
billingDetails,
paymentMethodId: paymentIntent.lastPaymentError?.paymentMethod.id,
},
});
if (error) {
// handle error
}
} else {
// Collect a new PaymentMethod from the customer
}
}
};
return (
// ...
);
}
```
## Test the integration
By this point you should have an integration that:
1. Collects and saves card details without charging the customer by using a SetupIntent
1. Charges the card off-session and has a recovery flow to handle declines and authentication requests
There are several test cards you can use to make sure this integration is ready for production. Use them with any CVC, postal code, and future expiration date.
| Number | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 4242424242424242 | Succeeds and immediately processes the payment. |
| 4000002500003155 | Requires authentication for the initial purchase, but succeeds for subsequent payments (including off-session ones) as long as the card is setup with `setup_future_usage`. |
| 4000002760003184 | Requires authentication for the initial purchase, and fails for subsequent payments (including off-session ones) with an `authentication_required` decline code. |
| 4000008260003178 | Requires authentication for the initial purchase, but fails for subsequent payments (including off-session ones) with an `insufficient_funds` decline code. |
| 4000000000009995 | Always fails (including the initial purchase) with a decline code of `insufficient_funds`. |
See the full list of [test cards](https://docs.stripe.com/testing.md).