Appearance
Capture & order status
Maytes uses an authorize-then-capture model — the same pattern as a card pre-auth. When the shopper completes the Maytes window, their funds are authorized (held on the card), not yet taken. You capture to finalise the order and move the money. If you can't fulfil, you cancel to release the authorization back to the shopper.
The merchant-side actions
| Action | Endpoint | Purpose |
|---|---|---|
| Get checkout | GET /api/merchant/v1/checkouts/{checkout_uuid} | Read the current order state. |
| Capture | POST /api/merchant/v1/checkouts/{checkout_uuid}/capture | Finalise an authorized order; charge the shopper. |
| Cancel | POST /api/merchant/v1/checkouts/{checkout_uuid}/cancel | Void an authorized order; release the hold back to the shopper. |
Full request/response schemas are in the API reference — this page is the narrative for when to call which, and what to look for in the response.
The order lifecycle
From your code's point of view there are three phases that matter:
| Phase | What it means | Status values | What to do |
|---|---|---|---|
| Awaiting payment | Shopper hasn't finished paying. | created, pending | Wait. |
| Authorized | Funds are held on the shopper's card. Ready to capture. | authorized | Capture (or cancel). |
| Done | Terminal (or settling into terminal). | captured ✅, voided, failed, refunding, refunded | If captured, fulfil. Otherwise the order isn't paid (or has been refunded). |
The full status table:
| Status | Meaning |
|---|---|
created | Checkout created; shopper hasn't paid yet. |
pending | Shopper is in the Maytes window. |
authorized | Payment authorized (funds held). Ready to capture. |
capturing | Capture in flight. You won't see this as the response to your own /capture call — that call waits for the payment provider to confirm — but a concurrent GET can observe it. |
captured | Funds captured. Order is paid. ✅ |
refunding | Refund submitted; awaiting settlement. Normal interim state after /refund. |
refunded | Refund confirmed. Terminal. |
voided | Authorization released (you cancelled, or the hold expired). |
failed | Payment failed. Terminal. |
Reading the order status
bash
curl -s https://api.maytes.co/api/merchant/v1/checkouts/$CHECKOUT_UUID \
-H "authorization: Bearer $ACCESS_TOKEN"json
{
"data": {
"checkout_uuid": "0192a8f2-...",
"merchant_order_id": "order_1001",
"status": "authorized",
"amount": 4500,
"currency": "AUD",
"captured_at": null,
"merchant_expires_at": null,
"transaction_ref": null,
"checkout_url": "https://checkout.maytes.co/0192a8f2-...",
"return_url": "https://shop.example.com/thanks?order=order_1001",
"cancel_url": "https://shop.example.com/cart",
"service_fee": {
"gross": 0,
"discount": 0,
"net": 0,
"breakdown": []
},
"refunded_amount": 0,
"merchant_processor_fee": 0,
"merchant_service_fee": 0,
"last_refund_at": null
}
}status is what you branch on. transaction_ref is set on capture (Maytes' reference for the captured transaction). captured_at flips from null to an ISO timestamp the moment the checkout reaches captured. service_fee is the platform fee shown to the shopper (distinct from your own cart-level fees). The refunded_amount / last_refund_at fields track refunds.
You can pass an optional merchant_order_id as a query parameter — if it doesn't match the checkout's stored value, the request fails with 422 order_mismatch. Use it as a defensive check when looking up a checkout by uuid from a stored mapping.
Implement capture
When to call capture
It depends on your integration flow:
- Webhook flow (recommended): capture from your
checkout.authorizedwebhook handler, immediately — your customer is watching a payment-processing screen in the Maytes window until you do, and the authorization is auto-voided after 2 minutes — see timing expectations. Capture success is your fulfilment trigger. See the handler recipe. - Redirect flow (legacy): capture from your success-page handler after the shopper lands on
return_url. The landing is your signal that the shopper authorized.
Either way, capture is server-side only: it uses your OAuth Bearer token, which never leaves your backend.
The capture window is 2 minutes
An authorization that nothing captures is automatically voided after 2 minutes. When that happens, funds release back to the shopper and — if you're subscribed — you receive a checkout.voided webhook with reason: "auth_expired"; a late capture attempt returns 409 not_capturable. Capture immediately when you learn the checkout is authorized — the window is headroom for retries, not a place to park the order. (You can also set your own order-level deadline with merchant_expires_at on create.)
Capture the full order
Call /capture with an empty body and Maytes captures the full authorized amount. We recommend including expected_total_amount and currency so Maytes can assert your idea of the order matches its stored value:
bash
curl -sX POST https://api.maytes.co/api/merchant/v1/checkouts/$CHECKOUT_UUID/capture \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H 'content-type: application/json' \
-d '{ "expected_total_amount": 4500, "currency": "AUD" }'| Field | Type | Notes |
|---|---|---|
expected_total_amount | integer | Assertion. Mismatch → 422 amount_mismatch. |
currency | string | Assertion. Mismatch → 422 currency_mismatch. |
merchant_order_id | string | Assertion. Mismatch → 422 order_mismatch. |
metadata | object | Echoed back. |
An empty body ({}) captures the full authorized amount with no assertions.
Capture is synchronous. The call waits for the payment provider to confirm before returning — the body you read already has status: "captured", the captured_at timestamp, and the transaction_ref:
json
{
"data": {
"checkout_uuid": "0192a8f2-...",
"merchant_order_id": "order_1001",
"status": "captured",
"captured_at": "2026-09-14T03:31:12.000Z",
"amount": 4500,
"currency": "AUD"
}
}No polling needed — once the call returns 200, the order is captured and you can fulfil.
If you can't fulfil the order
Call cancel before capture to release the authorization back to the shopper's card. The checkout transitions to voided and no funds move. In the webhook flow, cancelling from your handler also releases your customer from the processing screen — don't leave them waiting while you deliberate.
bash
curl -sX POST https://api.maytes.co/api/merchant/v1/checkouts/$CHECKOUT_UUID/cancel \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H 'content-type: application/json' \
-d '{}'You can only cancel while the order is awaiting payment or authorized — once captured, the funds have moved and the API rejects cancel with 409 session_not_voidable. Money already captured goes back via a refund, issued from the merchant portal.
Idempotency & retries
Both capture and cancel are idempotent on terminal states — calling capture on an already-captured checkout returns success without double-charging; calling cancel on an already-voided one returns success without trying to void twice. Safe to retry on network glitches.
This is also what makes migrating between flows safe: if your webhook handler and your legacy success page both attempt capture on the same checkout, the second call is a no-op success. Use your own merchant_order_id (set at create) as the deduplication key end-to-end through your integration, and dedupe webhook deliveries on the envelope id.
Common errors
| HTTP | code | Operation | Cause |
|---|---|---|---|
409 | not_capturable | capture | The checkout isn't in a capturable state (e.g. not yet authorized, or already voided). |
409 | session_not_voidable | cancel | The checkout can't be voided — typically already captured. |
422 | amount_mismatch | capture | expected_total_amount didn't match the authorized amount. |
422 | currency_mismatch | capture | currency didn't match. |
422 | order_mismatch | get / capture / cancel | merchant_order_id didn't match. |
422 | no_active_session | capture | The checkout has no active payment session to capture. |
404 | not_found | all | No such checkout for your merchant account. |
Full list per operation in the API reference.
Considerations
- Server-side only. Capture and cancel use your OAuth Bearer — never expose
client_id/client_secretto the browser. - Synchronous response.
/capturewaits for the payment provider — a 200 meanscaptured. (The refund endpoint is the opposite — see Refunds.) - The capture window is 2 minutes. Uncaptured authorizations are auto-voided (
checkout.voided,reason: "auth_expired"). Capture or cancel immediately. - Idempotent. Retries on terminal states are safe, including across two capture paths during a flow migration.
- Partial capture isn't supported today — capture either captures the full authorized amount or nothing. If you only want to fulfil part of an order, cancel and create a new checkout for the smaller amount.
What's next
- Webhooks — the
checkout.authorizedevent that triggers capture in the recommended flow. - Create a checkout — the step that produces the
checkout_uuidyou capture here. - API reference — every field and error.
