> This page is for Afterpay.

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

# Built-in UI on iOS

With Built-in UI, the SDK opens Afterpay's checkout right away and shows a loading state while your app gets a checkout token from your server. Because the checkout is open before the token exists, the customer sees a response the moment they tap your button. Built-in UI also supports [express checkout](/cash-app-afterpay/guides/api-development/additional-features/express-checkout), where the customer picks a shipping address and shipping option inside checkout.

Built-in UI is available in Swift with UIKit. The SDK calls it Checkout V2.

## Step 1: Create a checkout on your server

When the SDK asks for a token, your app calls your server, and your server calls [Create Checkout](/cash-app-afterpay/api-reference/reference/checkouts/create-checkout-1) and returns the `token` from the response.

```json
{
  "amount": { "amount": "100.00", "currency": "USD" },
  "consumer": { "email": "test@example.com" },
  "merchant": {
    "redirectConfirmUrl": "https://example.com/checkout/confirm",
    "redirectCancelUrl": "https://example.com/checkout/cancel",
    "popupOriginUrl": "https://static.afterpay.com"
  },
  "merchantReference": "merchant-order-123"
}
```

> **Warning**
>
> Set `merchant.popupOriginUrl` to `https://static.afterpay.com`. Otherwise, the checkout doesn't behave as expected.

For express checkout, also set `mode` to `express`. For the other express fields, see [Express checkout](/cash-app-afterpay/guides/api-development/additional-features/express-checkout).

## Step 2: Present the checkout

Call `presentCheckoutV2Modally` from the view controller that shows your cart. Set the [SDK configuration](/cash-app-afterpay/guides/sdk/ios-sdk#configure-the-sdk) first. Otherwise, debug builds trigger an assertion failure, and release builds return without presenting checkout or calling the completion handler.

```swift
Afterpay.presentCheckoutV2Modally(
  over: self,
  options: CheckoutV2Options(buyNow: true),
  didCommenceCheckout: { completion in
    // Call your server. Pass the token or an error to completion.
    checkoutService.createCheckout { result in
      completion(result)
    }
  },
  completion: { result in
    switch result {
    case .success(let token):
      // Send the token to your server to complete the order.
      break
    case .cancelled(let reason):
      // Handle the cancellation.
      break
    }
  }
)
```

If you pass an error to the `didCommenceCheckout` completion, the SDK shows a dialog that lets the customer retry or close checkout. If the customer's language isn't supported in your region, `Afterpay.enabled` is `false` and the SDK doesn't present checkout.

### Checkout options

Pass `CheckoutV2Options` to change the customer's experience in checkout:

| Option                   | Effect                                                                                                                                                                                                     |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `buyNow`                 | Shows a **Buy Now** button at the end of checkout, which signals that you capture the payment right away. Set it to `true` only if you capture the payment immediately, without a review step in your app. |
| `pickup`                 | For express checkout. Lets you set a pickup address in the `shipping` object when you create the checkout, and stops the customer from choosing a delivery address.                                        |
| `shippingOptionRequired` | For express checkout. Set it to `false` to stop the customer from choosing a shipping option in checkout.                                                                                                  |

## Step 3: Provide shipping options (express checkout)

For express checkout, the SDK calls your app when the customer chooses a shipping address and when they choose a shipping option. Return the shipping options for the address, or a `ShippingOptionsError`:

```swift
Afterpay.presentCheckoutV2Modally(
  over: self,
  options: CheckoutV2Options(shippingOptionRequired: true),
  didCommenceCheckout: { completion in
    checkoutService.createExpressCheckout { completion($0) }
  },
  shippingAddressDidChange: { address, completion in
    let standard = ShippingOption(
      id: "standard",
      name: "Standard",
      description: "3-5 days",
      shippingAmount: Money(amount: "5.00", currency: "USD"),
      orderAmount: Money(amount: "105.00", currency: "USD")
    )
    completion(.success([standard]))
  },
  shippingOptionDidChange: { shippingOption, completion in
    // Return an updated amount for the option, or nil to keep it.
    completion(nil)
  },
  completion: { result in
    // Handle the result.
  }
)
```

`orderAmount` is the order total including shipping and tax. The `ShippingOptionsError` cases are `shippingAddressUnrecognized`, `shippingAddressUnsupported`, `serviceUnavailable`, and `badResponse`. Checkout shows an error message to the customer for each one.

### Use a checkout handler

To handle these callbacks outside the presenting view controller, implement `CheckoutV2Handler` and register it with `Afterpay.setCheckoutV2Handler(_:)`. The protocol has the same three methods: `didCommenceCheckout(completion:)`, `shippingAddressDidChange(address:completion:)`, and `shippingOptionDidChange(shippingOption:completion:)`. The SDK holds the handler weakly, so keep a strong reference to it.

## Step 4: Complete the order

A successful result contains the order token. A canceled result contains a `CancellationReason`, such as `userInitiated`.

Send the order token to your server. To capture the full amount immediately, your server calls [Capture Full Payment](/cash-app-afterpay/api-reference/reference/payments/capture-full-payment) with the token. To capture later, call [Auth](/cash-app-afterpay/api-reference/reference/payments/auth) and then [Capture Payment](/cash-app-afterpay/api-reference/reference/payments/capture-payment).