Payments and authorisations
Payment reporting fields, manual-capture states, capture and cancellation operations, authentication, errors, and retry rules.
Payments and authorisations
The payments API exposes one-time card authorisations as a provider-neutral lifecycle. If captureMethod is omitted, or set to automatic, immediate capture remains the default. Manual capture is opt-in and supports one later capture for the full amount.
Financial event fields
Payment resources classify purchases, refunds, and chargebacks explicitly. The following fields are additive; existing response fields are unchanged.
| Field | Type | Meaning |
|---|---|---|
type | purchase / refund / chargeback | Financial event classification. Use this instead of inferring a chargeback from status or provider-specific metadata. |
financialEffectiveAt | ISO 8601 timestamp or null | The authoritative provider money-movement timestamp, when available. |
gatewayChargeId | string or null | The provider charge id that links a refund or chargeback movement to the original purchase. |
For example, a chargeback withdrawal can appear as:
{
"id": "3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0",
"amount": 49.99,
"currency": "GBP",
"type": "chargeback",
"financialEffectiveAt": "2026-08-14T12:05:43.000Z",
"gatewayChargeId": "ch_3Qx9Example"
}Chargeback amounts are signed from Flo's reporting perspective: a positive amount is money withdrawn, while a negative amount is money reinstated after a dispute win. To reconcile Stripe, group Stripe movements by the provider dispute identity retained in metadata and sum their signed amounts. Do not count each lifecycle notification as a separate chargeback.
Chargeback rows never participate in refund aggregation. This reporting contract applies prospectively and does not reclassify or replay historical transaction rows.
Checkout opt-in
POST /v1/checkouts/sessions accepts:
{
"clientId": "18bff186-284c-483f-acee-e712f21d2b8d",
"captureMethod": "manual",
"currency": "GBP",
"products": [{ "code": "order_123", "quantity": 1 }],
"accountData": {
"userId": "customer_123",
"email": "customer@example.com"
},
"successUrl": "https://merchant.example/order/accepted",
"cancelUrl": "https://merchant.example/checkout"
}| Field | Type | Required | Meaning |
|---|---|---|---|
captureMethod | automatic | manual | No | automatic is the default. manual requests an authorisation hold. |
Manual capture is eligible only for one-time card carts. Subscriptions, PayPal, other alternative payment methods, partial capture, incremental authorisation, and multiple capture are unsupported.
After cardholder authentication, GET /v1/checkouts/sessions/{sessionId}/status can return:
{
"data": {
"uuid": "fd4475e4-dc19-4439-b830-c9c8f67a35e7",
"status": "authorized",
"paymentId": "3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0",
"authorizationExpiresAt": "2026-08-11T14:30:00.000Z",
"latestTransactionAttempt": {
"result": "AUTHORIZED"
}
}
}Authenticate the status read with the session's x-checkout-session-token. Save paymentId; capture and cancellation accept the FloPay payment UUID, not a provider intent id.
Payment states
Payment.status is the canonical TransactionStateEnum value:
| Status | Meaning | Money collected? |
|---|---|---|
pending | Created but not yet submitted to a terminal provider outcome. | No |
authorized | Funds are held and capturable before authorizationExpiresAt. | No |
processing | Provider processing is in progress. | Not yet confirmed |
succeeded | Captured and paid. Immediate-capture payments also finish here. | Yes |
failed | Declined, authentication-failed, or otherwise terminally failed. | No |
refunded | The captured amount has been fully refunded. | Previously collected |
partially_refunded | Part of the captured amount has been refunded. | Partially retained |
refund_failed | A refund attempt failed; inspect the payment and refund operation before retrying. | Unchanged |
voided | An uncaptured hold was cancelled or expired. Inspect authorizationVoidReason. | No |
unknown | FloPay cannot classify the provider state yet. Re-read before acting. | Do not assume |
There is no captured, declined, cancelled, or expired payment status. A captured payment is succeeded; a declined authorisation is failed; a cancelled authorisation is voided with reason merchant_requested; and an expired authorisation is voided with reason expired. Provider cancellation can use provider_canceled.
Never treat authorized as paid. Fulfil an item order only after capture returns succeeded and your idempotent webhook handler processes item.purchased.
Capture the full authorisation
PUT /v1/payments/{paymentId}/capture
Authorization: Basic {base64(clientUuid:apiToken)}
Idempotency-Key: order_123-full-capture
Content-Type: application/json
{}The optional JSON body is included in the idempotent request identity. A 200 OK response returns the payment resource. On success its status is succeeded.
Cancel an authorisation
PUT /v1/payments/{paymentId}/cancel
Authorization: Basic {base64(clientUuid:apiToken)}
Idempotency-Key: order_123-cancel
Content-Type: application/json
{}A successful cancellation returns 200 OK with status: 'voided' and authorizationVoidReason: 'merchant_requested'. It releases the hold without collecting funds.
Authentication and permissions
Capture and cancellation are trusted-server operations. They accept Client Basic authentication with the client identifier (client.uuid) as the username and an active user-owned API token as the password. OAuth bearer callers must have a current merchant owner or admin membership. A member or revoked membership receives 403 Forbidden.
The payment is scoped to the authenticated merchant before FloPay resolves provider details. A missing payment and another merchant's payment both return the same 404 Not Found response.
Never expose a user-owned API token or OAuth access token to browser code. The FloPay SDK deliberately has no capture or cancellation method.
Idempotency and retries
Idempotency-Key is mandatory and must be nonblank. Use one stable key per logical operation:
- retry the same operation with the same key and the same request body after a timeout, lost response, or
5xx; - never reuse that key for a different payment, operation, or body;
- the same key with a different body returns
409 Conflictand preserves the original operation; - a durable replay returns the original outcome and never submits a second provider capture.
Capture failure returns 502 Bad Gateway, leaves the authorisation inspectable rather than claiming payment, and emits payment.capture_failed. Re-read the payment before deciding whether another operation is appropriate.
Errors
| HTTP status | When it is returned | Action |
|---|---|---|
400 | Invalid payment UUID, or missing/blank Idempotency-Key. | Correct the request; do not retry unchanged. |
403 | OAuth caller is not a current owner or admin. | Use an authorised server identity. |
404 | Payment is missing or outside the merchant scope. | Treat both cases identically; do not probe provider data. |
409 | Payment is not actively authorised, the hold expired, another operation is active, or the key conflicts with another request. | Re-read the payment and reconcile; do not generate a new key for an uncertain request. |
502 | The gateway could not capture or cancel. | Keep the order unfulfilled, re-read state, and use the same key for a safe transport retry. |
Events
| Event | Meaning |
|---|---|
payment.authorized | A capturable hold is active. Not a fulfilment signal. |
payment.capture_failed | A capture attempt failed and no paid outcome was produced. |
payment.authorization_voided | The merchant released an uncaptured hold. |
payment.authorization_expired | The provider expired an uncaptured hold. |
item.purchased | The capture completed through the existing purchase path; canonical fulfilment event for this item-only flow. |
See Pre-authorisation and capture for the end-to-end task and Webhook events for payloads.