Payment lifecycle
A Payment is the one object you create, track, and reconcile — whatever the method your customer picks, and whichever provider ends up processing it. This page covers the object, the API state machine, and the full acquiring chain underneath it — from created to reconciled.
The Payment object
Create a payment with POST /v1/payments, retrieve it with GET /v1/payments/{id}. The same shape comes back in webhooks, list responses, and settlement lines. Here is the recurring example order — ord_9f21_0716, €49.00 — after it succeeded:
{
"id": "pay_8Q2mX4nT1cVb",
"object": "payment",
"amount": 4900,
"amount_captured": 4900,
"currency": "EUR",
"status": "succeeded",
"payment_method": "ideal",
"reference": "ord_9f21_0716",
"customer": "cus_4T8nQb2Wp1Zr",
"provider": "prov_eu_acq_01",
"provider_attempts": [
{ "provider": "prov_eu_acq_01", "outcome": "approved", "latency_ms": 412 }
],
"risk": null,
"capture_method": "automatic",
"amount_refunded": 0,
"refunded": false,
"partially_refunded": false,
"next_action": null,
"created_at": "2026-07-16T09:24:31Z",
"livemode": false
}
| Field | Type | Notes |
|---|---|---|
id | string | Unique id, prefix pay_. Use it for retrieval, refunds, capture, cancel. |
amount | integer | Minor units. 4900 = €49.00. See Amounts & minor units. |
amount_captured | integer | Minor units captured so far. Equals amount after automatic capture; can trail it during partial capture. |
currency | string | ISO 4217 uppercase, e.g. EUR. |
status | string | One of the lifecycle statuses below. |
payment_method | string | Method code — card, ideal, pix, … See Payment methods. |
reference | string | Your order id. Echoed on every webhook and settlement line. |
customer | string | The cus_ id of the attached customer, or null. The create request accepts an inline { id | email, name } object; responses always return the id string. |
provider | string | Opaque id of the provider that processed (or is processing) the payment. |
provider_attempts | array | Each routing attempt: provider, outcome, latency_ms. See Routing. |
risk | object | Risk result for card-family payments: score 0–99, decision (approved / review / blocked), AVS/CVV checks. null for methods that aren't risk-scored, like this iDEAL payment. See Risk engine. |
capture_method | string | automatic (default) or manual. See Capture. |
amount_refunded | integer | Minor units refunded so far, across all refunds. |
refunded | boolean | true once amount_refunded == amount. |
partially_refunded | boolean | true while 0 < amount_refunded < amount. |
next_action | object | What the customer must do next — currently always a redirect — or null. |
created_at | string | ISO 8601, UTC. |
livemode | boolean | false for payments created with sk_test_ keys. |
Lifecycle & statuses
Every payment moves through one state machine, regardless of method or provider. Forward-only: a payment never returns to an earlier status.
Refunds never change status. After success, refund state lives in the flags amount_refunded, partially_refunded and refunded — see Refund state.
| Status | Meaning | Terminal |
|---|---|---|
requires_confirmation | Created with confirm: false, awaiting an explicit POST /v1/payments/{id}/confirm. Only seen in two-step integrations. | No |
requires_action | The customer must act: bank redirect, 3-D Secure challenge, QR scan, in-app approval. Follow next_action. | No |
processing | Finscale is routing the transaction across your provider set. Usually sub-second for cards; minutes-to-days for direct debit. | No |
requires_capture | Authorized — funds held at the issuer, waiting for your capture. Only with capture_method: "manual". | No |
succeeded | Funds captured. The payment will appear on a settlement report. | Yes |
failed | No provider approved the payment. Inspect provider_attempts for the per-attempt outcomes. | Yes |
canceled | You (or expiry) canceled it before completion. Any authorization hold is released. | Yes |
Payments skip states they don't need. The canonical request below confirms at create time (the default confirm: true), so the payment is born at requires_action — no requires_confirmation stop:
curl https://api.finscale.dev/v1/payments \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-H "Idempotency-Key: idem_ord_9f21_0716" \
-H "Content-Type: application/json" \
-d '{
"amount": 4900,
"currency": "EUR",
"payment_method": "ideal",
"reference": "ord_9f21_0716",
"customer": { "email": "anna@example.com" },
"return_url": "https://shop.example.com/checkout/return"
}'
{
"id": "pay_8Q2mX4nT1cVb",
"object": "payment",
"amount": 4900,
"currency": "EUR",
"status": "requires_action",
"payment_method": "ideal",
"reference": "ord_9f21_0716",
"provider": "prov_eu_acq_01",
"next_action": {
"type": "redirect",
"url": "https://pay.finscale.dev/r/8Q2mX4nT"
},
"created_at": "2026-07-16T09:24:31Z",
"livemode": false
}
next_action.url, then rely on the payment.succeeded / payment.failed webhooks. The return URL tells you the customer came back — the webhook tells you what happened.
The full acquiring chain
The status enum is the API's view. Underneath it runs the acquiring chain every card payment travels — nine stages from your create call to money reconciled in your bank. The first five live on the Payment; the last four live on settlement objects:
Stage by stage, mapped to what you can observe in the API:
| Stage | What happens | Where you see it |
|---|---|---|
| created | You call POST /v1/payments; Finscale validates, screens risk, and assigns pay_…. | The 201 response. With confirm: false, status requires_confirmation. |
| 3-D Secure | The issuer challenges the customer to authenticate. Skipped when no challenge is required. | status: "requires_action" + next_action.url; event payment.requires_action. See Cards & 3DS. |
| authenticated | The challenge passed; the transaction proceeds to authorization via routing. | status: "processing"; event payment.processing. |
| authorized | The issuer approves and holds the funds on the customer's account. | status: "requires_capture" with capture_method: "manual". With automatic, the payment passes through without stopping. See Authorization flows. |
| captured | You (or automatic capture) draw down the authorization — the money is now owed to you. | status: "succeeded", amount_captured set; event payment.succeeded. |
| cleared | The provider submits its capture batch to the card networks; funds move between banks. | No API state — this happens provider-side between capture and settlement. |
| settled | Finscale normalizes every provider's settlement files into one report per provider, day, and currency. | A stl_… settlement report flips to ready — totals obey gross + refunds + fees = net; event settlement.report.ready. |
| funded | The report's net is paid out to your bank account. | A po_… payout referencing the report, with an arrival_date. |
| reconciled | You match report rows to your orders, and the payout to the bank credit. | Your reference on every CSV row; the id chain pay_ ↔ stl_ ↔ po_. Workflow in Reconciliation. |
Non-card rails compress the chain: bank redirects and real-time payments are single-message (authorization and capture are one step, and the bank's own login replaces 3-D Secure), but every method still ends in the same clear → settle → fund → reconcile tail.
Amounts & minor units
amount is always an integer in the currency's minor unit, per ISO 4217. No floats, ever — 49.00 is rejected with an invalid_request_error.
| You want to charge | amount | currency | Exponent |
|---|---|---|---|
| €49.00 | 4900 | EUR | 2 |
| ¥4,900 | 4900 | JPY | 0 — zero-decimal |
| KD 4.900 | 4900 | KWD | 3 — three-decimal |
| R$ 265.00 | 26500 | BRL | 2 |
Rule of thumb: amount = display value × 10^exponent. All derived fields — amount_refunded, refund amount, settlement gross/fee/net — use the same unit, so arithmetic across objects is always integer-safe.
Capture now vs. manual capture
By default (capture_method: "automatic") authorization and capture are one step: processing resolves straight to succeeded. For card payments you can split them — authorize now, capture when you ship:
curl https://api.finscale.dev/v1/payments \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-H "Idempotency-Key: idem_ord_9f21_0716" \
-H "Content-Type: application/json" \
-d '{
"amount": 4900,
"currency": "EUR",
"payment_method": "card",
"capture_method": "manual",
"reference": "ord_9f21_0716"
}'
# → status: "requires_capture" once the authorization is approved
Capture the full amount, or less (for example after a partial shipment). Whatever you don't capture is released back to the customer:
curl https://api.finscale.dev/v1/payments/pay_8Q2mX4nT1cVb/capture \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-H "Content-Type: application/json" \
-d '{ "amount": 4900 }'
# → status: "succeeded". Omit "amount" to capture in full.
- Authorizations expire — capture within 7 days, or the hold is released and the payment moves to
canceled. - Void an authorization you no longer need with
POST /v1/payments/{id}/cancel. The hold is released immediately, no fee. - Manual capture is a card feature. Bank redirects, RTP and wallets are single-message rails — they capture on approval.
- The full pre-auth toolkit — raising a hold with
increment_authorization, partial and multiple capture, and void vs. refund semantics — is in Authorization flows.
Multi-currency
Charge in the customer's currency by setting currency — the same integration handles EUR, PLN, BRL, SEK and every other currency your account is enabled for. Three things follow from the currency:
- Method availability. Methods are currency-bound: PIX is
BRL-only, Blik isPLN-only, Swish isSEK-only. The method catalog lists each method's currencies. - Routing. Currency is a hard filter in the routing decision — only providers that process the currency are candidates.
- Settlement. You receive one payout per currency. Optional conversion to a single payout currency is applied at settlement, and every conversion is itemized on the report.
Presentment vs. settlement currency — and exactly when FX applies — is covered in Multicurrency.
Refund state
Refunds are their own objects (rf_…) and never rewind the payment's status. A fully refunded payment still reads status: "succeeded" — with refunded: true. Track refund state through the three flags:
| State | amount_refunded | partially_refunded | refunded |
|---|---|---|---|
| No refunds | 0 | false | false |
| Partially refunded | 1500 | true | false |
| Fully refunded | 4900 | false | true |
How refunds are created, split, and settled — including the local-method caveats — is covered in Refunds.