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
| Observation | Safe interpretation | Required response |
|---|---|---|
| Local command committed | Work accepted locally | Check financial and execution acceptance separately |
| Definitive guarded-ledger rejection | That financial transition was rejected | Stop the associated execution path |
| Ledger acknowledgement missing | Posting outcome uncertain | Recover the same posting identity |
| Payment request timed out | Execution outcome uncertain | Resolve through documented canonical evidence and safe replay semantics |
| Payment definitively succeeded | Execution happened | Complete missing accounting and application processing without executing again |
| Payment confirmed, no bank evidence yet | Payment outcome known; bank receipt not established | Keep 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: