Skip to navigation

Cash App Pay on Android

The Afterpay Android SDK works with the Cash App Pay Kit 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.

Cash App Pay is available for US merchants.

Choose an integration

IntegrationHow it worksAfter the customer approves
Server checkoutYour 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 ButtonYour 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.
  • 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.

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

When the customer approves or declines in Cash App, Pay Kit returns them to your app through a redirect URI. Declare an intent filter for that URI on the activity that runs checkout:

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

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:

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:

StateWhat to do
ReadyToAuthorizeShow or enable your Cash App Pay button.
AuthorizingDisable the button so the customer can’t start a second authorization.
ApprovedRead the grant from responseData.grants and finish the payment.
DeclinedThe customer declined. Let them start again or choose another payment method.
CashAppPayExceptionStateAn unrecoverable error occurred, such as an integration or network error.

For the full list of states, see the Pay Kit Android documentation.

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

cashAppPay.unregisterFromStateUpdates()

Server checkout

In this integration, your server creates the checkout, as it does for Built-in UI. Pay Kit then handles the customer’s approval instead of the Afterpay checkout screen.

Request checkout POST /v2/checkouts (isCashAppPay: true) Checkout token Checkout token signCashAppOrderToken JWT and order details createCustomerRequest authorizeCustomerRequest Approved (grant) validateCashAppOrder Checkout token Capture or authorize payment App Afterpay SDK Pay Kit Merchant server Afterpay API

Step 1: Create a checkout on your server

Your server calls Create Checkout with isCashAppPay set to true, and returns the checkout token to your app. Set both merchant.redirectConfirmUrl and merchant.redirectCancelUrl.

{
"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:

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:

PropertyDescription
amountThe order amount in dollars, as a Double.
merchantIdThe ID to use as the Pay Kit scopeId.
brandIdThe Cash App brand ID for your merchant account.
redirectUriThe redirect URL from the signed order.
jwtThe 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:

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

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.

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:

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, or authorizes it with 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. 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.

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.

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:

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

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:

PropertyDescription
cardDetailsThe one-time virtual card.
cardValidUntilThe time before which you must authorize the card, as an Instant, or null if Afterpay didn’t return one.
tokensThe 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.
  • For sandbox customer accounts, see Test environments.
  • For a working Afterpay Button integration, see CashAppV3SampleActivity in the SDK sample app.