# Accept in-app payments Build a customized payments integration in your iOS, Android, or React Native app using the Payment Sheet. The Payment Sheet is a customizable component that displays a list of payment methods and collects payment details in your app using a bottom sheet. > #### Accounts v2 API support > > The Payment Sheet doesn’t support *customer-configured Accounts* (Account configurations represent role-based functionality that you can enable for accounts, such as merchant, customer, or recipient). It only supports `Customer` objects. # Collect and save a payment method A SetupIntent flow allows you to collect payment method details and save them for future payments without creating a charge. In this integration, you build a custom flow where you render the Payment Element, create the *SetupIntent* (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), and confirm saving the payment method in your app. ## Set up Stripe [Server-side] [Client-side] 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 the 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.9.1") // Include the financial connections SDK to support US bank account as a payment method implementation("com.stripe:financial-connections:23.9.1") } ``` > 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). You also need to set your [publishable key](https://dashboard.stripe.com/apikeys) so that the SDK can make API calls to Stripe. To get started quickly, you can hardcode this on the client while you’re integrating, but fetch the publishable key from your server in production. ```kotlin // Set your publishable key: remember to change this to your live publishable key in production // See your keys here: https://dashboard.stripe.com/apikeys PaymentConfiguration.init(context, publishableKey = "<>") ``` ## Enable payment methods View your [payment methods settings](https://dashboard.stripe.com/settings/payment_methods) and enable the payment methods you want to support. You need at least one payment method enabled to create a *SetupIntent* (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). By default, Stripe enables cards and other prevalent payment methods that can help you reach more customers, but we recommend turning on additional payment methods that are relevant for your business and customers. See [Payment method support](https://docs.stripe.com/payments/payment-methods/payment-method-support.md) for product and payment method support, and our [pricing page](https://stripe.com/pricing/local-payment-methods) for fees. ## Create a Customer [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. > #### Use the Accounts v2 API to represent customers > > The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a [specify a preview version](https://docs.stripe.com/api-v2-overview.md#sdk-and-api-versioning) in your code. > > To request access to the Accounts v2 preview, > > For most use cases, we recommend [modeling your customers as customer-configured Account objects](https://docs.stripe.com/accounts-v2/use-accounts-as-customers.md) instead of using [Customer](https://docs.stripe.com/api/customers.md) objects. ```curl curl -X POST https://api.stripe.com/v1/customers \ -u "<>:" ``` ## Collect payment details [Client-side] We offer two styles of integration. | PaymentSheet | PaymentSheet.FlowController | | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ![PaymentSheet](https://b.stripecdn.com/docs-statics-srv/assets/android-overview.471eaf89a760f5b6a757fd96b6bb9b60.png) | ![PaymentSheet.FlowController](https://b.stripecdn.com/docs-statics-srv/assets/android-multi-step.84d8a0a44b1baa596bda491322b6d9fd.png) | | Displays a sheet to collect and save payment details. The button label is **Set up**. Clicking it saves the payment details. | Displays a sheet to only collect payment details. The button label is **Continue**. Clicking it returns the customer to your app, where your own button saves the payment details. | #### PaymentSheet #### Views (Classic) ### Initialize the PaymentSheet Initialize the PaymentSheet and pass in a `CreateIntentCallback`. Leave the implementations empty for now. ```kotlin class MyCheckoutActivity : AppCompatActivity() { private lateinit var paymentSheet: PaymentSheet override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... paymentSheet = PaymentSheet.Builder(::onPaymentSheetResult) .createIntentCallback { confirmationToken -> TODO() // You'll implement this later } .build(this) } fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { // You'll implement this later } } ``` ### Present the PaymentSheet Next, present the PaymentSheet by calling `presentWithIntentConfiguration()` and pass in an [IntentConfiguration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-intent-configuration/index.html). The `IntentConfiguration` contains details about the specific `SetupIntent`, such as the currency. ```kotlin class MyCheckoutActivity : AppCompatActivity() { // ... private fun handleCheckoutButtonPressed() {val intentConfig = PaymentSheet.IntentConfiguration(mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = "usd", ),// Other configuration options... ) paymentSheet.presentWithIntentConfiguration( intentConfiguration = intentConfig, // Optional configuration - See the "Customize the sheet" section in this guide configuration = PaymentSheet.Configuration.Builder( merchantDisplayName = "Example Inc.", ).build() ) } } ``` ### Confirm the Intent When your customer taps the **Setup** button in PaymentSheet, it calls the `CreateIntentCallback` you passed above with a [ConfirmationToken](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-confirmation-token/index.html) that represents your customer’s payment details and preferences. Implement this method to send a request to your server with `confirmationToken.id`. Your server creates a SetupIntent and returns its client secret. When you receive the response, return the response’s client secret or an error. The PaymentSheet confirms the SetupIntent using the client secret, or displays the error in its UI. ```kotlin class MyCheckoutActivity : AppCompatActivity() { private lateinit var paymentSheet: PaymentSheet override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... paymentSheet = PaymentSheet.Builder(::onPaymentSheetResult) .createIntentCallback { confirmationToken ->// Make a request to your server to create aSetupIntentand return its client secret val networkResult = myNetworkClient.createIntent( confirmationTokenId = confirmationToken.id, ) if (networkResult.isSuccess) { CreateIntentResult.Success(networkResult.clientSecret) } else { CreateIntentResult.Failure(networkResult.exception) } } .build(this) } fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { // You'll implement this later } } ``` After your customer saves a payment method, the sheet closes, and PaymentSheet invokes the [PaymentSheetResultCallback](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result-callback/index.html) you provide with a [PaymentSheetResult](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result/index.html). ```kotlin class MyCheckoutActivity : AppCompatActivity() { // ... fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {when(paymentSheetResult) { is PaymentSheetResult.Canceled -> { // Customer canceled - you should probably do nothing. } is PaymentSheetResult.Failed -> { print("Error: ${paymentSheetResult.error}") // PaymentSheet encountered an unrecoverable error. You can display the error to the user, log it, and so on } is PaymentSheetResult.Completed -> { // Display, for example, an order confirmation screen print("Completed") } } } } ``` #### Jetpack Compose ### Initialize the PaymentSheet Initialize the PaymentSheet using `remember` and pass in `PaymentSheet.Builder`. Leave the implementation of `resultCallback` and `createIntentCallback` empty for now. ```kotlin import androidx.compose.runtime.* import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.rememberPaymentSheet @Composable fun CheckoutScreen() { val paymentSheet = remember { Builder( resultCallback = { paymentSheetResult -> // You'll implement this later } ).createIntentCallback { confirmationToken -> // You'll implement this later } }.build() } ``` ### Present the PaymentSheet Next, present the PaymentSheet by calling `presentWithIntentConfiguration()` and pass an [IntentConfiguration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-intent-configuration/index.html). The `IntentConfiguration` contains details about the specific `SetupIntent`, such as the currency. ```kotlin @Composable fun CheckoutScreen() { val paymentSheet = remember { ... }.build() Button( onClick = { val intentConfig = PaymentSheet.IntentConfiguration( mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = "usd", ), ) } ) { Text("Checkout") } } ``` ### Confirm the Intent When your customer taps the **Setup** button in the PaymentSheet, it calls `createIntentCallback` with a [ConfirmationToken](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-confirmation-token/index.html) object that represents the customer’s payment details and preferences. Implement this callback to send a request to your server with `confirmationToken.id`. Your server creates a SetupIntent and returns its client secret. When you receive the response, return the client secret or an error. The PaymentSheet confirms the SetupIntent using the client secret, or displays the error in its UI. ```kotlin @Composable fun CheckoutScreen() { val paymentSheet = remember { Builder( resultCallback = { paymentSheetResult -> // You'll implement this later } ).createIntentCallback { confirmationToken -> // Make a request to your server to create a PaymentIntent and return its client secret try { val response = myNetworkClient.createIntent( confirmationTokenId = confirmationToken.id, ) CreateIntentResult.Success(response.clientSecret) } catch (e: Exception) { CreateIntentResult.Failure( cause = e, displayMessage = e.message ) } } }.build() Button( onClick = { val intentConfig = PaymentSheet.IntentConfiguration( mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = "usd", ), ) paymentSheet.presentWithIntentConfiguration( intentConfiguration = intentConfig, configuration = PaymentSheet.Configuration.Builder( merchantDisplayName = "Example Inc.", ).build() ) } ) { Text("Checkout") } } ``` After your customer saves a payment method, the sheet closes, and the callback you pass to `PaymentSheet.Builder.resultCallback` is invoked with a [PaymentSheetResult](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result/index.html). ```kotlin @Composable fun CheckoutScreen() { val paymentSheet = remember { Builder( resultCallback = { paymentSheetResult -> when(paymentSheetResult) { is PaymentSheetResult.Canceled -> { // Customer canceled - you should probably do nothing. } is PaymentSheetResult.Failed -> { println("Error: ${paymentSheetResult.error}") // PaymentSheet encountered an unrecoverable error. You can display the error to the user, log it, and so on } is PaymentSheetResult.Completed -> { // Display, for example, an order confirmation screen println("Completed") } } } ).createIntentCallback { confirmationToken -> ... // previously implemented confirm intent code } }.build() ... // other code } ``` #### PaymentSheet.FlowController This integration assumes your checkout screen has two buttons: a **Payment Method** button that presents the PaymentSheet to collect payment details, and a **Set up** button that sets up the payment details for future payment usage. #### Views (Classic) ### Initialize the PaymentSheet Initialize the `PaymentSheet.FlowController` and pass in a `CreateIntentCallback`. Leave the implementations empty for now. ```kotlin class MyCheckoutActivity : AppCompatActivity() { private lateinit var flowController: PaymentSheet.FlowController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... flowController = PaymentSheet.FlowController.Builder( resultCallback = ::onPaymentSheetResult, paymentOptionCallback = ::onPaymentOption ) .createIntentCallback { confirmationToken -> TODO() // You'll implement this later } .build(this) } fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { // Explained later } fun onPaymentOption(paymentOption: PaymentOption?) { // Explained later } } ``` After your checkout screen loads, configure the `PaymentSheet.FlowController` with an [IntentConfiguration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-intent-configuration/index.html). The `IntentConfiguration` contains details about the specific `SetupIntent`, such as the currency. ```kotlin fun handleCheckoutLoaded(currency: String) {flowController.configureWithIntentConfiguration( intentConfiguration = PaymentSheet.IntentConfiguration(mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = currency, ),), // Optional configuration - See the "Customize the sheet" section in this guide configuration = PaymentSheet.Configuration.Builder( merchantDisplayName = "Example Inc.", ).build(), callback = { success, error -> // If success, the FlowController was initialized correctly. // Use flowController.getPaymentOption() to populate your payment // method button. }, ) } ``` When `PaymentSheet.FlowController` completes configuration, it invokes your callback. Then, you can populate your **Payment Method** button with `flowController.getPaymentOption()`, which includes an image and a label that represent your customer’s initial payment method selection. ### Present the PaymentSheet When your customer taps your **Payment Method** button, call `presentPaymentOptions()` to collect payment details. Then, update your UI using the `paymentOption` property. ```kotlin // ... flowController.presentPaymentOptions() // ... fun onPaymentOption(paymentOption: PaymentOption?) { if (paymentOption != null) { paymentMethodButton.text = paymentOption.label paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds( paymentOption.drawableResourceId, 0, 0, 0 ) } else { paymentMethodButton.text = "Select" paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds( null, null, null, null ) } } ``` ### Update payment details If the customer changes the payment details (for example, by applying a discount code or editing their cart), update the `PaymentSheet.FlowController` instance to reflect the new values by calling `configureWithIntentConfiguration()` again to reflect the new values. This keeps the values shown in the UI in sync. > Some payment methods, such as Google Pay, show the amount in the UI. If the customer changes the payment and you don’t update the `EmbeddedPaymentElement`, the UI displays incorrect values. During configuration, don’t call `presentPaymentOptions()` or `confirm()` on `PaymentSheet.FlowController`. Disable your **Buy** and **Payment method** buttons, then enable them when your `ConfigCallback` runs. If the update succeeds, use `flowController.getPaymentOption()` to update your UI because the customer’s previously selected payment method might be unavailable. If the update fails, retry it. ```kotlin fun handleCartChanged( currency: String, ) { // Disable your "Buy" and "Payment method" buttons paymentMethodSelectionButton.isEnabled = false payButton.isEnabled = false // Update FlowController by configuring it again flowController.configureWithIntentConfiguration( intentConfiguration = PaymentSheet.IntentConfiguration( mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = currency, ), ), configuration = PaymentSheet.Configuration.Builder( merchantDisplayName = "Example Inc.", ).build(), callback = { success, error -> // If success, the FlowController was updated correctly if (success) { paymentMethodSelectionButton.isEnabled = true val canPay = flowController.getPaymentOption() != null payButton.isEnabled = canPay } else { // You must retry - until the update succeeds, the customer can't pay or select a payment method. // For example, you can automatically retry the update with an exponential back-off, or present the user with an alert that retries the update. } }, ) } ``` ### Confirm the Intent When your customer taps your **Buy** button, call `confirm()` to call the `CreateIntentCallback` you pass with a [ConfirmationToken](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-confirmation-token/index.html) object that represents your customer’s payment details and preferences. Implement this method to send a request to your server with `confirmationToken.id`. Your server creates a SetupIntent and returns its client secret. When you receive the response, return the client secret or an error. PaymentSheet confirms the SetupIntent using the client secret, or displays the error in the UI. ```kotlin class MyCheckoutActivity : AppCompatActivity() { private lateinit var flowController: PaymentSheet.FlowController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // … flowController = PaymentSheet.FlowController.Builder( resultCallback = ::onPaymentSheetResult, paymentOptionCallback = ::onPaymentOption ) .createIntentCallback { confirmationToken ->// Make a request to your server to create aSetupIntentand return its client secret try { val response = myNetworkClient.createIntent( confirmationTokenId = confirmationToken.id, // only required for server-side confirmation ) CreateIntentResult.Success(response.clientSecret) } catch (e: Exception) { CreateIntentResult.Failure( cause = e, displayMessage = e.message ) } } .build(this) } } ``` After your customer finishes saving their payment method, the sheet closes, and the [PaymentSheetResultCallback](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result-callback/index.html) is invoked with a [PaymentSheetResult](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result/index.html). ```kotlin class MyCheckoutActivity : AppCompatActivity() { // ... fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {when(paymentSheetResult) { is PaymentSheetResult.Canceled -> { // Customer canceled - you should probably do nothing. } is PaymentSheetResult.Failed -> { print("Error: ${paymentSheetResult.error}") // PaymentSheet encountered an unrecoverable error. You can display the error to the user, log it, and so on } is PaymentSheetResult.Completed -> { // Display, for example, an order confirmation screen print("Completed") } } } } ``` The following step explains the server code. #### Jetpack Compose ### Initialize the PaymentSheet.FlowController Initialize the `PaymentSheet.FlowController` and pass in callbacks to `PaymentSheet.FlowController.Builder`. Leave the implementations empty for now. ```kotlin import androidx.compose.runtime.* import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.PaymentSheet.FlowController.Builder import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.paymentsheet.model.PaymentOption private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { // You'll implement this later } private fun onPaymentOption(paymentOption: PaymentOption?) { // You'll implement this later } @Composable fun CheckoutScreen() { val flowController = remember{ Builder( resultCallback = ::onPaymentSheetResult, paymentOptionCallback = ::onPaymentOption, ).createIntentCallback { confirmationToken -> // You'll implement this later } }.build() } ``` After your checkout screen loads, configure the `PaymentSheet.FlowController` instance with an [IntentConfiguration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-intent-configuration/index.html). `IntentConfiguration` contains details about the specific `SetupIntent`, such as the currency. ```kotlin @Composable fun CheckoutScreen() { val flowController = remember{ ... }.build() LaunchedEffect(Unit) { flowController.configureWithIntentConfiguration( intentConfiguration = PaymentSheet.IntentConfiguration( mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = "usd", ), ), configuration = PaymentSheet.Configuration.Builder( merchantDisplayName = "Example Inc.", ).build(), callback = { success, error -> // If success, the FlowController was initialized correctly. // Use flowController.getPaymentOption() to populate your payment // method button. }, ) } } ``` When the `PaymentSheet.FlowController` instance completes configuration, it calls `paymentOptionCallback`. At that point, `paymentOption` contains an image and a label that represent the customer’s initial payment method selection. ### Present the PaymentSheet When a customer taps the **Payment Method** button, call `presentPaymentOptions()` to collect payment details. After it completes, `paymentOptionCallback` updates `paymentOption`. Then, update your UI. ```kotlin ... flowController.presentPaymentOptions() ... fun onPaymentOption(paymentOption: PaymentOption?) { if (paymentOption != null) { paymentMethodButton.text = paymentOption.label paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds( paymentOption.drawableResourceId, 0, 0, 0 ) } else { paymentMethodButton.text = "Select" paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds( null, null, null, null ) } } ``` ### Update payment details If the customer changes the payment details (for example, by applying a discount code or editing their cart), update the `PaymentSheet.FlowController` instance to reflect the new values by calling `configureWithIntentConfiguration()` again to reflect the new values. This keeps the values shown in the UI in sync. > Some payment methods, such as Google Pay, show the amount in the UI. If the customer changes the payment and you don’t update the `PaymentSheet.FlowController`, the UI displays incorrect values. During configuration, don’t call `presentPaymentOptions()` or `confirm()` on the `PaymentSheet.FlowController`. If the update succeeds, `paymentOptionCallback` runs with the updated payment option. The selection might change if the customer’s previously selected payment method is no longer available. If the update fails, retry it. ```kotlin fun updateCart( flowController: PaymentSheet.FlowController, onComplete: (Boolean) -> Unit ) { flowController.configureWithIntentConfiguration( intentConfiguration = PaymentSheet.IntentConfiguration( mode = PaymentSheet.IntentConfiguration.Mode.Setup( currency = "usd", ), ), configuration = PaymentSheet.Configuration.Builder( merchantDisplayName = "Example Inc.", ).build(), callback = { success, error -> // If success, the FlowController was updated correctly if (success) { paymentMethodSelectionButton.isEnabled = true val canPay = flowController.getPaymentOption() != null payButton.isEnabled = canPay } else { // You must retry - until the update succeeds, the customer can't pay or select a payment method. // For example, you can automatically retry the update with an exponential back-off, or present the user with an alert that retries the update. } }, ) } ``` ### Confirm the Intent When your customer taps your **Buy** button, call `confirm()`. The PaymentSheet calls the `createIntentCallback` you pass with a [ConfirmationToken](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-confirmation-token/index.html) that represents your customer’s payment details and preferences. ```kotlin @Composable fun CheckoutScreen() { val flowController = remember{ Builder( resultCallback = ::onPaymentSheetResult, paymentOptionCallback = ::onPaymentOption, ).createIntentCallback { confirmationToken -> // Make a request to your server to create a SetupIntent and return its client secret try { val response = myNetworkClient.createIntent( confirmationTokenId = confirmationToken.id, ) CreateIntentResult.Success(response.clientSecret) } catch (e: Exception) { CreateIntentResult.Failure( cause = e, displayMessage = e.message ) } } }.build() // Previous flowcontroller configuration code Column { // Payment method button... Button( onClick = { flowController.confirm() }, enabled = paymentOption != null ) { Text("Set up") } } } ``` After your customer saves their payment method, the sheet closes, and `PaymentSheet.FlowController` invokes the callback you pass to `PaymentSheet.FlowController.resultCallback` with a [PaymentSheetResult](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result/index.html). ```kotlin private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { when(paymentSheetResult) { is PaymentSheetResult.Canceled -> { // Customer canceled - you should probably do nothing. } is PaymentSheetResult.Failed -> { println("Error: ${(paymentSheetResult as PaymentSheetResult.Failed).error}") // PaymentSheet encountered an unrecoverable error. You can display the error to the user, log it, and so on } is PaymentSheetResult.Completed -> { // Display, for example, an order confirmation screen println("Completed") } null -> { // No result yet } } } ``` The following step explains the server code. ## Create a SetupIntent [Server-side] On your server, create a *SetupIntent* (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). You can manage payment methods from the [Dashboard](https://dashboard.stripe.com/settings/payment_methods). Stripe evaluates payment method restrictions and other parameters to determine the list of supported payment methods. If the call succeeds, return the SetupIntent *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)). If the call fails, [handle the error](https://docs.stripe.com/error-handling.md) and return an error message with a brief explanation for your customer. > Verify that all IntentConfiguration properties match your SetupIntent (for example, [usage](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-usage)). #### Ruby ```ruby require 'stripe' # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. client = Stripe::StripeClient.new('<>') post '/create-intent' do data = JSON.parse request.body.read params = { customer: ..., # The Customer ID you previously created automatic_payment_methods: {enabled: true}, } begin intent = client.v1.setup_intents.create(params) {client_secret: intent.client_secret}.to_json rescue Stripe::StripeError => e {error: e.error.message}.to_json end end ``` ## Charge the saved payment method later [Server-side] > `bancontact` and `ideal` are one-time payment methods by default. When set up for future usage, they generate a `sepa_debit` reusable payment method type so you need to use `sepa_debit` to query for saved payment methods. > #### Compliance > > You’re responsible for your compliance with all applicable laws, regulations, and network rules when saving a customer’s payment details. When rendering past payment methods to your end customer for future purchases, make sure you’re listing payment methods where you’ve collected consent from the customer to save the payment method details for this specific future use. To differentiate between payment methods attached to customers that can and can’t be presented to your end customer as a saved payment method for future purchases, use the [allow_redisplay](https://docs.stripe.com/api/payment_methods/object.md#payment_method_object-allow_redisplay) parameter. To find a payment method to charge, list the payment methods associated with your customer. This example lists cards, but you can list any supported [type](https://docs.stripe.com/api/payment_methods/object.md#payment_method_object-type). > #### Use the Accounts v2 API to represent customers > > The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a [specify a preview version](https://docs.stripe.com/api-v2-overview.md#sdk-and-api-versioning) in your code. > > To request access to the Accounts v2 preview, > > For most use cases, we recommend [modeling your customers as customer-configured Account objects](https://docs.stripe.com/accounts-v2/use-accounts-as-customers.md) instead of using [Customer](https://docs.stripe.com/api/customers.md) objects. #### Accounts v2 ```curl curl -G https://api.stripe.com/v1/payment_methods \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d type=card ``` #### Customers v1 ```curl curl -G https://api.stripe.com/v1/payment_methods \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d type=card ``` When you’re ready to charge your customer *off-session* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information), use the ID of the `Customer` or customer-configured `Account` and the `PaymentMethod` ID to 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/create.md#create_payment_intent-off_session) to true to indicate that the customer isn’t in your checkout flow to respond to any authentication requests. If, during your checkout flow, a partner (such as a card issuer or bank) 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 [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) to true to trigger confirmation immediately when the `PaymentIntent` is created. - Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the `PaymentMethod`’s ID. - Depending on how you represent customers in your integration, set either [customer_account](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer_account) to the ID of the customer-configured `Account` or [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the `Customer`. #### Accounts v2 ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=usd \ -d "automatic_payment_methods[enabled]=true" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d payment_method={{PAYMENT_METHOD_ID}} \ --data-urlencode "return_url=https://example.com/order/123/complete" \ -d off_session=true \ -d confirm=true ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=usd \ -d "automatic_payment_methods[enabled]=true" \ -d "customer={{CUSTOMER_ID}}" \ -d payment_method={{PAYMENT_METHOD_ID}} \ --data-urlencode "return_url=https://example.com/order/123/complete" \ -d off_session=true \ -d confirm=true ``` ## Test the integration #### Cards | Card number | Scenario | How to test | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | 4242424242424242 | The card payment succeeds and doesn’t require authentication. | Fill out the credit card form using the credit card number with any expiration, CVC, and postal code. | | 4000002500003155 | The card payment requires *authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). | Fill out the credit card form using the credit card number with any expiration, CVC, and postal code. | | 4000000000009995 | The card is declined with a decline code like `insufficient_funds`. | Fill out the credit card form using the credit card number with any expiration, CVC, and postal code. | | 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits. | Fill out the credit card form using the credit card number with any expiration, CVC, and postal code. | #### Bank redirects | Payment method | Scenario | How to test | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Bancontact, iDEAL | Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. | | Pay by Bank | Your customer successfully pays with a redirect-based and [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method. | Choose the payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page. | | Pay by Bank | Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. | Choose the payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. | | BLIK | BLIK payments fail in a variety of ways—immediate failures (for example, the code is expired or invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). | Use email patterns to [simulate the different failures.](https://docs.stripe.com/payments/blik/accept-a-payment.md#simulate-failures) | #### Bank debits | Payment method | Scenario | How to test | | ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit. | Fill out the form using the account number `AT321904300235473204`. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. | | SEPA Direct Debit | Your customer’s payment intent status transitions from `processing` to `requires_payment_method`. | Fill out the form using the account number `AT861904300235473202`. | See [Testing](https://docs.stripe.com/testing.md) for additional information to test your integration. ## Optional: Enable saved cards [Server-side] [Client-side] `PaymentSheet` can allow the customer to save their card and can include the customer’s saved cards in available payment methods. The customer must have an associated customer-configured [Account](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer) object or a [Customer](https://docs.stripe.com/api/customers/create.md) object on your server. To enable a checkbox that allows the customer to save their card, create a [CustomerSession](https://docs.stripe.com/api/customer_sessions.md), with `payment_method_save` set to `enabled`. #### Accounts v2 ```javascript // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. const stripe = require("stripe")("<>"); const express = require('express'); const app = express(); app.set('trust proxy', true); app.use(express.json()); app.post('/payment-sheet', async (req, res) => { // Use an existing Account ID if this is a returning customer. const customer_account = await stripe.v2.core.accounts.create(); const customerSession = await stripe.customerSessions.create({ customer_account: customer_account.id, components: { mobile_payment_element: { enabled: true, features: { payment_method_save: 'enabled', payment_method_redisplay: 'enabled', payment_method_remove: 'enabled' } }, }, }); res.json({ customerSessionClientSecret: customerSession.client_secret, customer_account: customer_account.id, }); }); ``` Next, present `PaymentSheet` with the customer’s ID and the `CustomerSession` client secret. ```kotlin val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Powdur") .customer( PaymentSheet.CustomerConfiguration.createWithCustomerSession( id = customerAccountId, clientSecret = customerSessionClientSecret, ) ) .build() paymentSheet.presentWithIntentConfiguration( intentConfiguration = // ... , configuration = configuration, ) ``` #### Customers v1 ```javascript // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. const stripe = require("stripe")("<>"); const express = require('express'); const app = express(); app.set('trust proxy', true); app.use(express.json()); app.post('/payment-sheet', async (req, res) => { // Use an existing Customer ID if this is a returning customer. const customer = await stripe.customers.create(); const customerSession = await stripe.customerSessions.create({ customer: customer.id, components: { mobile_payment_element: { enabled: true, features: { payment_method_save: 'enabled', payment_method_redisplay: 'enabled', payment_method_remove: 'enabled' } }, }, }); res.json({ customerSessionClientSecret: customerSession.client_secret, customer: customer.id, }); }); ``` Next, present `PaymentSheet` with the customer’s ID and the `CustomerSession` client secret. ```kotlin val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Powdur") .customer( PaymentSheet.CustomerConfiguration.createWithCustomerSession( id = customerId, clientSecret = customerSessionClientSecret, ) ) .build() paymentSheet.presentWithIntentConfiguration( intentConfiguration = // ... , configuration = configuration, ) ``` ## Optional: Allow delayed payment methods [Client-side] *Delayed payment methods* (A payment method that can't immediately return payment status when a customer attempts a transaction (for example, ACH debits). Businesses commonly hold an order in a pending state until payment is successful with these payment methods) don’t guarantee that you’ll receive funds from your customer at the end of checkout, either because they take time to settle (for example, US Bank Accounts, SEPA Debit, iDEAL, Bancontact, and Sofort) or because they require customer action to complete (for example, OXXO, Konbini, and Boleto). By default, `PaymentSheet` doesn’t display delayed payment methods. To include the delayed payment methods that `PaymentSheet` supports, set `allowsDelayedPaymentMethods` to true in your `PaymentSheet.Configuration`. ```kotlin val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Powdur") .allowsDelayedPaymentMethods(true) .build() ``` If the customer successfully uses a delayed payment method in a `PaymentSheet`, the payment result returned is `PaymentSheetResult.Completed`. ## Optional: Enable Google Pay > If your checkout screen has a dedicated **Google Pay** button, follow the [Google Pay guide](https://docs.stripe.com/google-pay.md?platform=android). You can use Embedded Payment Element to handle other payment method types. ### Set up your integration To use Google Pay, first enable the Google Pay API by adding the following to the `` tag of your **AndroidManifest.xml**: ```xml ... ``` For more details, see Google Pay’s [Set up Google Pay API](https://developers.google.com/pay/api/android/guides/setup) for Android. ### Add Google Pay To add Google Pay to your integration, pass a [PaymentSheet.GooglePayConfiguration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-google-pay-configuration/index.html) with your Google Pay environment (production or test) and the [country code of your business](https://dashboard.stripe.com/settings/account) when initializing [PaymentSheet.Configuration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/index.html). #### Kotlin ```kotlin val googlePayConfiguration = PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Test, countryCode = "US", currencyCode = "USD" // Required for Setup Intents, optional for Payment Intents ) val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name") .googlePay(googlePayConfiguration) .build() ``` ### Test Google Pay Google allows you to make test payments through their [Test card suite](https://developers.google.com/pay/api/android/guides/resources/test-card-suite). The test suite supports using Stripe [test cards](https://docs.stripe.com/testing.md). You must test Google Pay using a physical Android device instead of a simulated device, in a country where Google Pay is supported. Log in to a Google account on your test device with a real card saved to Google Wallet. ## Optional: Enable card scanning To enable card scanning support, [request production access](https://developers.google.com/pay/api/android/guides/test-and-deploy/request-prod-access) to the Google Pay API from the [Google Pay and Wallet Console](https://pay.google.com/business/console?utm_source=devsite&utm_medium=devsite&utm_campaign=devsite). - If you’ve enabled Google Pay, the card scanning feature is automatically available in our UI on eligible devices. To learn more about eligible devices, see the [Google Pay API constraints](https://developers.google.com/pay/payment-card-recognition/debit-credit-card-recognition) - **Important:** The card scanning feature only appears in builds signed with the same signing key registered in the [Google Pay & Wallet Console](https://pay.google.com/business/console). Test or debug builds using different signing keys (for example, builds distributed through Firebase App Tester) won’t show the **Scan card** option. To test card scanning in pre-release builds, you must either: - Sign your test builds with your production signing key - Add your test signing key fingerprint to the Google Pay and Wallet Console If your app doesn’t support Google Pay, you can use the Stripe card scanner. > The Stripe card scanner is in public preview. To enable card scanning support, add `stripecardscan` to the `dependencies` block of your [app/build.gradle](https://developer.android.com/studio/build/dependencies) file: #### Groovy ```groovy implementation 'com.stripe:stripecardscan:23.9.1' ``` ## Optional: Customize the sheet All customization is configured using the [PaymentSheet.Configuration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/index.html) object. ### Appearance Customize colors, fonts, and more to match the look and feel of your app by using the [appearance API](https://docs.stripe.com/elements/appearance-api/mobile.md?platform=android). ### Payment method layout Configure the layout of payment methods in the sheet using [paymentMethodLayout](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/-builder/index.html#2123253356%2FFunctions%2F2002900378). You can display them horizontally, vertically, or let Stripe optimize the layout automatically. ![](https://b.stripecdn.com/docs-statics-srv/assets/android-mpe-payment-method-layouts.3bcfe828ceaad1a94e0572a22d91733f.png) #### Kotlin ```kotlin PaymentSheet.Configuration.Builder("Example, Inc.") .paymentMethodLayout(PaymentSheet.PaymentMethodLayout.Automatic) .build() ``` ### Collect users addresses Collect local and international shipping or billing addresses from your customers using the [Address Element](https://docs.stripe.com/elements/address-element.md?platform=android). ### Business display name Specify a customer-facing business name by setting [merchantDisplayName](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/index.html#-191101533%2FProperties%2F2002900378). By default, this is your app’s name. #### Kotlin ```kotlin PaymentSheet.Configuration.Builder( merchantDisplayName = "My app, Inc." ).build() ``` ### Dark mode By default, `PaymentSheet` automatically adapts to the user’s system-wide appearance settings (light and dark mode). You can change this by setting light or dark mode on your app: #### Kotlin ```kotlin // force dark AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) // force light AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) ``` ### Default billing details To set default values for billing details collected in the payment sheet, configure the `defaultBillingDetails` property. The `PaymentSheet` pre-populates its fields with the values that you provide. #### Kotlin ```kotlin val address = PaymentSheet.Address(country = "US") val billingDetails = PaymentSheet.BillingDetails( address = address, email = "foo@bar.com" ) val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Merchant, Inc.") .defaultBillingDetails(billingDetails) .build() ``` ### Configure collection of billing details Use `BillingDetailsCollectionConfiguration` to specify how you want to collect billing details in the PaymentSheet. You can collect your customer’s name, email, phone number, and address. If you want to attach default billing details to the PaymentMethod object even when those fields aren’t collected in the UI, set `billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod` to `true`. #### Kotlin ```kotlin val billingDetails = PaymentSheet.BillingDetails( email = "foo@bar.com" ) val billingDetailsCollectionConfiguration = BillingDetailsCollectionConfiguration( attachDefaultsToPaymentMethod = true, name = BillingDetailsCollectionConfiguration.CollectionMode.Always, email = BillingDetailsCollectionConfiguration.CollectionMode.Never, address = BillingDetailsCollectionConfiguration.AddressCollectionMode.Full, ) val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Merchant, Inc.") .defaultBillingDetails(billingDetails) .billingDetailsCollectionConfiguration(billingDetailsCollectionConfiguration) .build() ``` > Consult with your legal counsel regarding laws that apply to collecting information. Only collect phone numbers if you need them for the transaction.