> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.runpayments.io/docs/guides/payments/card-present/unified-charge-workflow/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runpayments.io/_mcp/server. # Unified Charge Workflow [`POST /api/terminal/{device_id}/charge-card`](/reference/terminal-api/charge-card/initiate-charge) runs up to four stages in one interaction: **read** → **charge** → **print** (optional) → **email** (optional). ```json { "amount": "25.00", "beep": true, "include_signature": true, "print_receipt": true, "send_receipt": true, "email": "customer@example.com" } ``` * **Amount format:** decimal dollars as a string, greater than zero, at most 2 decimal places (e.g., `"19.99"`). Any invalid amount returns `400 {"error": "Field amount is invalid"}`. The same rule applies to `read-card`. * **`order_id`** is optional; if omitted, one is generated as `<4-digit-random>`. * **Requesting a step the device can't do never fails the transaction.** The charge completes, and the skipped step is tagged in `result.stage_errors`. * On success, `result` is the Payments API charge response. Fields vary by gateway, so treat the documented fields as representative, not exhaustive. ## Reading `stage_errors` | Key | Value | When it appears | | ----------- | ------------------------------------- | ----------------------------------------------------------------- | | `read` | Error message | The read failed or was cancelled. The charge was never attempted. | | `signature` | Always `"not supported"` | Capability gap. | | `charge` | Error message | A gateway fault prevented the charge — **not** a decline. | | `print` | `"not supported"` or an error message | Capability gap and device errors. | | `other` | Error message | Anything uncategorized. | ## Handling declines > **A decline is an outcome, not a failure — and status won't tell you about it** > > A declined card returns `status: "succeeded"` with no `stage_errors`. Approval or decline is indicated by **`result`** — the Payments API's own `result` field, carried inside the Terminal API's `result` object. Always check `result` before showing the merchant an approval. > > Print and email **still run on a decline** if requested — the merchant receives a receipt for the declined charge. Don't treat "printed" or "emailed" as equivalent to "paid." `result` values, per the [Payments API](/reference/payments-api/process-charge): | Value | Meaning | | ----- | -------- | | `A` | Approved | | `B` | Retry | | `C` | Declined | ### Recommended result handling 1. `status` is `failed` or `cancelled` → show the `stage_errors.read` (or other) message; no charge occurred. 2. `status` is `succeeded` → check `result`: * `A` (Approved) — show the approval. * `C` (Declined) — show the decline. * `B` (Retry) — a transient processor issue, not a decline; retry the charge with a new `charge-card` call rather than showing it to the merchant as declined. 3. Check `stage_errors` for `signature` / `print` tags and surface them as non-blocking notices. > Read, charge, print, and email in a single charge-card interaction.