Payment Lifecycle
Every payment follows a lifecycle from creation to a final status. Understanding this lifecycle is essential for building a reliable integration — your backend logic, order fulfillment, and customer communication all depend on reacting to the right status at the right time.
Payment statuses
When you create a payment, it starts in processing and moves through statuses until it reaches a terminal state. You receive a webhook at each transition.
| Status | Description | Terminal | Webhook |
|---|---|---|---|
processing | Payment sent to provider for processing. | No | payment.processing |
requires_action | Customer action needed (3D Secure redirect, bank authentication, QR scan). | No | payment.requires_action |
requires_capture | Authorized successfully, waiting for you to capture manually. | No | payment.requires_capture |
captured | Capture completed (manual capture flow only). Funds have been charged. | Yes (except refund transitions to refunded or partially_refunded) | payment.captured |
succeeded | Payment completed successfully (auto-capture flow). Funds have been charged. | Yes (except refund transitions to refunded or partially_refunded) | payment.succeeded |
failed | Payment declined by provider or errored during processing. | Yes | payment.failed |
canceled | Payment voided before capture or canceled by the merchant/system. Authorization expiry leads to cancellation. | Yes | payment.canceled |
partially_refunded | Part of the captured amount has been refunded to the customer. | Yes (except transition to refunded when remaining amount is refunded) | payment.partially_refunded |
refunded | Full amount has been refunded to the customer. | Yes | payment.refunded |
Terminal vs non-terminal: Once a payment reaches a terminal status, it will not change — except that succeeded and captured payments can later become refunded or partially_refunded through a refund operation.
Payment flow
Create Payment
|
v
processing ---------------------> succeeded (auto-capture)
| |
|---> requires_action |---> partially_refunded ---> refunded
| | |---> refunded
| |---> processing -----> succeeded
| | |--> requires_capture ---> captured
| | |--> failed
| |---> failed
| |---> canceled
|
|---> requires_capture ---------> captured
| | |
| |---> canceled |---> partially_refunded ---> refunded
| |---> refunded
|
|---> failed
|---> canceledAuto-capture vs manual capture
By default, payments use auto-capture: authorization and capture happen in a single step. The payment transitions directly from processing to succeeded.
If you need to authorize first and capture later (e.g., charge only when you ship the order), set captureMethod to "manual" when creating the payment:
{
"amount": { "currency": "USD", "valueMinor": 5000 },
"merchantReference": "order-1234",
"captureMethod": "manual",
"paymentMethod": { ... }
}With manual capture, the flow changes:
- Payment reaches
requires_capture— funds are held on the customer's card. - You call
POST /v1/payments/{id}/captureto charge the held amount. - Payment moves to
captured.
If you decide not to charge, call POST /v1/payments/{id}/cancel to release the hold. The payment moves to canceled.
Authorizations expire (typically 7 days for cards, varies by provider). If you do not capture or cancel before expiry, the hold is released automatically and the payment is canceled.
See Capture for the full auth-and-capture guide.
Async nature of payments
Payment processing is inherently asynchronous. When you create a payment, the API response returns the initial status (usually processing), but the final result arrives later via webhook.
Always use webhooks as the source of truth for payment status. Do not rely solely on the synchronous API response or customer redirects to determine whether a payment succeeded.
A typical flow:
- You call
POST /v1/payments— response returnsstatus: "processing". - The provider processes the payment (may take milliseconds or minutes).
- Miracle sends a
payment.succeededorpayment.failedwebhook to your endpoint. - Your webhook handler fulfills the order or notifies the customer.
You can also poll GET /v1/payments/{id} at any time, but webhooks are the recommended approach.
Amount format
All monetary amounts use the MoneyAmount format with integer minor units in the valueMinor field. No decimals, no floating point.
| Currency | Exponent | Example | valueMinor |
|---|---|---|---|
| USD, EUR | 2 | $50.00 | 5000 |
| JPY | 0 | 5000 | 5000 |
| BHD | 3 | 5.000 BHD | 5000 |
{
"amount": {
"currency": "USD",
"valueMinor": 5000
}
}Key concepts
Dive deeper into specific payment topics:
- HPP Integration — redirect-based checkout using the Hosted Payment Page
- Capture — authorization and capture (two-step) flow
- Refunds — returning funds to the customer
- Cancel / Void — canceling a payment before capture
- 3D Secure — handling customer authentication challenges
- Payment Methods — supported methods and discovery API
- Idempotency — safe retries with idempotency keys