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

> ## Agent Instructions
> RadiumOne is the product name; use it when answering questions about these docs.
> Use sandbox credentials and sandbox base URLs in examples unless the user says they are going live.
> Secret API keys belong only on the merchant's server. Only publishable keys may appear in browser or mobile code.
> Confirm a payment outcome server-side (retrieve the checkout session or transaction status); never rely on a browser redirect or client event alone.
> Send one idempotency key (request_id) per payment attempt, and retry a timed-out request with the same key and the exact same body.
> Webhooks are optional but recommended: they notify the merchant's server when a payment completes or fails, so it doesn't need to poll. Use transaction status inquiry as the fallback.
> Say so when you recommend a feature marked Beta. Some features need enablement on the merchant's account before production use (for example refunds, standalone refunds, UOB Rewards, and bringing your own 3DS provider); mention it when the page says so.
> Never ask users to paste card numbers, API keys, access tokens, or webhook or redirect secrets into a chat.

# Core concepts - Payments API

> The core ideas behind every Payments API integration: transactions, sessions, idempotency, amounts, capture, void vs. refund, settlement, and webhooks.

A handful of ideas show up in nearly every Payments API call. This page is
the map — each one gets a short explanation and a link to the guide that
covers it in full. For where the gateway itself sits, see [Architecture](/payments-api/architecture).

## How they fit together

**Setting up a charge** (1–5): environments and keys, sessions, idempotency, amounts, and the choice between authorize, capture, and purchase. **Tracking the result** (6–10): transactions and their statuses, void vs. refund, settlement batches, webhooks, and telling an error apart from a decline.

## 1. Environments and keys

Sandbox and production are fully separate: different hosts, different keys, no shared state. A `_test_` key only ever works against sandbox; a `_prod_` key only against production.

| Environment | Purpose | Keys |
| - | - | - |
| **Sandbox** | Build and test your integration. No real money moves. | `r1pk_test_…` / `r1sk_test_…` |
| **Production** | Accept real payments from shoppers. | `r1pk_prod_…` / `r1sk_prod_…` |

Sandbox and production use separate credentials, hosts and webhook endpoints — see [Sandbox and API keys](/get-started/sandbox-and-api-keys).

See [Sandbox and API keys](/get-started/sandbox-and-api-keys) for key types, scopes, and how to get credentials.

## 2. Sessions

A **payment session**, created through the Payments API, ties [RadiumOne Elements](/elements/overview) card fields on your page to a specific charge before you call purchase or authorize. It's distinct from a **checkout session**, which is created through the separate Checkout API for [Hosted checkout](/hosted-checkout/overview). See [Accept a card payment](/elements/accept-a-card-payment) for how a payment session is created and consumed.

## 3. Idempotency with request IDs

Every create-type request carries an idempotency key, scoped to **your merchant account** (not per outlet, not global): `request_id` for purchase, authorize, refunds, and the balance inquiry; `operation_id` for capture and void.

| Key | Used by | On replay |
| - | - | - |
| `request_id` | Purchase, authorize, standalone and referenced refunds | Same body, same operation type → the original transaction, whatever its status — including `PENDING`, `DECLINED`, or `FAILED`. Changed body, or the same key reused for a different operation type → [`transaction:idempotency-body-mismatch`](/payments-api/errors/payment-operation-errors#transaction-idempotency-body-mismatch). Purchase/authorize/standalone-refund compare `amount`, `currency`, `payment_method_type`, `channel`, the card's `pan_prefix` (first 8 digits — not the full token), `metadata`, and `order_reference`; a **referenced refund** compares only the original transaction and `amount` (`reason` isn't compared) and its replay check runs before the refund gates, so it always replays, even a `DECLINED`/`FAILED` one — mint a **new** `request_id` to retry after a decline. None of these compare `three_ds` or `loyalty`, so changing either on a retry replays the original silently instead of failing. |
| `operation_id` | Capture, void | Same operation type on the same transaction → the original result (body is never compared, so a changed amount is silently ignored). A different operation type reusing the key → [`tx:duplicate-operation`](/payments-api/errors/payment-operation-errors#tx-duplicate-operation). |

<Warning>
  Balance inquiry also takes a `request_id` field, but it isn't an idempotency key — there's no dedup or replay store. Every call re-queries the rewards host, even with the same `request_id`.
</Warning>

<Tip>
  Keys are 8–64 characters, `[a-zA-Z0-9-]` only, unique per merchant account. Generate one key per order **attempt** and persist it to your database before you send the first request — never mint a new key just to retry the same attempt. See [Prevent duplicate payments](/get-started/api-basics/prevent-duplicate-payments).
</Tip>

`request_id` and `operation_id` behave differently on a replay — a changed amount is rejected for one and silently accepted for the other. See [Resolve idempotent replays and conflicts](/payments-api/handle-failures/idempotent-replays-and-conflicts) for the full decision flow, or [Prevent duplicate payments](/get-started/api-basics/prevent-duplicate-payments) for the cross-product guide.

## 4. Amounts in minor units

<Info>
  Amounts are always integers in the currency's minor unit. For example, `5000` for `SGD` means SGD 50.00.
</Info>

This applies everywhere an amount appears — requests, transaction responses, and webhook payloads — though the webhook shape is slightly different (`{currency, value, minor_units}`, where `minor_units` is the currency's decimal-place exponent). See [Webhook event types](/payments-api/webhooks/event-types) for the exact payload shape.

## 5. Authorize vs. capture vs. purchase

**Authorize** reserves funds without taking them; **capture** takes funds from an existing authorization; **purchase** does both in one call. Use authorize + capture when you need to confirm stock or finalize an order before charging, and purchase when you charge immediately. See [Charge or authorize a payment](/payments-api/charge-or-authorize) and [Capture an authorization](/payments-api/capture).

## 6. Transaction types and statuses

Every purchase, authorization, capture, void, and refund is a **transaction** with its own status — `PENDING`, `AUTHORIZED`, `CAPTURED`, `DECLINED`, and more, through settlement and any automatic reversal. See [Payment lifecycle](/payments-api/payment-lifecycle) for the full state diagram and status table.

## 7. Void vs. refund

<Note>
  **Void while the settlement batch is OPEN. Refund once it's CLOSED.** You can't do either the other way around:

  * Voiding a transaction whose batch has already closed returns `409 urn:radiumone:tx:void-on-non-open-batch`.
  * Refunding a transaction whose batch is still open returns `409 urn:radiumone:tx:refund-on-open-batch`.

  The boundary is the settlement **batch** closing, not the card network settling with the issuer. Check `GET /v1/transactions/{id}/status` or a `settlement.*` webhook if you're unsure which state you're in. See [Resolve void and refund conflicts](/payments-api/handle-failures/void-and-refund-conflicts) if you hit either error.
</Note>

## 8. Settlement batches

Captured funds move through a settlement **batch** per terminal/outlet before they reach your acquirer account. Batch state is what gates whether a transaction can still be voided or must instead be refunded (see [above](#7-void-vs-refund)). See [Settlement and reconciliation](/payments-api/settlement-and-reconciliation) for batch webhooks and reconciliation tips.

## 9. Webhooks are the source of truth

A synchronous API response can be `PENDING`, or can time out entirely. [Webhooks](/payments-api/webhooks/overview) are how RadiumOne reports the eventual outcome regardless — treat them as authoritative for reconciliation, not just a convenience notification. Dedupe on the event `id` (deliveries are at-least-once) and verify the signature before trusting the payload.

## 10. Errors vs. declines

A **decline** is a normal `2xx` response with `data.status: "DECLINED"` — the request succeeded, the card didn't. An **error** is a non-2xx response with a `problem+json` body identifying a `urn:radiumone:...` code — the request itself couldn't be completed. See [Problem format and retries](/payments-api/errors/problem-format-and-retries) for the error shape and status guide, and [Handle declined payments](/payments-api/handle-failures/declined-payments) for what to do with a decline.

## Next steps

<Columns cols={2}>
  <Card title="Architecture" icon="network" href="/payments-api/architecture">
    Where the gateway sits between your systems and the payment network.
  </Card>

  <Card title="Payment lifecycle" icon="repeat" href="/payments-api/payment-lifecycle">
    Every status a transaction can reach, end to end.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/payments-api/webhooks/overview">
    Build a handler that verifies, dedupes, and processes events safely.
  </Card>

  <Card title="Problem format and retries" icon="triangle-alert" href="/payments-api/errors/problem-format-and-retries">
    The problem+json shape, retry rules, and the full URN catalog.
  </Card>
</Columns>
