Skip to content

Payment obligations and replacement attempts

Merchant API 0.34 adds GET /v1/payments/{id}/obligation with payments:read. A root or any child payment ID returns the same capability-free family: preserved merchant reference, amount, currency and merchant expiry, ordered attempts, current head and nullable collected_payment_id. The corresponding server-only team route is GET /portal/merchants/{merchant}/payments/{id}/obligation, using the current MFA identity and X-Futa-Membership-Version.

These are passive reads. Do not send a body, query, content encoding, checkout token or idempotency key. Responses use Cache-Control: no-store. Tenant ownership, live membership/version and the shared merchant quota apply. Missing or foreign payments and a disabled synthetic switch feature return 404; unavailable or malformed upstream responses return 503. Nothing retries a read for you.

The same call in Futa’s generated TypeScript client, kept for its own use and not published:

const family = await payments.getPaymentObligation({ id: originalPaymentId });
if (family.collectedPaymentId !== null) {
const collectedAttempt = await payments.getPayment({ id: family.collectedPaymentId });
// Display and address receipts/refunds using the actual collected attempt.
}

Original declined payment IDs remain declined, and their creation idempotency responses and historical events are unchanged. Each attempt has its own actual state. Existing dashboard counts and success rates describe attempts; approved monetary volume is counted from collected attempts, without multiplying the obligation amount by the number of attempts. An absent collection ID does not establish that a pending authorization cannot debit. INVESTIGATION requires investigation; CONSISTENT describes the recorded ancestor closure fences, not every possible payment risk.

The generated models preserve nullable fields and exact timestamp strings, including fractional seconds. The standalone payment-obligation.schema.json and server additionally enforce conditional root/child rules, ordered unique identity chains and CDF amounts in multiples of 100. The technical 64-replacement bound is not a commercial retry policy.

This is a default-off local synthetic feature. Public alternate-method controls remain gated until webhook correlation and portal integration are completed. Existing v1 webhook bodies still describe individual attempts; this API publication does not change subscriptions, emit a root approval, or claim real-provider no-debit guarantees.