# Collect card payments Prepare your application and back end to collect card payments using Stripe Terminal. # Android New to the Payment Intents API? Here are some helpful resources: - [The Payment Intents API](https://docs.stripe.com/payments/payment-intents.md) - [The PaymentIntent object](https://docs.stripe.com/api/payment_intents.md) - [More payment scenarios](https://docs.stripe.com/payments/more-payment-scenarios.md) Collecting payments with Stripe Terminal requires writing a payment flow in your application. Use the Stripe Terminal SDK to create and update a [PaymentIntent](https://docs.stripe.com/api.md#payment_intents), an object representing a single payment session. Designed to be robust to failures, the Terminal integration splits the payment process into several steps, each of which can be retried safely: 1. [Create a PaymentIntent](https://docs.stripe.com/terminal/payments/collect-card-payment.md#create-payment). 1. [Process the payment](https://docs.stripe.com/terminal/payments/collect-card-payment.md#process-payment). Authorization on the customer’s card takes place when the SDK processes the payment. 1. (Optional) [Capture the payment](https://docs.stripe.com/terminal/payments/collect-card-payment.md#capture-payment) ## Create a PaymentIntent [Client-side] [Server-side] The first step when collecting payments is to start the payment flow. When a customer begins checking out, your application must create a `PaymentIntent` object. This represents a new payment session on Stripe. - [createPaymentIntent (Android)](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/create-payment-intent.html) You can create a `PaymentIntent` on the client or server. Use [test amounts](https://docs.stripe.com/terminal/references/testing.md#physical-test-cards) to try producing different results. An amount ending in `00` results in an approved payment. > #### Don't recreate PaymentIntents for declined cards > > Don’t recreate a PaymentIntent if a card is declined. Instead, re-use the same PaymentIntent to help [avoid double charges](https://docs.stripe.com/terminal/payments/collect-card-payment.md#avoiding-double-charges). ### Client-side Create a `PaymentIntent` from your client: #### Kotlin ```kotlin val params = PaymentIntentParameters.Builder() .setAmount(1000) .setCurrency("usd") .build() Terminal.getInstance().createPaymentIntent( params, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling successful operation } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } } ) ``` ### Server-side You can create the `PaymentIntent` on your server if the information required to start a payment isn’t readily available in your app. The following example shows how to create a `PaymentIntent` on your server: #### curl ```bash curl https://api.stripe.com/v1/payment_intents \ -u <>: \ -d "amount"=1000 \ -d "currency"="usd" \ -d "payment_method_types[]"="card_present" \ -d "capture_method"="manual" ``` For Terminal payments, the `payment_method_types` parameter must include `card_present`. You can control the payment flow as follows: - To fully control the payment flow for `card_present` payments, set the `capture_method` to `manual`. This allows you to add a reconciliation step before finalizing the payment. - To authorize and capture payments in one step, set the `capture_method` to `automatic`. To accept payments in Australia, you need to set `capture_method` to `automatic` or `manual_preferred`. For more details, visit our [Australia documentation](https://docs.stripe.com/terminal/payments/regional.md?integration-country=AU). To accept Interac payments in Canada, you must also include `interac_present` in `payment_method_types`. For more details, visit our [Canada documentation](https://docs.stripe.com/terminal/payments/regional.md?integration-country=CA). The `PaymentIntent` contains a [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret), a key that’s unique to the individual `PaymentIntent`. To use the client secret, you must obtain it from the `PaymentIntent` on your server and [pass it to the client side](https://docs.stripe.com/payments/payment-intents.md#passing-to-client). #### Ruby ```ruby post '/create_payment_intent' do intent = # ... Create or retrieve the PaymentIntent {client_secret: intent.client_secret}.to_json end ``` - [retrievePaymentIntent (Android)](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/retrieve-payment-intent.html) To retrieve a `PaymentIntent`, use the client secret to call `retrievePaymentIntent`. After you retrieve the `PaymentIntent`, use it to call `processPaymentIntent`. #### Kotlin ```kotlin Terminal.getInstance().retrievePaymentIntent( clientSecret, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling successful operation } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } } ) ``` ## Process the payment [Client-side] You can process a payment immediately with the card presented by a customer, or instead inspect card details before proceeding to process the payment. For most use cases, we recommend processing immediately, because it’s a simpler integration with fewer API calls. However, if you want to insert your own business logic before authorizing the card, use the two-step collect-and-confirm flow. #### Process immediately After you create a PaymentIntent, the next step is to process the payment. The reader prompts the customer to insert or tap their card and then attempts to authorize the payment. - [processPaymentIntent (Android)](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/process-payment-intent.html) While processing a payment, cardholder might take a few seconds to get their card from their wallet or pose a question to the operator during payment. #### Kotlin ```kotlin val cancelable = Terminal.getInstance().processPaymentIntent( paymentIntent = paymentIntent, collectConfig = CollectPaymentIntentConfiguration.Builder().build(), confirmConfig = ConfirmPaymentIntentConfiguration.Builder().build(), callback = object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { println("processPaymentIntent succeeded") // Notify your backend to capture the PaymentIntent if (paymentIntent.id != null) { ApiClient.capturePaymentIntent(paymentIntent.id) { error -> if (error != null) { println("capturePaymentIntent failed: $error") } else { println("capturePaymentIntent succeeded") } } } else { println("Payment collected offline") } } override fun onFailure(e: TerminalException) { println("processPaymentIntent failed: $e") } } ) ``` ### Cancel collection #### Programmatic cancellation - [Cancelable (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-cancelable/index.html) You can cancel processing a PaymentIntent using the `Cancelable` object returned by the Android SDK. #### Customer-initiated cancellation - [setCustomerCancellation (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-collect-payment-intent-configuration/-builder/set-customer-cancellation.html) - [Customer Cancellation (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-customer-cancellation/index.html) Smart readers show customers a cancel button by default. You can disable this by setting `customerCancellation` to `DISABLE_IF_AVAILABLE`. Tapping the cancel button cancels the active transaction. #### Kotlin ```kotlin Terminal.getInstance().collectPaymentMethod( paymentIntent, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling successful operation } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } },CollectPaymentIntentConfiguration.Builder() .setCustomerCancellation(CustomerCancellation.DISABLE_IF_AVAILABLE) // turn OFF the cancel button, ON by default .build(), ) ``` ### Handle events - [MobileReaderListener (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-mobile-reader-listener/index.html) When collecting a payment method using a reader such as the [Stripe M2](https://docs.stripe.com/terminal/readers/stripe-m2.md), without a built-in display, your app must be able to display events from the payment method collection process to users. These events help users successfully collect payments (for example, retrying a card, trying a different card, or using a different read method). When a transaction begins, the SDK passes a `ReaderInputOptions` value to your app’s reader display handler, denoting the acceptable types of input (for example, `Swipe`, `Insert`, or `Tap`). In your app’s checkout UI, prompt the user to present a card using one of these options. During the transaction, the SDK might request your app to display additional prompts (for example, `Retry Card`) to your user by passing a `ReaderDisplayMessage` value to your app’s reader display handler. Make sure your checkout UI displays these messages to the user. #### Kotlin ```kotlin class ReaderActivity : AppCompatActivity(), MobileReaderListener { // ... override fun onRequestReaderInput(options: ReaderInputOptions) { Toast.makeText(this, options.toString(), Toast.LENGTH_SHORT).show() } override fun onRequestReaderDisplayMessage(message: ReaderDisplayMessage) { Toast.makeText(this, message.toString(), Toast.LENGTH_SHORT).show() } // ... } ``` ### Collect payments with Tap to Pay on Android When your application is ready to collect a payment, the Stripe Terminal Android SDK takes over the display to handle the collection process. After calling the [process payment](https://docs.stripe.com/terminal/payments/collect-card-payment.md#process-payment) method, your application remains running. The Android device displays a full-screen prompt to the cardholder, instructing them to present their card or NFC-based mobile wallet. If there’s an error reading the card, a prompt for retry displays. A successful presentation returns a success indication, and then control returns to your application. ![Tap to Pay on Android payment collection](https://b.stripecdn.com/docs-statics-srv/assets/ttpa-payment-collection.bd48493bc27d1f5ebb076aeeeaa43456.png) Payment collection - For manual capture of payments, a successful `processPaymentIntent` call results in a `PaymentIntent` with a status of `requires_capture`. - For automatic capture of payments, the `PaymentIntent` transitions to a `succeeded` state. You must manually capture a PaymentIntent within 2 days or the authorization expires and funds are released to the customer. ### Handle failures - [TerminalException (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-terminal-exception/index.html) When processing a payment fails, the SDK returns an error that includes the updated `PaymentIntent` if it was declined by stripe. Your application needs to inspect the `PaymentIntent` to decide how to deal with the error. | PaymentIntent status | Meaning | Resolution | | ------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `requires_payment_method` | Payment method declined | Try collecting a different payment method by calling `processPaymentIntent` again with the same `PaymentIntent`. | | `requires_confirmation` | Temporary connectivity problem | Call `processPaymentIntent` again with the same `PaymentIntent` to retry the request. | | `PaymentIntent` is `nil` | Request to Stripe timed out, unknown `PaymentIntent` status | Retry processing the original `PaymentIntent`. Don’t create a new one, because that might result in multiple authorizations for the cardholder. | If you encounter multiple, consecutive timeouts, there might be a problem with your connectivity. Make sure that your app can communicate with the internet. ### Avoid double charges The `PaymentIntent` object enables money movement at Stripe—use a single `PaymentIntent` to represent a transaction. Re-use the same `PaymentIntent` after a card is declined (for example, if it has insufficient funds), so your customer can try again with a different card. If you edit the `PaymentIntent`, you must call `processPaymentIntent` to update the payment information on the reader. A `PaymentIntent` must be in the `requires_payment_method` state before Stripe can process it. An authorized, captured, or canceled `PaymentIntent` can’t be processed by a reader. #### Collect, inspect, and confirm After you create a PaymentIntent, the next step is to process the payment. The reader prompts the customer to insert or tap their card and then creates a PaymentMethod. ## Collect a PaymentMethod - [collectPaymentMethod (Android)](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/collect-payment-method.html) After you’ve created a `PaymentIntent`, the next step is to collect a payment method with the SDK. To collect a payment method, your app needs to be connected to a reader. The connected reader waits for a card to be presented after your app calls `collectPaymentMethod`. #### Kotlin ```kotlin val cancelable = Terminal.getInstance().collectPaymentMethod( paymentIntent, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling successful operation } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } } ) ``` This method collects encrypted payment method data using the connected card reader, and associates the encrypted data with the local `PaymentIntent`. ### Optionally inspect payment method details - [CollectPaymentIntentConfiguration (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-collect-payment-intent-configuration/index.html) - [CardPresentDetails (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-card-present-details/index.html) For advanced use cases, you can examine the payment method details of the presented card and perform your own business logic prior to authorization. Use the `updatePaymentIntent` parameter in `CollectPaymentIntentConfiguration` to attach a `PaymentMethod` to the server-side `PaymentIntent`. This data is returned in the `collectPaymentMethod` response. #### Kotlin ```kotlin val collectConfig = CollectPaymentIntentConfiguration.Builder() .updatePaymentIntent(true) .build() val cancelable = Terminal.getInstance().collectPaymentMethod(paymentIntent, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { val pm = paymentIntent.paymentMethod val card = pm?.cardPresentDetails ?: pm?.interacPresentDetails // Placeholder for business logic on card before confirming paymentIntent } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } } ) ``` This method attaches the collected encrypted payment method data with an update to the `PaymentIntent` object. It doesn’t require authorization until you confirm the payment. After payment method collection, you must authorize or cancel the payment within 30 seconds. If the SDK is [operating offline](https://docs.stripe.com/terminal/features/operate-offline/collect-card-payments.md), the `paymentMethod` field isn’t present in the `PaymentIntent` object. You can access attributes like card brand, funding, and other useful data at this point. Stripe attempts to detect whether a mobile wallet is used in a transaction as shown in the `wallet.type` attribute. However, the attribute isn’t populated if the card’s issuing bank doesn’t support reader-driven identification of a mobile wallet, so accurate detection isn’t guaranteed. After authorization and when you [process the payment](https://docs.stripe.com/terminal/payments/collect-card-payment.md#process-payment), Stripe receives up-to-date information from the networks and updates `wallet.type`. ### Cancel collection #### Programmatic cancellation - [Cancelable (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-cancelable/index.html) You can cancel collecting a payment method using the `Cancelable` object returned by the Android SDK. #### Customer-initiated cancellation - [setCustomerCancellation (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-collect-payment-intent-configuration/-builder/set-customer-cancellation.html) - [Customer Cancellation (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-customer-cancellation/index.html) Smart readers show customers a cancel button by default. You can disable this by setting `customerCancellation` to `DISABLE_IF_AVAILABLE`. Tapping the cancel button cancels the active transaction. #### Kotlin ```kotlin Terminal.getInstance().collectPaymentMethod( paymentIntent, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling successful operation } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } },CollectPaymentIntentConfiguration.Builder() .setCustomerCancellation(CustomerCancellation.DISABLE_IF_AVAILABLE) // turn OFF the cancel button, ON by default .build(), ) ``` ### Handle events - [MobileReaderListener (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-mobile-reader-listener/index.html) When collecting a payment method using a reader such as the [Stripe M2](https://docs.stripe.com/terminal/readers/stripe-m2.md), without a built-in display, your app must be able to display events from the payment method collection process to users. These events help users successfully collect payments (for example, retrying a card, trying a different card, or using a different read method). When a transaction begins, the SDK passes a `ReaderInputOptions` value to your app’s reader display handler, denoting the acceptable types of input (for example, `Swipe`, `Insert`, or `Tap`). In your app’s checkout UI, prompt the user to present a card using one of these options. During the transaction, the SDK might request your app to display additional prompts (for example, `Retry Card`) to your user by passing a `ReaderDisplayMessage` value to your app’s reader display handler. Make sure your checkout UI displays these messages to the user. #### Kotlin ```kotlin class ReaderActivity : AppCompatActivity(), MobileReaderListener { // ... override fun onRequestReaderInput(options: ReaderInputOptions) { Toast.makeText(this, options.toString(), Toast.LENGTH_SHORT).show() } override fun onRequestReaderDisplayMessage(message: ReaderDisplayMessage) { Toast.makeText(this, message.toString(), Toast.LENGTH_SHORT).show() } // ... } ``` ### Collect payments with Tap to Pay on Android When your application is ready to collect a payment, the Stripe Terminal Android SDK takes over the display to handle the collection process. After calling the [process payment](https://docs.stripe.com/terminal/payments/collect-card-payment.md#process-payment) method, your application remains running. The Android device displays a full-screen prompt to the cardholder, instructing them to present their card or NFC-based mobile wallet. If there’s an error reading the card, a prompt for retry displays. A successful presentation returns a success indication, and then control returns to your application. ![Tap to Pay on Android payment collection](https://b.stripecdn.com/docs-statics-srv/assets/ttpa-payment-collection.bd48493bc27d1f5ebb076aeeeaa43456.png) Payment collection ## Confirm the PaymentIntent - [confirmPaymentIntent (Android)](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/confirm-payment-intent.html) After successfully collecting a payment method from the customer, the next step is to confirm the payment with the SDK. When you’re ready to proceed with the payment, call `confirmPaymentIntent` with the updated `PaymentIntent` from the [previous step](https://docs.stripe.com/terminal/payments/collect-card-payment.md#collect-inspect-payment-method). - For manual capture of payments, a successful `confirmPaymentIntent` call results in a `PaymentIntent` with a status of `requires_capture`. - For automatic capture of payments, the `PaymentIntent` transitions to a `succeeded` state. Always confirm PaymentIntents using the Terminal SDK on the client. Server-side confirmation bypasses critical interactions, such as PIN prompts, and might result in transaction failures. #### Kotlin ```kotlin val cancelable = Terminal.getInstance().confirmPaymentIntent( paymentIntent, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder handling successful operation } override fun onFailure(e: TerminalException) { // Placeholder for handling exception } } ) ``` You must manually capture a PaymentIntent within 2 days or the authorization expires and funds are released to the customer. ### Handle failures - [TerminalException (Android)](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-terminal-exception/index.html) When confirming a payment fails, the SDK returns an error that includes the updated `PaymentIntent`. Your application needs to inspect the `PaymentIntent` to decide how to deal with the error. | PaymentIntent Status | Meaning | Resolution | | ------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `requires_payment_method` | Payment method declined | Try collecting a different payment method by calling `collectPaymentMethod` again with the same `PaymentIntent`. | | `requires_confirmation` | Temporary connectivity problem | Call `confirmPaymentIntent` again with the same `PaymentIntent` to retry the request. | | `PaymentIntent` is `nil` | Request to Stripe timed out, unknown `PaymentIntent` status | Retry confirming the original `PaymentIntent`. Don’t create a new one, because that might result in multiple authorizations for the cardholder. | If you encounter multiple, consecutive timeouts, there might be a problem with your connectivity. Make sure that your app can communicate with the internet. ### Avoid double charges The `PaymentIntent` object enables money movement at Stripe—use a single `PaymentIntent` to represent a transaction. Re-use the same `PaymentIntent` after a card is declined (for example, if it has insufficient funds), so your customer can try again with a different card. If you edit the `PaymentIntent`, you must call `collectPaymentMethod` to update the payment information on the reader. A `PaymentIntent` must be in the `requires_payment_method` state before Stripe can confirm it. An authorized, captured, or canceled `PaymentIntent` can’t be confirmed by a reader. ## Capture the payment [Server-side] If you defined `capture_method` as `manual` during `PaymentIntent` creation in [Step 1](https://docs.stripe.com/terminal/payments/collect-card-payment.md#create-payment), the SDK returns an authorized but not captured `PaymentIntent` to your application. Learn more about the difference between [authorization and capture](https://docs.stripe.com/payments/place-a-hold-on-a-payment-method.md). When your app receives a confirmed `PaymentIntent` from the SDK, make sure it notifies your backend to capture the payment. Create an endpoint on your backend that accepts a `PaymentIntent` ID and sends a request to the Stripe API to capture it: ```curl curl -X POST https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/capture \ -u "<>:" ``` A successful `capture` call results in a `PaymentIntent` with a status of `succeeded`. To make sure the application fee captured is correct for connected accounts, inspect each `PaymentIntent` and modify the application fee, if needed, before manually capturing the payment. ### Reconcile payments To monitor the payments activity of your business, you might want to reconcile PaymentIntents with your internal orders system on your server at the end of a day’s activity. A `PaymentIntent` that retains a `requires_capture` status might represent two things: **Unnecessary authorization on your customer’s card statement** - Cause: User abandons your app’s checkout flow in the middle of a transaction - Solution: If the uncaptured `PaymentIntent` isn’t associated with a completed order on your server, you can [cancel](https://docs.stripe.com/api/payment_intents/cancel.md) it. You can’t use a canceled `PaymentIntent` to perform charges. **Incomplete collection of funds from a customer** - Cause: Failure of the request from your app notifying your backend to capture the payment - Solution: If the uncaptured `PaymentIntent` is associated with a completed order on your server, and no other payment has been taken for the order (for example, a cash payment), you can [capture](https://docs.stripe.com/api/payment_intents/capture.md) it. ### Collect tips (US only) In the US, eligible users can [collect a tip on the receipt when capturing payments](https://docs.stripe.com/terminal/features/collecting-tips/on-receipt.md).