A shared payment interface with provider selection, status tracking and scheduled recovery work.
This payment orchestration case study covers a checkout that works with several providers through one integration layer. Connectors translate the authorisation, capture and cancellation requests. Routing rules then pick a provider by conditions, volume splits or fallback settings.
We kept the payment's later work in scope as well, since it reaches well past the checkout. Pending checks, refunds and merchant notifications all outlive it. Each one needed a home of its own.
The challenge
A provider may confirm a payment before the merchant app has heard about it. Treating both as one status hides a real difference. The payment may still need a check, or the merchant update may just need another try. The integration therefore kept two records apart. One holds the financial operation, and the other holds the work still waiting to go out.
What we built
Confirmation validates the request and loads the payment and customer context. It then works out which authentication the payment requires and selects the connector to use. Optional fraud checks can run before the provider call.
A verified incoming notification updates the payment. A separate signed webhook then tells the merchant, with its own delivery tracking and retries. Refunds and disputes also pass through the same integration layer that handles the original payment. Saved payment details can sit in a separate vault.
Technical approach
Typed Rust interfaces define the operations each connector supports. Conversion functions map the shared requests and responses to a provider's format, error codes included. Type checking catches a mismatched shape, though provider behaviour still needs checking at runtime. Adding a connector means a new application build.
Make unfinished work durable
PostgreSQL holds the jobs for payment checks, refund-status requests and outbound webhooks. Schedulers pick the due records and send the work through Redis to consumers. A retry raises the attempt count and records the next run time, while completion stores the finished status and business result. Stopping a process leaves its outstanding database job ready for later.
Tenant settings keep PostgreSQL schemas and Redis prefixes apart. Merchant-sensitive fields use merchant-specific keys, and the key store sits behind a master key or an external KMS.
Payment state can live in PostgreSQL directly. An optional mode writes through Redis first and drains the changes to PostgreSQL afterward. That mode allows a brief disagreement between the two stores, and it leans on Redis durability and the drainer's progress.
Project success
Checkout can switch provider routes while keeping the shared payment model. More useful for follow-up work, an operator can tell a pending payment apart from an undelivered merchant webhook. The job record shows the attempts and the next scheduled run. An accepted provider update no longer vanishes inside a failed notification.
Technology stack
Application
Rust, Actix-web
Transaction and job storage
PostgreSQL
Scheduling transport
Redis
Integration contracts
Typed Rust connector interfaces
Key management
Merchant key store, external KMS options
Other engagements
Recognize the pattern?
If one of these looks like your situation, the discovery call is where we find out how close.