Cancel an unsubmitted payment
Use POST /v1/payments/{id}/cancel with a current sandbox API key that has payments:write and one Idempotency-Key matching [A-Za-z0-9_-]{8,128}. Send an empty body and no query parameters. The service takes merchant ownership and audit identity from authentication; caller-supplied merchant, actor or internal-key headers are rejected.
Cancellation is available only while the original payment is unexpired and CREATED, before a payer method or issuer attempt has been admitted. Cancellation and wallet/bank admission use the same database locks. Once submission wins, cancellation returns 409 PAYMENT_NOT_CANCELLABLE. Pending, uncertain, approved, declined and ordinarily expired payments cannot be cancelled. This command does not refund, reverse, reserve or post money.
A successful command returns a retained CancelledPayment with state EXPIRED and reason PAYMENT_CANCELLED. A payment link retains reason LINK_CANCELLED and its existing CANCELLED link status. The transition, event, webhook outbox, authenticated actor audit and cancellation receipt commit together. An already merchant-cancelled original produces no second transition.
Keep the original payment ID and key. If a reply is lost, a deadline expires, or the service returns 503, the cancellation may already have committed. Retry that same command with the same key. The response bytes remain identical across service and database restarts. Reusing that key for another payment returns 409 IDEMPOTENCY_CONFLICT. API-key validity, scope and shared merchant quota are checked again for every replay. A revoked key must be replaced with another authorized key for the same merchant; the first cancellation actor remains unchanged.
The same call in Futa’s generated TypeScript client, kept for its own use and not published:
const result = await payments.cancelPayment({ id: originalPaymentId, idempotencyKey: retainedCancellationKey,});Send an empty POST; nothing retries it for you. The answer includes the original amount and currency and the merchant reference. Treat the checkout URL as private even though a cancelled payment cannot be submitted. Responses use Cache-Control: no-store.
A 404 means the payment is absent from the authenticated merchant. Handle 401/403 by restoring appropriate access, 429 by waiting for the quota window, and 409 by reading the original payment state or correcting conflicting key reuse. The synthetic implementation does not establish live-provider cancellation or production readiness. See direct sandbox payments for method/MSISDN initiation. An admitted direct payment is already submitted and cannot use this cancellation endpoint.