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

Accept Cash App Pay in your iOS app with your Afterpay merchant account. The Afterpay SDK works with the [Cash App Pay Kit iOS SDK](https://github.com/cashapp/cash-app-pay-ios-sdk): Pay Kit sends the customer to Cash App to approve the payment, and the Afterpay SDK connects that approval to your Afterpay order.

> **Info**
>
> Cash App Pay is available in the US.

You can accept Cash App Pay in two ways:

| Flow                                | How it works                                                                                                                             | Your server                                     |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| [Server checkout](#server-checkout) | Your server creates an Afterpay checkout for Cash App Pay. After the customer approves the payment in Cash App, your server captures it. | Creates the checkout and captures the payment.  |
| [Afterpay Button](#afterpay-button) | The SDK creates the checkout from the order details. After the customer approves the payment, the SDK returns a one-time virtual card.   | Processes the virtual card like any other card. |

## Set up Pay Kit

Both flows use the same Pay Kit setup.

### Install Pay Kit

Add the `cash-app-pay-ios-sdk` package with Swift Package Manager:

```swift
.package(
  url: "https://github.com/cashapp/cash-app-pay-ios-sdk",
  .upToNextMajor(from: "0.6.1")
)
```

Add the `PayKit` product to your app target, and add `PayKitUI` if you use the Cash App Pay button. You can also install Pay Kit with CocoaPods: `pod 'CashAppPayKit'` and `pod 'CashAppPayKitUI'`.

### Handle the redirect from Cash App

After the customer approves or declines the payment, Cash App opens your app with a redirect URL. Register a [custom URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) or a [universal link](https://developer.apple.com/ios/universal-links/) for your app. Then post `CashAppPay.RedirectNotification` when your app opens the URL:

```swift
import PayKit
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  func scene(
    _ scene: UIScene,
    openURLContexts URLContexts: Set<UIOpenURLContext>
  ) {
    guard let url = URLContexts.first?.url else { return }
    NotificationCenter.default.post(
      name: CashAppPay.RedirectNotification,
      object: nil,
      userInfo: [UIApplication.LaunchOptionsKey.url: url]
    )
  }
}
```

### Observe Pay Kit states

Pay Kit reports its progress through the `CashAppPayObserver` protocol. Update your UI for each state:

```swift
extension CheckoutViewController: CashAppPayObserver {
  func stateDidChange(to state: CashAppPayState) {
    switch state {
    case .readyToAuthorize(let customerRequest):
      // Enable the Cash App Pay button.
      self.customerRequest = customerRequest
    case .approved(let customerRequest, let grants):
      // Complete the order with the first grant. See the next sections.
      break
    case .declined:
      // Let the customer choose another payment method.
      break
    case .redirecting, .polling:
      // Optionally show a loading indicator.
      break
    case .integrationError, .apiError, .unexpectedError, .networkError:
      // Hide Cash App Pay or let the customer try again.
      break
    default:
      break
    }
  }
}
```

| State                    | What to do                                                                                    |
| ------------------------ | --------------------------------------------------------------------------------------------- |
| `readyToAuthorize`       | Show the Cash App Pay button. When the customer taps it, call `authorizeCustomerRequest(_:)`. |
| `approved`               | The customer approved the payment. Use the grant to complete the order.                       |
| `declined`               | The customer declined the payment. Start again or offer another payment method.               |
| `redirecting`, `polling` | Pay Kit is opening Cash App or waiting for the result. Optionally show a loading indicator.   |
| `integrationError`       | Your integration moved between states in an unsupported order. Fix the integration.           |
| `apiError`               | The Cash App Pay API isn't available. Hide Cash App Pay for now.                              |
| `networkError`           | The device couldn't connect. Let the customer try again.                                      |
| `unexpectedError`        | Contact Cash App developer support.                                                           |

> **Warning**
>
> If Cash App isn't installed, the customer is sent to a web page to download it, and Pay Kit stays in the `polling` state. Set a timeout for this state. When it expires, dismiss any loading indicator and let the customer start again or choose another payment method.

## Server checkout

This flow uses the merchant configuration that you set with `Afterpay.setConfiguration(_:)`. For details, see [Configure the SDK](/afterpay-online-developer/guides/sdk/ios-sdk#configure-the-sdk).

### Step 1: Create a checkout for Cash App Pay

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

> **Warning**
>
> Set both `merchant.redirectConfirmUrl` and `merchant.redirectCancelUrl` in the request. If you don't, Create Checkout returns an error.

### Step 2: Create Pay Kit

Create Pay Kit with `Afterpay.cashAppClientId` and the same environment as your Afterpay configuration:

```swift
private var payKit: CashAppPay?

func setUpPayKit() {
  // cashAppClientId is nil until you set the Afterpay configuration.
  guard let clientID = Afterpay.cashAppClientId else { return }

  let payKit = CashAppPay(
    clientID: clientID,
    endpoint: Afterpay.environment == .production
      ? .production
      : .sandbox
  )
  payKit.addObserver(self)
  self.payKit = payKit
}
```

### Step 3: Sign the token and create a customer request

Sign the checkout token with the Afterpay SDK. Then use the signing data to create a Pay Kit customer request. Do this as soon as your checkout screen loads, so that the customer can approve the payment without waiting.

The completion handler runs on a background queue, so move to the main queue before you update your UI. If you haven't set the configuration, or `Afterpay.enabled` is `false`, the SDK doesn't call the completion handler.

```swift
Afterpay.signCashAppOrderToken(token) { result in
  DispatchQueue.main.async {
    switch result {
    case .success(let cashData):
      self.cashData = cashData
      self.payKit?.createCustomerRequest(
        params: CreateCustomerRequestParams(
          actions: [
            .oneTimePayment(
              scopeID: cashData.brandId,
              money: Money(amount: cashData.amount, currency: .USD)
            ),
          ],
          // Your app's redirect URL from "Handle the redirect".
          redirectURL: URL(string: "yourapp://cashapp")!,
          referenceID: nil,
          metadata: nil
        )
      )
    case .failed:
      // Hide the Cash App Pay button, or create a new checkout.
      self.hideCashAppPay()
    }
  }
}
```

Pay Kit moves to `creatingCustomerRequest`, then to `readyToAuthorize`.

### Step 4: Authorize the customer request

When the customer taps the Cash App Pay button, authorize the request. Pay Kit opens Cash App.

```swift
@objc func didTapCashAppPay() {
  guard let customerRequest else { return }
  payKit?.authorizeCustomerRequest(customerRequest)
}
```

### Step 5: Validate the order

When Pay Kit moves to `approved`, validate the order with the Afterpay SDK. Don't skip this step. As with signing, the completion handler runs on a background queue.

```swift
case .approved(let customerRequest, let grants):
  guard
    let customerId = customerRequest.customerProfile?.id,
    let grant = grants.first
  else { return }

  Afterpay.validateCashAppOrder(
    jwt: cashData.jwt,
    customerId: customerId,
    grantId: grant.id
  ) { result in
    DispatchQueue.main.async {
      switch result {
      case .success:
        // Ask your server to capture the payment.
        self.completeOrder()
      case .failed:
        // Start the payment again.
        self.restartPayment()
      }
    }
  }
```

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

This flow uses the Afterpay Button configuration instead of the merchant configuration:

```swift
Afterpay.setV3Configuration(
  CheckoutV3Configuration(
    shopDirectoryMerchantId: "your_merchant_id",
    region: .US,
    environment: .sandbox
  )
)
```

### Step 1: Create the checkout

Call `checkoutV3WithCashAppPay` with the customer and order total. Store the result.

```swift
Afterpay.checkoutV3WithCashAppPay(
  consumer: Consumer(email: "customer@example.com"),
  orderTotal: OrderTotal(total: 100, shipping: .zero, tax: .zero)
) { result in
  switch result {
  case .success(let payload):
    self.payload = payload
    // Create the customer request with payload.cashAppSigningData.
  case .cancelled(let reason):
    // Signing failed. Hide Cash App Pay or start again.
    print("Signing failed: \(reason)")
  case .failure(let error):
    // Creating the checkout failed.
    print("Checkout failed: \(error)")
  }
}
```

### Step 2: Create Pay Kit and the customer request

Create Pay Kit with `Afterpay.checkoutV3CashAppClientId`, which reads the Afterpay Button configuration. For the endpoint, use the same environment that you passed to `CheckoutV3Configuration`: `Afterpay.environment` reads the merchant configuration, so it's `nil` if you set only the Afterpay Button configuration. Then create the customer request with the signing data from the payload:

```swift
let signingData = payload.cashAppSigningData

payKit?.createCustomerRequest(
  params: CreateCustomerRequestParams(
    actions: [
      .oneTimePayment(
        scopeID: signingData.brandId,
        money: Money(amount: signingData.amount, currency: .USD)
      ),
    ],
    // Your app's redirect URL from "Handle the redirect".
    redirectURL: URL(string: "yourapp://cashapp")!,
    referenceID: nil,
    metadata: nil
  )
)
```

When Pay Kit moves to `readyToAuthorize` and the customer taps the Cash App Pay button, call `authorizeCustomerRequest(_:)`.

### Step 3: Confirm the checkout

When Pay Kit moves to `approved`, confirm the checkout with the grant. The SDK returns the virtual card details and the time before which you must authorize the card.

```swift
Afterpay.checkoutV3ConfirmForCashAppPay(
  token: payload.token,
  singleUseCardToken: payload.singleUseCardToken,
  cashAppPayCustomerID: grant.customerID,
  cashAppPayGrantID: grant.id,
  jwt: payload.cashAppSigningData.jwt
) { result in
  switch result {
  case .success(let response):
    // Process response.paymentDetails before response.cardValidUntil.
    break
  case .failure(let error):
    // Handle the error.
    break
  }
}
```

## Test Cash App Pay

In the sandbox, test with the [Cash App sandbox app](/cash-app-pay-partner-api/guides/technical-guides/sandbox/sandbox-app). Make sure that Pay Kit and the Afterpay SDK use the same environment.