Skip to content
Your database transaction stops at the payment API
/ArticleJuly 9, 2026Varun Mohan

A database transaction can atomically commit the records it controls. It cannot make an independently operated payment processor's action part of the same local commit. Consider a worker executing an approved payment. The database transaction commits a command and an outbox item. The worker sends the request, the processor executes it, and the worker crashes before recording the response. After restart, the database still shows incomplete work. Another payment request might duplicate the action.

This problem exists whether the internal ledger is in Postgres or a separate service. A separate ledger adds another boundary; the existing processor boundary does not make that additional coordination free.

Follow the acknowledgements

sequenceDiagram
  participant DB as Postgres
  participant W as Worker
  participant P as Payment provider
  DB->>DB: Commit command and outbox
  DB->>W: Durable work available
  W->>P: Execute with persisted request identity
  P->>P: Execute payment
  P--xW: Response lost; worker crashes
  Note over DB,P: Local incomplete state does not imply external failure
ObservationSafe interpretationRequired response
Local command committedWork accepted locallyCheck financial and execution acceptance separately
Definitive guarded-ledger rejectionThat financial transition was rejectedStop the associated execution path
Ledger acknowledgement missingPosting outcome uncertainRecover the same posting identity
Payment request timed outExecution outcome uncertainResolve through documented canonical evidence and safe replay semantics
Payment definitively succeededExecution happenedComplete missing accounting and application processing without executing again
Payment confirmed, no bank evidence yetPayment outcome known; bank receipt not establishedKeep settlement distinct

This table is an architectural failure model, not a statement that every provider exposes the same lookup or replay interface.

Use identities at the right layer

Business intent identifies what was approved. A ledger-write identity identifies a financial posting. A payment-request identity identifies a provider operation. Event identity identifies a delivered notification. They can be derived from the same command, but their guarantees are not interchangeable. Provider replay protection has conditions and retention limits. For example, Stripe documents request-key reuse, parameter comparison and possible key pruning. A key in your database does not extend a provider's retention policy or prove a new request is safe. Stripe idempotent requests.

Do not assume lookup by request key exists. If the response containing an operation ID was lost, recovery must use an actually supported lookup or identical-request retry contract. Otherwise hold the operation for investigation instead of inventing an API or issuing a replacement action.

What a ledger boundary adds

FRAGMENT's configured posting rules can reject invalid financial transitions independently of the calling application. That helps only where the model, eligible accounts and conditions describe the intended invariant, and the executor honors the result. It does not turn a rejected posting into proof that no separate credential holder could execute a payment. Configure consistency. Supported FRAGMENT payments also associate payment flows with configured accounting. Evaluate those documented capabilities as an integrated path rather than assuming every application must wire an external processor to a separate ledger. Preserve the remaining operational coordination and verify exact recovery semantics. Make Payments.

Choose the financial boundary because its enforcement and supported integration benefits justify its costs. The existence of a processor boundary alone is not a reason to choose a remote ledger.

Implementation references

Use the current API contracts when implementing the architecture: