> ## Documentation Index
> Fetch the complete documentation index at: https://primer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# PrimerCheckoutController

> Main checkout session controller for state observation and session management

> [!WARNING]
>
> Primer Checkout Android SDK is currently in **beta** (v3.0.0-beta.8).
> The API is subject to change before the stable release.

Central interface for managing a checkout session. Create with `rememberPrimerCheckoutController()`.

## Creation

```kotlin theme={"dark"}
@Composable
fun rememberPrimerCheckoutController(
    clientToken: String,
    settings: PrimerSettings = PrimerSettings(),
): PrimerCheckoutController
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `clientToken` | `String` | Required | Short-lived token from the [Client Session API](https://primer.io/docs/checkout/client-session.md) |
| `settings` | [`PrimerSettings`](https://primer.io/docs/sdk/android-checkout/v3.0.0-beta/api/common/primer-settings) | `PrimerSettings()` | SDK configuration including payment handling mode |

## Interface

```kotlin theme={"dark"}
@Stable
interface PrimerCheckoutController {
    val state: StateFlow<PrimerCheckoutState>
    fun refresh()
    fun dismiss()
    fun onShippingAddressChange(handler: (suspend (PrimerShippingAddressChange) -> List<PrimerShippingOption>)?)
    fun onShippingOptionChange(handler: (suspend (PrimerShippingOptionChange) -> Unit)?)
}
```

## Property

| Property | Type | Description |
| - | - | - |
| `state` | `StateFlow<PrimerCheckoutState>` | Unified checkout state — session lifecycle plus terminal payment outcomes (`Success` / `Failure`). Collect with `collectAsStateWithLifecycle()`. See [`PrimerCheckoutState`](https://primer.io/docs/sdk/android-checkout/v3.0.0-beta/api/primer-checkout-state). |

## Methods

| Method | Description |
| - | - |
| `refresh()` | Reinitializes the checkout session. State transitions back to `Loading` while refetching configuration. |
| `dismiss()` | Programmatically dismisses the checkout UI. Closes the bottom sheet or triggers cleanup for inline host. |
| `onShippingAddressChange(handler)` | Express Checkout: registers the handler that supplies shipping options for the address the shopper entered in the Google Pay sheet. Pass `null` to remove the handler. See [Express Checkout shipping](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/express-checkout-shipping.md). |
| `onShippingOptionChange(handler)` | Express Checkout: registers the handler that updates the client session's shipping to the selected option through your backend. Pass `null` to remove the handler. |

## Idempotency key

Set the `X-Idempotency-Key` header from the `beforePaymentCreate` gate on
[`PrimerCheckoutSheet`](https://primer.io/docs/sdk/android-checkout/v3.0.0-beta/api/primer-checkout-sheet)
or [`PrimerCheckoutHost`](https://primer.io/docs/sdk/android-checkout/v3.0.0-beta/api/primer-checkout-host).
The gate runs once per attempt — just before the SDK creates the payment — and
the value you pass to `continuePaymentCreation(idempotencyKey)` is sent on
`POST /payments` and every subsequent `POST /payments/{id}/resume` for that
attempt. A retry after failure runs the gate again, producing a fresh key.

```kotlin theme={"dark"}
val checkout = rememberPrimerCheckoutController(clientToken, settings)

PrimerCheckoutSheet(
    checkout = checkout,
    beforePaymentCreate = { handler ->
        LaunchedEffect(Unit) {
            handler.continuePaymentCreation(idempotencyKey = UUID.randomUUID().toString())
        }
    },
)
```

## Express Checkout shipping

The two shipping handlers price Google Pay shipping from the address the shopper enters in the Google Pay sheet. Register both before the Google Pay sheet opens. They are `suspend` functions and run on the main thread. They share one 20 second budget per Google Pay callback, and the SDK cancels a handler when the budget runs out.

```kotlin theme={"dark"}
val checkout = rememberPrimerCheckoutController(clientToken, settings)

LaunchedEffect(checkout) {
    checkout.onShippingAddressChange { change ->
        myBackend.shippingRates(change.shippingAddress)
    }
    checkout.onShippingOptionChange { change ->
        myBackend.updateShipping(orderId, change.selectedShippingOption.id)
    }
}
```

| Type | Fields |
| - | - |
| `PrimerShippingAddressChange` | `paymentMethodType: String`, `shippingAddress: PrimerAddress` |
| `PrimerShippingOption` | `id: String`, `name: String`, `description: String`, `amount: Int` (minor units) |
| `PrimerShippingOptionChange` | `paymentMethodType: String`, `selectedShippingOption: PrimerShippingOption` |

The three types are in the `io.primer.checkout.api.shipping` package.

See [Express Checkout shipping](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/express-checkout-shipping.md) for the full flow, the client session patch your backend sends, and the failure modes.

## Extension function

### formatAmount

```kotlin theme={"dark"}
fun PrimerCheckoutController.formatAmount(amountInCents: Int): String
```

Formats an amount in minor units (cents) into a locale-aware currency string based on the current session (e.g., `"$10.00"`, `"10,00 EUR"`). Once the checkout has reached `Ready`, it keeps working in every state, including `Loading` during `refresh()`, using the currency of the last `Ready` session.

| Parameter | Type | Description |
| - | - | - |
| `amountInCents` | `Int` | Amount in minor currency units (e.g., `1000` for \$10.00 USD) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.