Distributed payment ledger with idempotent settlement
A payment ledger that cannot double-charge, assembled one safety layer at a time. Arrow keys or click to advance.
01
The request path into the payment service
flowchart LR
Client([Merchant client]) --> AG[API Gateway]
AG --> PS[Payment Service - ECS Fargate]
A client authorization arrives over TLS at API Gateway, which authenticates the merchant and forwards to a stateless Payment Service on ECS Fargate. Nothing here is safe against retries yet - it is just the entry layer.
02
The idempotency gate
flowchart LR
Client([Merchant client]) --> AG[API Gateway]
AG --> PS[Payment Service - ECS Fargate]
PS -->|conditional write PENDING| IK[(DynamoDB idempotency)]
IK -->|on retry return cached| PS
Before any money moves, the service claims the client-supplied UUID with a conditional write to DynamoDB - attribute_not_exists, ConsistentRead true. First writer wins and proceeds; a retry reads the winner's stored response and returns it verbatim. The gate fails closed.
03
The double-entry ledger
flowchart LR
Client([Merchant client]) --> AG[API Gateway]
AG --> PS[Payment Service - ECS Fargate]
PS -->|conditional write| IK[(DynamoDB idempotency)]
PS -->|pooled txn| RP[RDS Proxy]
RP -->|debit and credit pair| AU[(Aurora PostgreSQL Multi-AZ ledger)]
Past the gate, the service writes one DEBIT and one CREDIT in a single Aurora PostgreSQL ACID transaction, summing to zero, through RDS Proxy in transaction mode so pooled connections never leak tenant context. A UNIQUE constraint on idempotency_key is the second line of defense. Multi-AZ synchronous replication gives RPO zero.
04
Events without two-phase commit
flowchart LR
Client([Merchant client]) --> AG[API Gateway]
AG --> PS[Payment Service - ECS Fargate]
PS -->|conditional write| IK[(DynamoDB idempotency)]
PS -->|ledger and outbox in one txn| AU[(Aurora PostgreSQL ledger)]
AU -->|poll unpublished| OL[Outbox Poller Lambda]
OL --> SQS[SQS]
SQS --> DC[Downstream consumers]
The outbox row is written in the same Aurora transaction as the ledger entries. A Lambda polls unpublished rows every 100 ms, publishes to SQS, and stamps published_at. Downstream consumers dedupe on the idempotency key - at-least-once, never lost, never phantom.
05
Saga orchestration for multi-step settlement
flowchart TD
subgraph Edge[Edge]
Client([Merchant client]) --> AG[API Gateway]
end
subgraph Compute[Compute]
AG --> PS[Payment Service - ECS Fargate]
SF[Step Functions saga] -->|hold release fee| PS
OL[Outbox Poller Lambda]
end
subgraph Data[Data]
PS -->|conditional write| IK[(DynamoDB idempotency)]
PS -->|ledger and outbox| AU[(Aurora PostgreSQL ledger)]
AU -->|poll| OL --> SQS[SQS] --> DC[Downstream consumers]
end
PS -->|start settlement| SF
Marketplace settlement spans hold, KYC verify, release, and fee collection - no single ACID transaction covers it. A Step Functions Standard Workflow persists each state, retries with backoff, and on failure runs a compensating state machine with derived idempotency keys.
06
The full system with reconciliation and audit
flowchart TD
subgraph Edge[Edge]
Client([Merchant client]) --> AG[API Gateway]
end
subgraph Compute[Compute]
AG --> PS[Payment Service - ECS Fargate]
PS -->|tokenize| PC[Payment Cryptography HSM]
SF[Step Functions saga] -->|hold release fee| PS
PS -->|start settlement| SF
OL[Outbox Poller Lambda]
RL[Reconciliation Lambda]
end
subgraph Data[Data]
PS -->|conditional write| IK[(DynamoDB idempotency)]
PS -->|ledger and outbox| AU[(Aurora PostgreSQL ledger)]
AU -->|poll| OL --> SQS[SQS] --> DC[Downstream consumers]
RL -->|scan nightly| AU
end
subgraph Control[Control and audit]
SF -->|stuck compensation| EB[EventBridge]
RL -->|discrepancy| EB
EB --> SNS[SNS] --> IM([Incident Manager])
PS -.privileged access.-> CT[(CloudTrail audit log)]
end
Every layer plus the safety net: a daily reconciliation Lambda scans the ledger for unbalanced or duplicate entries, a stuck compensation pages on-call via EventBridge to SNS to Incident Manager, CloudTrail records every privileged access, and Payment Cryptography keeps PANs out of the ledger entirely.