Confirm payment
API contract v1.0.0
confirmPayment/v1/payments/{payment_id}/confirmSubmits the first payment, or starts a new attempt after the previous attempt clearly failed. A new attempt must start before expires_at, stay within the maximum number of attempts (3 by default) and pass risk checks. When the payer switches to another card, they provide the card details again and you use a new idempotency key. If an attempt is already in flight (processing, requires_action), the API returns the original status or 409 payment_in_progress and doesn't trigger a new charge. When authentication is required, the API returns requires_action and next_action, and you redirect the payer's browser to the authentication page. Raw card data is used only transiently in the platform's isolated card processing environment and is never stored or logged.
Request headers
| Request headers | Required | Format | Description |
|---|---|---|---|
| Authorization | Yes | Bearer <API_KEY> | Server-side key, to be used only on the merchant's server. The merchant and environment are derived from the key. A missing, invalid, revoked or expired key returns 401. |
| Content-Type | Yes | application/json | The request body is UTF-8 encoded JSON. |
| Idempotency-Key | Yes | 1–255 visible ASCII characters | Idempotency key, 1–255 visible ASCII characters. The scope is merchant, environment, method and path: with the same key and matching core business parameters, the original business object is reused, and conflicting parameters return 409 idempotency_conflict. Use one key per submission, and reuse the same key when you retry after a network error; use a new key for a new submission, such as a card change. For concurrent requests with the same key, only one request gets to run, and the others wait for its transaction to commit and then return the same object (200). If the wait times out, the API returns 409 operation_in_progress (retryable=true); retry later with the original key. A request rejected with 400, 401, 403 or 422 doesn't consume the idempotency key; fix it and submit with a new key. |
Request body
JSON object ConfirmPaymentRequest
| Name | Type | Required | Description |
|---|---|---|---|
payment_method_data | PaymentMethodData | Condition: Send either this or payment_method_id; you can omit both if payment_method_id was provided at creation | Payment method data for direct card. It's accepted only when confirm=true and the merchant has the direct card capability enabled. Raw card fields are used only transiently in the platform's isolated card processing environment and are never written to a database, cache, queue, log or error report. Send either this or payment_method_id. |
type | string | Required | Payment method type. The initial release supports only card.
Example: |
card | CardDetails | Required | Raw card details, used only transiently in the controlled card processing pipeline. |
payment_method_id | string | Condition: Send either this or payment_method_data; you can omit both if a payment method was provided at creation | A scoped payment credential supported by the adapter. Constraints: Example: |
return_url | string(uri) | Condition: Required if not provided at creation | The merchant's return URL after authentication completes (required if it wasn't provided at creation). It must match a redirect URL rule registered for the current environment: the match covers the scheme, host (exact or one-level wildcard), port and path prefix (by path segment), and the query string and fragment aren't part of the match. When the platform redirects the payer back, it uses this URL exactly as provided and appends no parameters. Constraints: Example: |
billing_details | BillingDetails | Condition: Required if the current provider's capabilities demand it; if it's missing, the API returns 400 before anything is submitted to the provider, and error.field names the field | The minimum necessary cardholder information. Whether it's required depends on the capabilities of the current provider adapter. |
name | string | No | Cardholder name. Constraints: Example: |
email | string(email) | No | Cardholder email address. Constraints: Example: |
address | Address | No | Billing address. |
browser_info | BrowserInfo | Condition: Required if the current provider's capabilities demand it; if it's missing, the API returns 400 before anything is submitted to the provider, and error.field names the field | The payer's real browser context, used for 3DS. It must come from the payer's browser and can't be replaced with merchant server information. Whether it's required depends on the capabilities of the current provider adapter; if required information is missing, a clear error is returned before anything is submitted to the provider. |
user_agent | string | No | The payer's browser User-Agent. Constraints: Example: |
accept_header | string | No | The Accept request header of the payer's browser. Constraints: Example: |
language | string | No | Browser language (BCP 47). Constraints: Example: |
screen_width | integer(int32) | No | Constraints: Example: |
screen_height | integer(int32) | No | Constraints: Example: |
color_depth | integer(int32) | No | Constraints: Example: |
timezone_offset_minutes | integer(int32) | No | Time zone offset in minutes, following the sign convention of JavaScript's getTimezoneOffset(). For example, UTC+8 is -480. Constraints: Example: |
javascript_enabled | boolean | No | Example: |
ip_address | string | No | The payer's IPv4 or IPv6 address. Resolve it through a trusted proxy chain, and don't use the merchant server's IP address in its place. Constraints: Example: |
Request example
curl -X POST 'https://api.fluxan.co/v1/payments/<PAYMENT_ID>/confirm' \
-H 'Authorization: Bearer <TEST_SERVER_API_KEY>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: <UNIQUE_OPERATION_KEY>' \
-d '{
"payment_method_data": {
"type": "card",
"card": {
"number": "<UPSTREAM_TEST_CARD_NUMBER>",
"exp_month": 12,
"exp_year": 2030,
"cvc": "<UPSTREAM_TEST_CVC>"
}
},
"browser_info": {
"user_agent": "Mozilla/5.0",
"accept_header": "text/html,application/xhtml+xml",
"language": "zh-CN",
"screen_width": 1366,
"screen_height": 768,
"color_depth": 24,
"timezone_offset_minutes": -480,
"javascript_enabled": true,
"ip_address": "203.0.113.10"
}
}'Placeholders to replace
<PAYMENT_ID>: The id returned when you create a payment (starts with pay_)<TEST_SERVER_API_KEY>: Test-environment server API key, created on the "API Key" page of the merchant portal<UNIQUE_OPERATION_KEY>: The idempotency key you generate for this request (for example, a UUID); reuse the same key when you retry after a network error<UPSTREAM_TEST_CARD_NUMBER>: Test card number, shown under "API docs and testing guide" in the merchant portal after you sign in<UPSTREAM_TEST_CVC>: Test card CVC, shown in the merchant portal together with the test card number
Note
Server API keys must only be used on your own server, so the docs site doesn't offer interactive API testing. Get a test key: API Key page in the merchant portal (opens the merchant portal in a new tab) (Test environment; Requires Owner, Admin or Developer)
Responses
The current result is known (succeeded, failed or requires_action), or this is an idempotent replay with the same key.
{
"id": "pay_demo_004",
"merchant_order_no": "ORDER-DEMO-004",
"environment": "test",
"amount": 10000,
"currency": "USD",
"status": "succeeded",
"resolution_status": "known",
"captured_amount": 10000,
"refunded_amount": 0,
"refund_returned_amount": 0,
"refund_reserved_amount": 0,
"refund_status": "none",
"authentication_status": "not_required",
"reconciliation_status": "pending",
"upstream_receipt_status": "not_received",
"latest_attempt_id": "pat_demo_005",
"attempt_count": 2,
"version": 6,
"next_action": null,
"failure": null,
"description": "Example order",
"customer_reference": "CUSTOMER-DEMO-004",
"metadata": {
"business_order_type": "one_time_purchase"
},
"card": {
"brand": "visa",
"last4": "1234"
},
"created_at": "2026-09-23T09:00:00Z",
"succeeded_at": "2026-09-23T09:12:05Z",
"expires_at": "2026-09-23T09:30:00Z",
"request_id": "req_demo_011"
}Response fields
Returns an object. See the object section for field descriptions.
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | Platform payment ID. Example: |
merchant_order_no | string | Required | Merchant order ID, case-sensitive as sent. Example: |
environment | string | Required | Environment: test is the test environment and live is the live environment. Test and live objects are isolated from each other.
Example: |
amount | integer(int64) | Required | Order amount, in the smallest currency unit. It can't be changed after the first submission to the provider. Constraints: Example: |
currency | string | Required | Uppercase three-letter currency code. Constraints: Example: |
status | string | Required | Payment status. Clients must not treat an unknown status as success.
Example: |
resolution_status | string | Required | Outcome confirmation.
Example: |
captured_amount | integer(int64) | Required | The amount confirmed as captured; equal to amount on success. Constraints: Example: |
refunded_amount | integer(int64) | Required | The confirmed effective refunded principal: the total of successful refunds minus confirmed refund returns. It excludes refunds that are still processing and platform exception refunds. Constraints: Example: |
refund_returned_amount | integer(int64) | Required | The cumulative refund principal confirmed as returned. Constraints: Example: |
refund_reserved_amount | integer(int64) | Required | Principal reserved for refunds that are under review or processing. Constraints: Example: |
refund_status | string | Required | Refund summary status, derived from refunded_amount (the effective refunded principal): none, partial, full.
Example: |
authentication_status | stringNullable | Required | The 3DS authentication status of the current attempt, taken from the authentication session status: not_required, pending, challenge, authenticated, failed, abandoned, expired. Tolerate new values. Successful authentication (authenticated) doesn't mean the charge succeeded, so don't use it to decide the payment outcome. It's null when there is no attempt yet or the attempt hasn't entered authentication. Example: |
reconciliation_status | ReconciliationStatus | Required | The progress of the platform's funds processing, for reconciliation reference only. It doesn't indicate whether the payment succeeded or when you'll be paid out.
|
upstream_receipt_status | UpstreamReceiptStatus | Required | The progress of the platform's funds processing, for reconciliation reference only. It doesn't indicate whether the payment succeeded or when you'll be paid out.
|
latest_attempt_id | stringNullable | Required | ID of the latest payment attempt; null if no attempt has been submitted yet. Example: |
attempt_count | integer(int32) | Required | The number of attempts started. The default maximum is 3. Constraints: Example: |
version | integer(int64) | Required | Object version, which increases with every effective business change. Compare versions to discard older events, and don't order events by status name. Constraints: Example: |
next_action | NextActionNullable | Required | The action the payer must complete when status=requires_action; null in all other cases. |
type | string | Required | The initial release of the public API exposes only redirect_to_url. Clients must tolerate types that are added in the future.
Example: |
authentication_session_id | string | Required | Authentication session ID, linked to a specific payment attempt. Example: |
url | string(uri) | Required | The URL of the platform's authentication page. Example: |
expires_at | string(date-time) | Required | The time the authentication access expires (10 minutes at most). Example: |
failure | PaymentFailureNullable | Required | The reason when the attempt that latest_attempt_id points to has clearly failed; otherwise null. |
code | string | Required | Failure reason code. Example: |
message | string | Required | Sanitized description. Example: |
attempt_id | string | Required | ID of the failed payment attempt. Example: |
description | string | No | Order summary. Constraints: Example: |
customer_reference | string | No | Merchant-side customer reference. Constraints: Example: |
metadata | object | No | Custom key-value pairs set by the merchant: up to 20 keys. A key can be up to 40 characters, and a value is a string of up to 200 characters. Don't put sensitive data such as card numbers in it. The platform checks for such data and rejects the request. Constraints: Example: |
card | CardSummaryNullable | Required | Masked details of the card used in the latest attempt; null when there are no card details yet. |
brand | string | Required | Card brand. Example: |
last4 | string | Required | Last four digits of the card number. Constraints: Example: |
created_at | string(date-time) | Required | Creation time. Example: |
succeeded_at | string(date-time)Nullable | Required | The time the payment was confirmed as successful; null if it hasn't succeeded. Example: |
expires_at | string(date-time) | Required | The deadline for starting a new attempt, fixed at creation (by default 30 minutes after creation; the platform can adjust it to between 5 and 120 minutes, so go by this field). Expiry only closes the entry point for new attempts. It doesn't invalidate a payment that was already submitted. Example: |
request_id | string | Required | Unique identifier of this request, used for troubleshooting and support. Example: |
Possible error codes
| HTTP | Error code | When it occurs | How to handle |
|---|---|---|---|
| 400 | invalid_request | Idempotency-Key is missing; the order ID contains invalid characters; payment_method_data and payment_method_id are submitted together; confirm=false carries card details; confirm=true has no return_url; a checkout session request sends both groups of identity parameters, or neither; a browser_info / billing_details field the provider requires is missing; the cursor, time format or limit is invalid | Fix the request according to error.field and resubmit it; don't charge again automatically |
| 403 | capability_disabled | Direct card isn't enabled; the payment or refund request capability has been turned off; the currency isn't enabled | Use hosted checkout instead, or contact platform Operations |
| 403 | merchant_not_active | The merchant has been suspended or closed; callbacks, queries and notifications for business that was already accepted aren't affected | Contact platform Operations |
| 404 | not_found | An incorrect ID; using a test key to look up a live object; looking up a platform exception refund (not returned by the open API) | Check the ID and the environment of the key you used; the platform doesn't reveal whether another merchant's object exists |
| 409 | idempotency_conflict | The key was reused but the amount, currency, order ID or similar parameters differ | This is an integration bug: check your idempotency key generation logic; don't change the parameters and replay the original key; use a new key for a new operation |
| 409 | operation_in_progress | Requests with the same key were sent concurrently | Retry later with the same key, or retrieve related_resource_id; don't switch to a new key |
| 409 | payment_in_progress | Calling confirm again while an attempt is in flight (processing / requires_action), or resubmitting the same order with a new key | Retrieve the Payment that related_resource_id points to and continue according to its status (guide the payer through authentication or wait for the result); don't create a new order |
| 409 | invalid_state | Canceling a payment that is processing / succeeded; calling confirm on an order that has expired, been canceled or already succeeded; creating a checkout session for an order that can't be paid | Retrieve the object's latest state, then decide what to do |
| 409 | attempt_limit_reached | The default maximum is 3 attempts, and the platform can lower it | This order can't be charged again; once you've confirmed the original payment is closed, use a new order ID |
| 422 | card_brand_not_supported | The card brand isn't in the overlap between the merchant's configuration and the provider's capabilities | Ask the payer to use another card |
| 422 | payment_method_unusable | payment_method_id doesn't belong to the current environment, provider account or this merchant | Get a new payment credential |
| 422 | url_not_allowed | return_url / cancel_url doesn't match a registered redirect URL rule | Register it on the redirect URL allowlist page in the merchant portal, then resubmit |
| 422 | risk_rejected | The request matched a basic rule such as a risk list; the specific rule and threshold aren't disclosed | Don't try to work around it; contact the platform if you have questions |
| 503 | temporarily_unavailable | The platform has paused new transactions on this channel; the response doesn't give the internal reason | Retry later with the original idempotency key; this request created no Payment or charge; if it keeps happening, contact platform Operations |
| 401 | unauthenticatedCommon | No Authorization header was sent; the key was copied incompletely; the old key expired after a rotation; the key was revoked | Check your credentials: on the API Key page in the merchant portal, verify the key's status and environment |
| 403 | forbiddenCommon | Creating a refund with a read-only key; calling getEvent without the read scope for the event's business area; if the request's source IP isn't on the key's allowlist, the more specific ip_not_allowed is returned | Use a key that has the required scope; don't work around the restriction |
| 429 | rate_limitedCommon | A rate limit per merchant and per key; switching keys doesn't get around the merchant's overall limit | Wait for Retry-After seconds, then retry with the original idempotency key |
| 500 | internal_errorCommon | — | Retry with the original idempotency key, or query by merchant order ID / refund ID; never charge again with a new key |
| 502 / 503 | upstream_unavailableCommon | A provider or platform dependency failed, and the platform can't confirm whether this request took effect | Same as above |
Notes
- Use it for the first submission after you create a payment with
confirm=false, or for a new attempt after the previous attempt clearly failed. - A new attempt must happen before
expires_at, within the maximum number of attempts (3 by default, and the platform may lower it), and it must pass risk checks. - Retry with a new card using a new idempotency key. Replaying the same key only returns the result of the original operation and never charges the new card.
- If an attempt is already in flight (
processingorrequires_action), the API returns the current status or 409payment_in_progress. Guide the payer through authentication or wait for the result, and don't create a new order.