# Set up future card payments

Use manual server-side confirmation or present payment methods separately.

# React Native


> We recommend that you follow the [Accept in-app payments](https://docs.stripe.com/payments/mobile/accept-payment.md?platform=react-native&type=setup) 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 (
    <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](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 up a payment method for future payments, you must attach it to an object that represents your customer. When your customer creates an account or has their first transaction with your business, create either a customer-configured [Account](https://docs.stripe.com/api/v2/core/accounts/create.md) object with the Accounts v2 API or a [Customer](https://docs.stripe.com/api/customers/create.md) object with the Customers API.

#### Accounts v2

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-08-26.preview" \
  --json '{
    "contact_email": "jenny.rosen@example.com",
    "display_name": "Jenny Rosen",
    "configuration": {
        "customer": {}
    },
    "include": [
        "configuration.customer"
    ]
  }'
```

Successful creation returns the customer-configured [Account](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-customer) object. Inspect the object for the customer’s `id` and store the value in your database for later retrieval.

#### Customers v1

```curl
curl https://api.stripe.com/v1/customers \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "name=Jenny Rosen" \
  --data-urlencode "email=jennyrosen@example.com"
```

Successful creation returns the [Customer](https://docs.stripe.com/api/customers/object.md) object. Inspect the object for the customer’s `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 <<YOUR_SECRET_KEY>>: \
  -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.
![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/ios/card-field.mp4)
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 (
    <View>
      <CardField
        postalCodeEnabled={true}
        placeholders={{
          number: '4242 4242 4242 4242',
        }}
        cardStyle={{
          backgroundColor: '#FFFFFF',
          textColor: '#000000',
        }}
        style={{
          width: '100%',
          height: 50,
          marginVertical: 30,
        }}
        onCardChange={(cardDetails) => {
          console.log('cardDetails', cardDetails);
        }}
        onFocus={(focusedField) => {
          console.log('focusField', focusedField);
        }}
      />
    </View>
  );
}
```

> 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 (
    <View>
      // ...
      <Button onPress={handlePayPress} title="Save" loading={loading} />
    </View>
  );
}
```

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.

#### Accounts v2

```curl
curl -G https://api.stripe.com/v1/payment_methods \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "customer_account={{CUSTOMERACCOUNT_ID}}" \
  -d type=card
```

#### Customers v1

```curl
curl -G https://api.stripe.com/v1/payment_methods \
  -u "<<YOUR_SECRET_KEY>>:" \
  -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 <<YOUR_SECRET_KEY>>: \
  -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 (
    <View>
      // ...
      <Button
        onPress={handleRecoveryFlow}
        title="Recovery flow"
        loading={loading}
      />
    </View>
  );
}
```

### 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 (
    <View>
      // ...
      <Button
        onPress={handleRecoveryFlow}
        title="Recovery flow"
        loading={loading}
      />
    </View>
  );
}
```

## 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
2. 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).

