> This page is for Afterpay Online Developer, version Main (default).
> For other versions, use one of these documentation indexes:
> - Main (default): https://developers.afterpay.com/afterpay-online-developer/main/llms.txt
> - v1: https://developers.afterpay.com/afterpay-online-developer/v-1/llms.txt
> - Chinese: https://developers.afterpay.com/afterpay-online-developer/chinese/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.afterpay.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.afterpay.com/_mcp/server.

# Cash App Pay on Android

The Afterpay Android SDK works with the [Cash App Pay Kit Android SDK](https://github.com/cashapp/cash-app-pay-android-sdk) to accept Cash App Pay with your Afterpay merchant account. The Afterpay SDK prepares and verifies the order, and Pay Kit sends the customer to Cash App to approve the payment.

> **Info**
>
> Cash App Pay is available for US merchants.

## Choose an integration

| Integration                         | How it works                                                                                                                                              | After the customer approves                                                                                      |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [Server checkout](#server-checkout) | Your server creates a checkout with `isCashAppPay: true`. Your app signs the checkout token with the Afterpay SDK and creates a Pay Kit customer request. | Your app validates the order with the Afterpay SDK, and your server captures the payment with the Afterpay API.  |
| [Afterpay Button](#afterpay-button) | Your app passes the order details to the Afterpay SDK, which creates and signs the checkout. Your server doesn't call the Afterpay API.                   | The Afterpay SDK confirms the checkout and returns a one-time virtual card that you process like any other card. |

Both integrations share the same Pay Kit setup.

## Before you begin

* Install and configure the Afterpay SDK. See [Android SDK](/afterpay-online-developer/guides/sdk/android-sdk).
* Use the same environment everywhere. If you configure the Afterpay SDK with `AfterpayEnvironment.SANDBOX`, create a sandbox Pay Kit instance, and have your server use the Afterpay sandbox. Use production for all three in release builds.

## Set up Pay Kit

### Install Pay Kit

Add Pay Kit to your module's dependencies. The Afterpay SDK sample app uses version 2.3.0.

#### Kotlin DSL

```kotlin
dependencies {
  implementation("app.cash.paykit:core:2.3.0")
}
```

#### Groovy

```groovy
dependencies {
  implementation 'app.cash.paykit:core:2.3.0'
}
```

### Add a deep link

When the customer approves or declines in Cash App, Pay Kit returns them to your app through a redirect URI. Declare an [intent filter](https://developer.android.com/training/app-links/deep-linking#adding-filters) for that URI on the activity that runs checkout:

```xml
<activity
  android:name=".CheckoutActivity"
  android:launchMode="singleTop"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
      android:host="example.com"
      android:scheme="example" />
  </intent-filter>
</activity>
```

Use a custom scheme that only your app handles, or a [verified Android App Link](https://developer.android.com/training/app-links/verify-android-applinks). In this example, the redirect URI is `example://example.com/`.

### Create a Pay Kit instance

Create the instance with the Pay Kit client ID from `Afterpay.environment?.payKitClientId`. This value is available after you configure the Afterpay SDK, and it matches the configured environment.

```kotlin
private lateinit var cashAppPay: CashAppPay

private fun initializeCashAppPay() {
  val clientId = checkNotNull(Afterpay.environment?.payKitClientId) {
    "Configure the Afterpay SDK first"
  }
  cashAppPay = if (Afterpay.environment == AfterpayEnvironment.SANDBOX) {
    CashAppPayFactory.createSandbox(clientId)
  } else {
    CashAppPayFactory.create(clientId)
  }
}
```

`CashAppPayFactory.createSandbox` creates an instance that uses the Cash App Pay sandbox. `CashAppPayFactory.create` creates a production instance.

### Register for state updates

Pay Kit reports its progress through `CashAppPayListener`. Register a listener after you create the instance:

```kotlin
private val cashAppPayListener = object : CashAppPayListener {
  override fun cashAppPayStateDidChange(newState: CashAppPayState) {
    when (newState) {
      is ReadyToAuthorize -> runOnUiThread {
        cashAppPayButton.isEnabled = true
      }
      Authorizing -> runOnUiThread {
        cashAppPayButton.isEnabled = false
      }
      is Approved -> onApproved(newState.responseData)
      Declined -> {
        // Let the customer try again or pick another payment method.
      }
      is CashAppPayExceptionState -> {
        Log.e(TAG, "Cash App Pay error", newState.exception)
      }
      else -> Unit
    }
  }
}

cashAppPay.registerForStateUpdates(cashAppPayListener)
```

Pay Kit can call the listener from a background thread, so switch to the main thread before you update your UI.

These states drive your integration:

| State                      | What to do                                                                    |
| -------------------------- | ----------------------------------------------------------------------------- |
| `ReadyToAuthorize`         | Show or enable your Cash App Pay button.                                      |
| `Authorizing`              | Disable the button so the customer can't start a second authorization.        |
| `Approved`                 | Read the grant from `responseData.grants` and finish the payment.             |
| `Declined`                 | The customer declined. Let them start again or choose another payment method. |
| `CashAppPayExceptionState` | An unrecoverable error occurred, such as an integration or network error.     |

For the full list of states, see the [Pay Kit Android documentation](https://developers.cash.app/cash-app-pay-partner-api/guides/pay-kit-sdk/pay-kit-android/getting-started).

When you're done with Pay Kit, for example when the checkout screen closes, unregister the listener:

```kotlin
cashAppPay.unregisterFromStateUpdates()
```

## Server checkout

In this integration, your server creates the checkout, as it does for [Built-in UI](/afterpay-online-developer/guides/sdk/android-sdk/present-checkout/built-in-ui). Pay Kit then handles the customer's approval instead of the Afterpay checkout screen.

```mermaid
%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#FFF',
      'primaryTextColor': '#000',
      'primaryBorderColor': '#000',
      'lineColor': '#000',
      'secondaryColor': '#fff',
      'tertiaryColor': '#fff',
      'noteBkgColor': '#fff',
      'noteBorderColor': '#000',
      'background': '#fff'
    }
  }
}%%
sequenceDiagram
    participant App
    participant SDK as Afterpay SDK
    participant PayKit as Pay Kit
    participant Server as Merchant server
    participant API as Afterpay API
    App->>Server: Request checkout
    Server->>API: POST /v2/checkouts (isCashAppPay: true)
    API-->>Server: Checkout token
    Server-->>App: Checkout token
    App->>SDK: signCashAppOrderToken
    SDK-->>App: JWT and order details
    App->>PayKit: createCustomerRequest
    App->>PayKit: authorizeCustomerRequest
    PayKit-->>App: Approved (grant)
    App->>SDK: validateCashAppOrder
    App->>Server: Checkout token
    Server->>API: Capture or authorize payment
```

### Step 1: Create a checkout on your server

Your server calls [Create Checkout](/afterpay-online-developer/api-reference/reference/checkouts/create-checkout-1) with `isCashAppPay` set to `true`, and returns the checkout token to your app. Set both `merchant.redirectConfirmUrl` and `merchant.redirectCancelUrl`.

```json
{
  "amount": { "amount": "100.00", "currency": "USD" },
  "consumer": { "email": "customer@example.com" },
  "merchant": {
    "redirectConfirmUrl": "https://example.com/confirm",
    "redirectCancelUrl": "https://example.com/cancel"
  },
  "isCashAppPay": true
}
```

### Step 2: Sign the checkout token

Pass the token to `Afterpay.signCashAppOrderToken`. The SDK signs the order with Afterpay and returns the data you need for Pay Kit.

`signCashAppOrderToken` is a suspending function, and Pay Kit's `createCustomerRequest` must run on a background thread, so call both from a coroutine on `Dispatchers.IO`:

```kotlin
private var cashAppJwt: String? = null

private fun signOrder(token: String) {
  lifecycleScope.launch(Dispatchers.IO) {
    Afterpay.signCashAppOrderToken(token) { result ->
      when (result) {
        is CashAppSignOrderResult.Success -> {
          cashAppJwt = result.response.jwt
          createCustomerRequest(result.response)
        }
        is CashAppSignOrderResult.Failure -> {
          Log.e(TAG, "Signing failed", result.error)
        }
      }
    }
  }
}
```

A successful result contains an `AfterpayCashApp` with these properties:

| Property      | Description                                                                           |
| ------------- | ------------------------------------------------------------------------------------- |
| `amount`      | The order amount in dollars, as a `Double`.                                           |
| `merchantId`  | The ID to use as the Pay Kit `scopeId`.                                               |
| `brandId`     | The Cash App brand ID for your merchant account.                                      |
| `redirectUri` | The redirect URL from the signed order.                                               |
| `jwt`         | The signed order, as a JSON Web Token (JWT). Keep it to validate the order in step 5. |

From Java, call `Afterpay.signCashAppOrderTokenAsync`, which returns a `CompletableFuture`. In Kotlin, the async variant requires opting in to `DelicateCoroutinesApi`.

### Step 3: Create a Pay Kit customer request

Create a one-time payment action. Pay Kit expects the amount in cents, so convert the amount from the signed order:

```kotlin
private fun createCustomerRequest(cashApp: AfterpayCashApp) {
  val action = CashAppPayPaymentAction.OneTimeAction(
    currency = CashAppPayCurrency.USD,
    amount = (cashApp.amount * 100).roundToInt(),
    scopeId = cashApp.merchantId,
  )
  cashAppPay.createCustomerRequest(
    action,
    redirectUri = "example://example.com/",
  )
}
```

Pass the redirect URI that matches your [deep link](#add-a-deep-link). When the request is ready, Pay Kit moves to the `ReadyToAuthorize` state.

### Step 4: Authorize the customer request

When the customer taps your Cash App Pay button, start the authorization. Pay Kit opens Cash App, where the customer approves or declines.

```kotlin
cashAppPayButton.setOnClickListener {
  cashAppPay.authorizeCustomerRequest()
}
```

Pay Kit's Cash App Pay button isn't connected to Pay Kit's state. You call `authorizeCustomerRequest` when it's tapped, and you enable or disable it as the state changes. See the [Pay Kit Android documentation](https://developers.cash.app/cash-app-pay-partner-api/guides/pay-kit-sdk/pay-kit-android/getting-started).

When the customer returns to your app, Pay Kit moves to `Approved` or `Declined`.

### Step 5: Validate the order

> **Don't skip validation**
>
> When Pay Kit reports `Approved`, validate the order with Afterpay before you capture the payment.

Call `Afterpay.validateCashAppOrder` with the JWT from step 2, the customer ID, and the grant ID. It blocks while it calls Afterpay, so call it from a background thread:

```kotlin
private fun onApproved(data: CustomerResponseData) {
  val jwt = cashAppJwt ?: return
  val customerId = data.customerProfile?.id ?: return
  val grant = data.grants?.firstOrNull() ?: return

  lifecycleScope.launch(Dispatchers.IO) {
    Afterpay.validateCashAppOrder(jwt, customerId, grant.id) {
      when (it) {
        is CashAppValidationResponse.Success -> {
          // Ask your server to capture the payment.
        }
        is CashAppValidationResponse.Failure -> {
          Log.e(TAG, "Validation failed", it.error)
        }
      }
    }
  }
}
```

The result is `Success` only when Afterpay reports a `SUCCESS` status. A successful result contains the customer's `cashAppTag`.

From Java, call `Afterpay.validateCashAppOrderAsync`, which returns a `CompletableFuture`.

### Step 6: Capture the payment

Your server captures the payment with [Capture Full Payment](/afterpay-online-developer/api-reference/reference/payments/capture-full-payment), or authorizes it with [Auth](/afterpay-online-developer/api-reference/reference/payments/auth) to capture later. Use the checkout token, as for any other Afterpay order. The `grantId` request field is only for payments with a stored on-file grant, so don't send the grant from this checkout.

## Afterpay Button

In this integration, the Afterpay SDK creates the checkout from your app, as it does for the [Afterpay Button](/afterpay-online-developer/guides/sdk/android-sdk/present-checkout/afterpay-button). After the customer approves in Cash App, the SDK returns a one-time virtual card.

`beginCheckoutV3WithCashAppPay` and `confirmCheckoutV3WithCashAppPay` are Kotlin suspending functions with no Java variants.

### Step 1: Configure the SDK

Set the Afterpay Button configuration, then fetch your merchant configuration and apply it with `setConfigurationV3`. The merchant configuration sets `Afterpay.environment`, which provides the Pay Kit client ID.

```kotlin
val checkoutConfiguration = CheckoutV3Configuration(
  shopDirectoryMerchantId = "your_merchant_id",
  region = AfterpayRegion.US,
  environment = AfterpayEnvironment.SANDBOX,
)
Afterpay.setCheckoutV3Configuration(checkoutConfiguration)

lifecycleScope.launch {
  val result = withContext(Dispatchers.IO) {
    Afterpay.fetchMerchantConfigurationV3()
  }
  result
    .onSuccess { configuration ->
      // Set the configuration on the main thread.
      Afterpay.setConfigurationV3(configuration)
      initializeCashAppPay()
      cashAppPay.registerForStateUpdates(cashAppPayListener)
      // Pay Kit's createCustomerRequest must run off the main thread.
      withContext(Dispatchers.IO) { beginCheckout() }
    }
    .onFailure { Log.e(TAG, "Configuration failed", it) }
}
```

`fetchMerchantConfigurationV3` makes a blocking network request, so call it from a background thread. Call `setConfigurationV3` on the main thread, because it updates any Afterpay views on the calling thread. Store the configuration and apply it again each time your app starts, instead of fetching it before every checkout.

### Step 2: Begin checkout

Call `Afterpay.beginCheckoutV3WithCashAppPay` with the customer, the order total, and optionally the items. The SDK creates the checkout and signs it, so you don't call `signCashAppOrderToken` in this integration.

```kotlin
private var checkout: CheckoutV3CashAppPay? = null

private suspend fun beginCheckout() {
  Afterpay.beginCheckoutV3WithCashAppPay(
    consumer = Consumer(email = "customer@example.com"),
    orderTotal = OrderTotal(
      total = BigDecimal("100.00"),
      shipping = BigDecimal.ZERO,
      tax = BigDecimal.ZERO,
    ),
    items = cartItems, // Array<CheckoutV3Item>
  )
    .onSuccess {
      checkout = it
      createCustomerRequest(it)
    }
    .onFailure { Log.e(TAG, "Checkout failed", it) }
}
```

If you didn't call `setCheckoutV3Configuration`, pass the configuration in the `configuration` parameter.

Keep the returned `CheckoutV3CashAppPay`. It contains the `token`, `singleUseCardToken`, and `jwt` that you need to confirm the checkout, and the `amount` and `brandId` for Pay Kit.

### Step 3: Create a Pay Kit customer request

Use the `brandId` as the scope ID, and convert the amount to cents:

```kotlin
private fun createCustomerRequest(checkout: CheckoutV3CashAppPay) {
  val action = CashAppPayPaymentAction.OneTimeAction(
    currency = CashAppPayCurrency.USD,
    amount = (checkout.amount * 100).roundToInt(),
    scopeId = checkout.brandId,
  )
  cashAppPay.createCustomerRequest(
    action,
    redirectUri = "example://example.com/",
  )
}
```

### Step 4: Authorize the customer request

Authorize the request when the customer taps your Cash App Pay button, as in the [server checkout](#step-4-authorize-the-customer-request).

### Step 5: Confirm the checkout

When Pay Kit reports `Approved`, pass the grant ID and customer ID to `Afterpay.confirmCheckoutV3WithCashAppPay`, along with the values you kept from step 2. This call sends the grant to Afterpay, so you don't call `validateCashAppOrder` in this integration.

```kotlin
private fun onApproved(data: CustomerResponseData) {
  val checkout = checkout ?: return
  val grant = data.grants?.firstOrNull() ?: return

  lifecycleScope.launch(Dispatchers.IO) {
    Afterpay.confirmCheckoutV3WithCashAppPay(
      customerId = grant.customerId,
      grantId = grant.id,
      token = checkout.token,
      singleUseCardToken = checkout.singleUseCardToken,
      jwt = checkout.jwt,
    )
      .onSuccess { processCard(it) }
      .onFailure { Log.e(TAG, "Confirmation failed", it) }
  }
}
```

### Step 6: Process the virtual card

A successful result is a `CheckoutV3Data`:

| Property         | Description                                                                                                  |
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
| `cardDetails`    | The one-time virtual card.                                                                                   |
| `cardValidUntil` | The time before which you must authorize the card, as an `Instant`, or `null` if Afterpay didn't return one. |
| `tokens`         | The tokens you need to update the merchant reference.                                                        |

Send the card details to your server and process them through your existing card payments flow.

## Test in the sandbox

* Use a sandbox Afterpay configuration, a sandbox Pay Kit instance, and your server's sandbox credentials together.
* To approve payments without a real Cash App account, use the [Cash App sandbox app](https://developers.cash.app/cash-app-pay-partner-api/guides/technical-guides/sandbox/sandbox-app).
* For sandbox customer accounts, see [Test environments](/afterpay-online-developer/guides/api-development/test-environments).
* For a working Afterpay Button integration, see [`CashAppV3SampleActivity`](https://github.com/afterpay/sdk-android/blob/master/sample/src/main/java/com/example/CashAppV3SampleActivity.kt) in the SDK sample app.