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

# Payments

> Take a Tap on Phone sale end to end: describe it with CheckoutRequest, launch the checkout, follow what the operator and the cardholder see, handle the result, and find the transaction on the Rinne API.

One sale is one `CheckoutRequest`, one `launch`, and exactly one `PaymentResult`. This page follows a sale from the moment your app describes it to the moment it appears on the Rinne API.

## Describe the sale

```kotlin theme={null}
import com.rinne.sdk.PaymentMethod
import com.rinne.sdk.checkout.CheckoutRequest

val request = CheckoutRequest(
    amountCents = 4990,
    paymentMethod = PaymentMethod.CREDIT,
    installments = 3,
    requestId = order.id, // for example "order-2026-000418"
    metadata = mapOf("orderId" to order.id, "cashier" to "ana"),
)
```

<ParamField path="amountCents" type="Long" required>
  What to charge, in cents. Positive, BRL only. `4990` is R\$ 49,90.
</ParamField>

<ParamField path="paymentMethod" type="PaymentMethod" required>
  `CREDIT` or `DEBIT`. It is the credit-or-debit question every Brazilian point of sale asks before the tap, and it is recorded as the transaction's `payment_method`. The card reader picks the card application by its own rules and never reports which, so the operator's answer is the only source of the modality.
</ParamField>

<ParamField path="installments" type="Int" default="1">
  `1` is à vista. Values above 1 apply to credit only and are relayed to the acquirer. The header of the checkout shows the per-installment figure, for example "Crédito · 3x de R\$ 16,63".
</ParamField>

<ParamField path="requestId" type="String?" required>
  Your idempotency key, required on a purchase. The SDK never mints or changes it. Reuse the same value when you retry the same sale; a reused `requestId` with different sale data is refused by the platform. It is also how you find the transaction on the API.
</ParamField>

<ParamField path="metadata" type="Map<String, Any?>?" default="null">
  Your own data, stored against the transaction and returned with it. Capped at 4 KB serialized, and rejected rather than truncated when it is larger or when a key looks like card data (`pan`, `cardNumber`, `track2`, `cvv`, `expiryDate` and similar). Ordinary keys such as `panel` pass.
</ParamField>

<ParamField path="type" type="TransactionType" default="PURCHASE">
  `PURCHASE` for a sale. `REFUND` is a card re-tap for the full amount of an earlier sale, described in [Refunds](/tap-on-phone/android/refunds).
</ParamField>

A request that breaks these rules, such as a purchase without a `requestId`, throws `IllegalArgumentException` before the screen opens: it is a programming error, not a payment outcome.

## Launch the checkout

Register the contract once and launch it per sale. The result comes back through Android's activity-result machinery, which is what keeps it safe when the system kills your process while the payment screen is up.

```kotlin theme={null}
private val checkout = registerForActivityResult(Checkout.Contract()) { result ->
    when (result) {
        is PaymentResult.Approved -> showReceipt(result.transactionId)
        is PaymentResult.Declined -> offerAnotherMethod(result.code)
        is PaymentResult.Failed -> showError(result.code, result.message)
        is PaymentResult.Cancelled -> Unit
    }
}

checkout.launch(request)
```

The terminal must be prepared first with `Payments.getReady`; launching on an unprepared terminal ends in `Failed` with `TERMINAL_NOT_READY`. Preparing once at startup and again on resume is safe, because `getReady` returns at once on a prepared terminal.

## What happens on the phone

The SDK owns the screen from here until the operator dismisses the outcome. Every screen carries the amount and the descriptor built from your request.

| Screen                                  | What happens                                                                                                                                                                  | Who acts               |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| Getting ready                           | The device is attested and the card reader starts. The operator can cancel.                                                                                                   | Nobody                 |
| Tap to pay                              | The contactless artwork and the accepted marks are shown. The reader may replace the supporting line with guidance such as "Present the card again". The operator can cancel. | The cardholder taps    |
| Processing                              | The card was read and the transaction is authorised online. Nothing can cancel it any more.                                                                                   | Nobody                 |
| PIN pad                                 | Shown only when the card asks for a PIN, on the certified pad that covers the whole screen. The cardholder can cancel here.                                                   | The cardholder types   |
| Approved, Declined, Failed or Cancelled | The outcome, with **Try again** only where retrying could work.                                                                                                               | The operator taps Done |

Your callback fires only after the operator taps **Done**, so a second `launch` never races the first, and a decline can be retried from the screen without starting a second sale behind it. Walk through every screen in the [simulator](/tap-on-phone/android/simulator).

<Note>
  Whether a PIN is asked is decided by the card and the amount, not by your app. Cards ask above their contactless limit; the sandbox behaves the same way.
</Note>

## Handle the result

`Approved` means the money will move. `Declined` is the issuer's answer, so offer another card or payment method rather than retrying the same card. `Failed` is a technical fault to fix, with a code that says whether retrying can help. `Cancelled` means nobody was charged. Every variant and every code is on [Results and errors](/tap-on-phone/android/results-and-errors).

<Warning>
  Receipt fields such as the authorisation code, the masked card number and the timestamp are not on `PaymentResult`. Read them from the transaction record on the Rinne API. Inventing them from SDK identifiers puts fabricated data on a customer's receipt.
</Warning>

## Find the transaction on the API

The SDK registers the transaction with Rinne while authorising, under the `requestId` you sent. Look it up on the transactions list, which requires the `transaction.list` permission, or receive it through a `transaction.created` webhook:

```bash theme={null}
curl "https://api-sandbox.rinne.com.br/core/v1/transactions?request_id=order-2026-000418" \
  -H "x-api-key: $RINNE_API_KEY"
```

The record carries the amount, `payment_method`, your `metadata`, the status and the receipt fields. See [Transactions](/concepts/transactions) for the lifecycle and [Webhooks](/guides/webhooks) for the events.

## Reconcile after an interruption

If Android kills your process while the payment screen is up, the restored screen delivers `Failed` with a message telling you to reconcile. The transaction may well have been captured, so look it up by `requestId` before charging the customer again. An approved record means the sale went through; no record means you can retry with the same `requestId`.

## Next steps

<CardGroup cols={2}>
  <Card title="Refunds" icon="rotate-left" href="/tap-on-phone/android/refunds">
    Refund a sale with a card re-tap or from your backend.
  </Card>

  <Card title="Testing in the sandbox" icon="flask" href="/tap-on-phone/android/testing">
    Pick the outcome with the amount and verify every sale on the API.
  </Card>
</CardGroup>
