Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management

Set up future card payments

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

Warning

We recommend that you follow the Set up future payments 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.

The Setup Intents API 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.

Note

The steps in this guide are fully implemented on GitHub. Clone the repo and follow the instructions to run the demo app.

Set up Stripe

First, you need a Stripe account. Register now.

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:

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

Client-side

The Stripe Android SDK is open source and fully documented.

To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

build.gradle.kts
Kotlin
plugins { id("com.android.application") } android { ... } dependencies { // ... // Stripe Android SDK implementation("com.stripe:stripe-android:21.15.0") // Include the financial connections SDK to support US bank account as a payment method implementation("com.stripe:financial-connections:21.15.0") }

Note

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

Kotlin
import com.stripe.android.PaymentConfiguration class MyApp : Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext,
"pk_test_TYooMQauvdEDq54NiTphI7jx"
) } }

Note

Use your test keys while you test and develop, and your live mode 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. 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.

Command Line
cURL
curl https://api.stripe.com/v1/customers \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d name="Jenny Rosen" \ --data-urlencode email="jennyrosen@example.com"

Successful creation returns the Customer 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 page in the Dashboard.

Create a SetupIntent
Server-side

A SetupIntent is an object that represents your intent to set up a payment method for future payments.

The SetupIntent object contains a 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.

Command Line
curl
curl https://api.stripe.com/v1/setup_intents/ \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -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 parameter to on_session to improve authorization rates.

Client-side

On the client, request a SetupIntent from your server.

CheckoutActivity.kt
Kotlin
View full sample
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, a drop-in UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code.

CardInputWidget performs real-time validation and formatting.

Caution

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. 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 and PaymentMethod.BillingDetails instances to create a ConfirmSetupIntentParams instance.

MyPaymentActivity.kt
Kotlin
View full sample
// Collect card details val cardInputWidget = findViewById<CardInputWidget>(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<EditText>(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. 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. 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 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 authentication and customizing the authentication experience, see Supporting 3D Secure Authentication on Android.

The result of the flow returns to your calling Activity through the callback provided, here onPaymentResult. The PaymentResult returned by the PaymentLauncher 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
MyPaymentActivity.kt
Kotlin
View full sample
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 are ready to charge your customer off-session, use the Customer and PaymentMethod IDs to create a PaymentIntent. To find a card to charge, list the PaymentMethods associated with your Customer.

Command Line
cURL
curl -G https://api.stripe.com/v1/payment_methods \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -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 to true to indicate that the customer is not 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 property to true, which causes confirmation to occur immediately when the PaymentIntent is created.
  • Set payment_method to the ID of the PaymentMethod and customer to the ID of the Customer.
Command Line
curl
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -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 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 is complete.

Start a recovery flow

If the PaymentIntent has any other status, the payment did not 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. Check the PaymentIntent’s lastPaymentError to inspect why the payment attempt failed. For card errors, you can show the user the last payment error’s message. Otherwise, you can show a generic failure message.

RecoveryFlowActivity.kt
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 or remove 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 the original, failed PaymentIntent by reusing its 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.

RecoveryFlowActivity.kt
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
  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.

NumberDescription
Succeeds and immediately processes the payment.
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.
Requires authentication for the initial purchase, and fails for subsequent payments (including off-session ones) with an authentication_required decline code.
Requires authentication for the initial purchase, but fails for subsequent payments (including off-session ones) with an insufficient_funds decline code.
Always fails (including the initial purchase) with a decline code of insufficient_funds.

See the full list of test cards.

Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc