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

# Results and errors

> The PaymentResult your app receives after a Tap on Phone checkout, every ErrorCode with its meaning and retry rule, what ReadyResult reports at setup, and how to reconcile after a process death.

Every checkout ends in exactly one `PaymentResult`, delivered to the callback you registered with `Checkout.Contract` after the operator dismisses the outcome screen. Preparing the terminal ends in exactly one `ReadyResult`. Both use the same thirteen `ErrorCode` values.

## How a checkout ends

```mermaid theme={null}
stateDiagram-v2
    [*] --> Preparing: launch
    Preparing --> PresentCard: terminal ready
    Preparing --> Failed: setup problem
    PresentCard --> Processing: card read
    PresentCard --> Cancelled: operator cancels
    Processing --> PinEntry: card asks for a PIN
    Processing --> Approved: authorised
    Processing --> Declined: issuer refused
    Processing --> Failed: technical fault
    PinEntry --> Processing: PIN accepted
    PinEntry --> Cancelled: cardholder cancels
    PinEntry --> Failed: PIN not accepted
    Declined --> PresentCard: Try again, retriable codes only
    Failed --> PresentCard: Try again, retriable codes only
    Cancelled --> PresentCard: Try again
    Approved --> [*]: Done
    Declined --> [*]: Done
    Failed --> [*]: Done
    Cancelled --> [*]: Done
```

The operator reads the outcome and taps **Done** before your callback fires, so a second `launch` never races the first. Try every path in the [simulator](/tap-on-phone/android/simulator): the Scenario section picks the outcome and the error code.

## PaymentResult

```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
    }
}
```

<ResponseField name="Approved" type="PaymentResult">
  Authorised. The money will move.

  <Expandable title="properties">
    <ResponseField name="transactionId" type="String?">
      The SDK-side identifier of this attempt, for correlating with the transaction record on the Rinne API.
    </ResponseField>

    <ResponseField name="cardScheme" type="String?">
      The accepted scheme, such as `VISA` or `MASTERCARD`, when the card reader reported one.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="Declined" type="PaymentResult">
  The issuer or the card refused. The transaction completed correctly and the answer was no, so retrying the same card usually fails the same way.

  <Expandable title="properties">
    <ResponseField name="transactionId" type="String?">
      The SDK-side identifier of this attempt.
    </ResponseField>

    <ResponseField name="code" type="ErrorCode">
      Why it was refused. Almost always `DECLINED_BY_ISSUER`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="Failed" type="PaymentResult">
  Did not complete for a technical reason. This is a fault to fix, not an answer to relay to the customer.

  <Expandable title="properties">
    <ResponseField name="code" type="ErrorCode">
      The stable reason.
    </ResponseField>

    <ResponseField name="message" type="String?">
      Underlying detail, when there is one. Diagnostic text for your logs, never suitable to show a cardholder.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="Cancelled" type="PaymentResult">
  Ended deliberately before an outcome existed, by the operator, the cardholder at the PIN pad or your app.

  <Expandable title="properties">
    <ResponseField name="code" type="ErrorCode" default="CANCELLED">
      `CANCELLED` for a real cancellation. Another code appears when the card reader abandoned the transaction for a reason worth surfacing, such as `TERMINAL_NOT_READY`.
    </ResponseField>
  </Expandable>
</ResponseField>

Every variant also carries `raw`, a JSON snapshot of the card reader's response for support triage, and `diagnostics`, decoded card diagnostics when the reader returned any. Neither ever contains cardholder data.

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

### After a process death

If Android kills your process while the payment screen is up, the restored screen delivers `Failed` with a message telling you to reconcile against the Rinne API using your `requestId`. It is deliberately not `Cancelled`: nobody cancelled anything, and the transaction may well have been captured.

Reconcile before charging the customer again, with a key that holds the `transaction.list` permission:

```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"
```

An approved transaction in the response means the sale went through; an empty list means it did not, and you can retry with the same `requestId`.

## ReadyResult

`Payments.getReady` answers with `Ready` or `Failed(code, message)`. Three codes are worth handling at setup, because each one names who fixes it:

```kotlin theme={null}
Payments.getReady(context, accessTokenProvider = { backend.fetchTapOnPhoneToken() }, config = config, environment = Environment.SANDBOX) { result ->
    when (result) {
        is ReadyResult.Ready -> enablePayButton()
        is ReadyResult.Failed -> when (result.code) {
            ErrorCode.ACCESS_TOKEN_REJECTED -> refreshSessionAndRetry()
            ErrorCode.TAP_ON_PHONE_NOT_ENABLED -> showContactRinne()
            else -> showSetupError(result.code, result.message)
        }
    }
}
```

| `ErrorCode`                | Meaning                                                                                                                                | Who fixes it |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `ACCESS_TOKEN_REJECTED`    | The token was missing, expired or lacked the Tap on Phone scope. The SDK already asked your provider for a fresh one and retried once. | Your backend |
| `TAP_ON_PHONE_NOT_ENABLED` | The merchant has no active affiliation with contactless capture.                                                                       | Rinne        |
| `EMV_CONFIG_UNAVAILABLE`   | No card configuration is published yet for this SDK version.                                                                           | Rinne        |

## ErrorCode

The `code` on `Declined`, `Failed`, `Cancelled` and `ReadyResult.Failed` is one of thirteen values, in a fixed order that only ever grows. The managed checkout shows the operator text in the device language and offers **Try again** only where retrying could work. If you build your own error screen, key it off the code and use `ErrorCatalog[code]` for a default message, retry hint and `retriable` flag.

| `ErrorCode`                | Meaning                                                                                                                                                                | Retriable | Operator text                                                                                                                                                         |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TERMINAL_NOT_READY`       | No usable terminal: never prepared, disposed, or its session ended.                                                                                                    | Yes       | The card reader is not ready yet. Wait for setup to finish, then try again.                                                                                           |
| `ATTESTATION_FAILED`       | The device failed integrity checks. Usually this phone can never take payments.                                                                                        | No        | This device cannot accept payments. Check that the device is up to date and unmodified.                                                                               |
| `CARD_READ_TIMEOUT`        | The card was never read, or reading did not finish in time.                                                                                                            | Yes       | The card was not read. Hold the card flat against the back of the phone until it beeps.                                                                               |
| `PIN_FAILED`               | PIN entry failed, timed out, or the pad could not be shown safely.                                                                                                     | Yes       | The PIN was not accepted. Ask the customer to try again, or use another card.                                                                                         |
| `DECLINED_BY_ISSUER`       | The issuer or the card refused. Not a technical fault.                                                                                                                 | No        | The payment was declined. Ask the customer for another card or payment method.                                                                                        |
| `NETWORK`                  | The device could not reach the payment network.                                                                                                                        | Yes       | No connection to the payment network. Check the internet connection and try again.                                                                                    |
| `CANCELLED`                | Ended deliberately by the operator, the cardholder or your app.                                                                                                        | Yes       | The payment was cancelled. Start a new payment when you are ready.                                                                                                    |
| `UNKNOWN`                  | No more specific code applies. `raw` carries the underlying cause.                                                                                                     | Yes       | The payment could not be completed. Try again. If it keeps happening, contact support.                                                                                |
| `LOCATION_UNAVAILABLE`     | Location permission is missing or location services are off. The failed screen offers a button to grant it.                                                            | Yes       | Location is required to accept payments. Turn on location and allow it for this app, then try again.                                                                  |
| `SECURE_ENTRY_BLOCKED`     | The camera or microphone is in use, the screen is being recorded, or another app is drawing over the PIN pad. The device is fine; the moment is not.                   | Yes       | The PIN cannot be entered safely right now. Close anything recording the screen or using the camera or microphone, and any app drawing over this one, then try again. |
| `ACCESS_TOKEN_REJECTED`    | The token from your backend was missing, expired, minted for another purpose or lacked the Tap on Phone scope. The SDK already asked for a fresh one and retried once. | Yes       | The app could not sign in to accept payments. Try again. If it keeps happening, sign out and back in, or contact support.                                             |
| `TAP_ON_PHONE_NOT_ENABLED` | The merchant is not enabled for contactless capture. Contact Rinne to enable it.                                                                                       | No        | This account is not enabled to accept card payments on a phone.                                                                                                       |
| `EMV_CONFIG_UNAVAILABLE`   | No card configuration is available yet for this SDK version. Contact Rinne if it persists.                                                                             | Yes       | This version of the app is not ready to accept payments yet. Try again later, or check for an app update.                                                             |

<Note>
  `Declined` and `Failed` are different screens on purpose. Showing a network failure as "payment declined" tells the operator to ask for another card when the real problem is connectivity, and offers a retry guaranteed to fail the same way.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Simulator" icon="play" href="/tap-on-phone/android/simulator">
    Pick any error code and see the screen, the operator text and whether Try again appears.
  </Card>

  <Card title="Device setup" icon="mobile-screen" href="/tap-on-phone/android/device-setup">
    Catch NFC, location and connectivity problems before a customer is waiting.
  </Card>
</CardGroup>
