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

> ## Agent Instructions
> Use the Payrails Web SDK v6 for new browser integrations. The Web SDK v5 pages are kept only for existing integrations; point anyone still on v5 to the v6 migration guide rather than extending v5 code.
> Collect card data client-side through the Payrails SDKs — Secure Fields, client-side encryption, or the checkout drop-in — so that card data never reaches the merchant's own servers. Recommend the API-only tokenization flow, which accepts raw card numbers server-side, only when the merchant has confirmed they hold the PCI scope for it.
> Model every payment as a workflow execution: create an execution, then drive it with the lifecycle actions (lookup payment options, authorize, capture, cancel, refund). Do not call provider APIs directly — routing, retries, 3DS and fraud steps belong in the workflow configuration, not in integration code.
> Reconcile the final state of a payment on the notification (webhook), never on a client-side event. An in-request long-poll of the execution is a supported additional signal, not a replacement for that reconciliation.
> Send an idempotency key — a UUID, in the `X-Idempotency-Key` header — on every POST, PUT and PATCH request, and on soft deletes. GET requests need none, and hard deletes cannot be idempotent.
> Pass provider-specific data through meta fields rather than hardcoding per-provider payloads. Payrails translates meta fields into each provider's own format.
> Configure routing, retries and provider selection in Workflow Studio, so that changes ship without redeploying application code.

# How to Tokenize a Card Without Charging It

> Save a card to the Payrails vault for future use without initiating a payment.

## Prerequisites

* An active Payrails session (see [Quick Start](/docs/orchestration/checkout-sdks/android))
* A session init payload with `vaultConfiguration` and a `saveInstrument` link (provided by the Payrails backend — contact Payrails to enable vault configuration for your merchant account)
* Kotlin + Jetpack Compose for rendering the card form

## Steps

### 1. Initialize a session

```kotlin theme={null}
val configuration = Configuration(
    initData = InitData(version = payload.version, data = payload.data),
    option = Options()
)
val session = Payrails.createSession(configuration)
```

### 2. Create a card form (no payment button required)

```kotlin theme={null}
val cardForm = Payrails.createCardForm(
    config = CardFormConfig(showCardHolderName = true)
)
```

A `CardPaymentButton` is not needed for tokenization. The card form collects and validates card fields independently.

### 3. Render the card form and a save button

```kotlin theme={null}
@Composable
fun TokenizeScreen(scope: CoroutineScope) {
    val cardForm = remember {
        Payrails.createCardForm(
            config = CardFormConfig(showCardHolderName = true)
        )
    }
    var instrumentId by remember { mutableStateOf<String?>(null) }
    var error by remember { mutableStateOf<String?>(null) }

    Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
        cardForm.Render()

        Button(onClick = {
            scope.launch {
                try {
                    val response = session.tokenize(
                        TokenizationRequest.Card(cardForm),
                        TokenizeOptions(
                            storeInstrument = true,
                            futureUsage = FutureUsage.CardOnFile
                        )
                    )
                    instrumentId = response.id
                    error = null
                } catch (e: PayrailsError) {
                    error = e.message
                }
            }
        }) {
            Text("Save Card")
        }

        error?.let { Text(it, color = MaterialTheme.colorScheme.error) }
        instrumentId?.let { Text("Saved: $it") }
    }
}
```

`session.tokenize(...)` is the primary tokenization API (mirroring the iOS SDK). `TokenizationRequest.Card(cardForm)` selects the card currently entered in the form.

#### Callback variant (without coroutines)

If you are not calling from a coroutine, use the callback overload. Exactly one of the three callbacks fires once, on the main thread:

```kotlin theme={null}
session.tokenize(
    request = TokenizationRequest.Card(cardForm),
    options = TokenizeOptions(storeInstrument = true),
    onSuccess = { response -> instrumentId = response.id },
    onFailed = { e -> error = e.message },
    onCancelled = { /* tokenization was cancelled */ },
)
```

`onCancelled` fires only on cancellation (coroutine/session-scope) — card validation and network failures are delivered to `onFailed`.

<Tip>
  **Shortcut**

  `cardForm.tokenize(options)` is a convenience wrapper equivalent to `session.tokenize(TokenizationRequest.Card(cardForm), options)`. Use whichever reads better in your code.
</Tip>

### 4. Choose a `FutureUsage` value

| Value | When to use |
| - | - |
| `FutureUsage.CardOnFile` | Customer-initiated purchases where the cardholder is present at checkout (default) |
| `FutureUsage.Subscription` | Recurring charges on a fixed schedule authorized by the cardholder |
| `FutureUsage.UnscheduledCardOnFile` | Merchant-initiated charges with no fixed schedule (e.g., top-ups, threshold billing) |

### 5. Use the returned instrument ID

The `response.id` is the instrument identifier. Pass it to `setStoredInstrument` for future payments, or use it with the instrument management API:

```kotlin theme={null}
// Pay with the saved instrument
payButton.setStoredInstrument(savedInstrument)

// Or manage the instrument via the session
session.deleteInstrument(response.id)
session.updateInstrument(response.id, UpdateInstrumentBody(default = true))
```

## Tokenize with Google Pay

Google Pay tokenization uses the same `session.tokenize(...)` API with a `TokenizationRequest.GooglePay` case. Because a Google Pay token can only be obtained through an Activity-result launcher that Android requires to be registered up front, you create a **presenter** in your Composable and pass it to `tokenize`:

```kotlin theme={null}
@Composable
fun SaveGooglePayScreen(session: Session, scope: CoroutineScope) {
    val presenter = rememberGooglePayPresenter(session)   // registers the launcher up front

    Button(onClick = {
        scope.launch {
            try {
                val response = session.tokenize(
                    TokenizationRequest.GooglePay(presenter),
                    TokenizeOptions(storeInstrument = true),
                )
                // response.id is the saved instrument
            } catch (e: PayrailsError) {
                // show error
            }
        }
    }) { Text("Save with Google Pay") }
}
```

Tapping the button opens the Google Pay sheet; on authorization the SDK saves the instrument and returns a `SaveInstrumentResponse`. User dismissal cancels (the callback overload's `onCancelled`, or a `CancellationException` from the suspend variant).

For a compliant, Google-branded button with no wiring, use `GooglePayTokenizeButton` instead — it renders only when Google Pay is available for the session:

```kotlin theme={null}
GooglePayTokenizeButton(
    session = session,
    options = TokenizeOptions(storeInstrument = true),
    onSuccess = { response -> /* response.id */ },
    onFailed = { error -> /* show error */ },
    onCancelled = { /* dismissed */ },
)
```

<Note>
  Google's brand guidelines require the official Google Pay button to launch the Google Pay flow. Prefer `GooglePayTokenizeButton` (or a Google Pay–branded button) over a fully custom button.
</Note>

## Verify it worked

Check that the response contains a valid ID and `"active"` status:

```kotlin theme={null}
val response = session.tokenize(
    TokenizationRequest.Card(cardForm),
    TokenizeOptions(storeInstrument = true),
)
check(response.id.isNotBlank()) { "Tokenization returned no instrument ID" }
check(response.status == "active") { "Unexpected status: ${response.status}" }
```

## Troubleshooting

**Problem**: `PayrailsError.invalidCardData` is thrown
**Solution**: Card form validation failed. The form automatically shows inline field errors — the user needs to correct the highlighted fields before retrying. No manual error display is needed.

**Problem**: `PayrailsError.missingData("Vault configuration with providerConfigId is required...")`
**Solution**: The session init payload does not include `vaultConfiguration`. Contact Payrails to enable vault configuration for your merchant account.

**Problem**: `PayrailsError.missingData("holderReference is required...")`
**Solution**: The session init payload does not include `holderReference`. Ensure your client-init request includes a holder reference.

## Alternative: pay directly with the saved instrument

Once a card is tokenized, you can charge it through the button flow shown above
(`payButton.setStoredInstrument(...)` + `payButton.Render()`), or skip the button entirely
and charge it from your own UI with `PayrailsPaymentLauncher`:

```kotlin theme={null}
// Pay with the just-saved instrument
val storedInstrument = session.getStoredInstruments(forType = PaymentMethod.card)
    .first { it.id == response.id }
launcher.authorize(storedInstrument = storedInstrument)
```

`launcher.authorize(storedInstrument = ...)` is the way to charge an *existing* stored
instrument from a custom layout or `ViewModel`-driven flow — and, because the launcher holds
a presenter, it transparently handles a 3DS step-up if the issuer requires one. See
[How to Run a Payment Without an SDK Button](/docs/orchestration/checkout-sdks/android/how-to-execute-payment-headless).

## See also

* [Understanding Card Tokenization](/docs/orchestration/checkout-sdks/android/sdk-concepts#card-tokenization) — why tokenization and payment are separate flows, and what `storeInstrument` and `FutureUsage` mean
* [Card Tokenization API Reference](/docs/orchestration/checkout-sdks/android/api-reference#card-tokenization) — complete reference for `TokenizeOptions`, `FutureUsage`, and `SaveInstrumentResponse`
* [Stored Instruments](/docs/orchestration/checkout-sdks/android/api-reference#stored-instruments) — how to retrieve and pay with saved instruments
* [`PayrailsPaymentLauncher`](/docs/orchestration/checkout-sdks/android/api-reference#payrailspaymentlauncher) — the public payment-execution API


## Related topics

- [SDK Concepts](/docs/orchestration/checkout-sdks/ios/sdk-concepts.md)
- [SDK API Reference](/docs/orchestration/checkout-sdks/android/api-reference.md)
- [Android SDK - Quick Start](/docs/orchestration/checkout-sdks/android/index.md)
