If you've ever tried to use a gift card or wallet for an online purchase, you've probably experienced this frustration. You have a ₹500 as gift card or in wallet, but your cart total is ₹750. The checkout won't let you combine the gift card with your credit card. You either abandon the extra items or don't use the gift card at all.
This isn't a technical limitation; it's an architectural one. Most payment systems treat each payment method as an isolated transaction rather than composable building blocks.
At Razorpay, we power checkout for thousands of merchants across India, and this limitation was costing them real money.
Merchants wanted to accept partial payments through gift cards, loyalty points, or store credit, with customers covering the remaining balance through standard payment methods. However, our payment gateway architecture wasn't designed for chaining multiple payment methods in a single order.
Each payment method lived in its own silo, processed independently, with no concept of partial payments or sequential authorization.
That's why we built LinkedPayments, a system that treats payment methods as composable units that can be chained together to fulfill an order. Customers can now use a gift card for ₹500, then pay the remaining ₹250 via UPI, card, or any other supported method.
The system handles authorization sequencing, failure recovery, settlement splitting, and reconciliation across linked payments automatically.
Here's how we built it, and more importantly, how we designed for reliability when payment chains introduce complex failure modes.
The Core Challenge: Payment Atomicity vs. Composability
Before diving into the solution, let's talk about what makes linked payments architecturally complex. The fundamental tension is between atomicity (payments either complete fully or fail entirely) and composability (payments can be partial and sequential).
Traditional payment systems optimize for atomicity. When a customer initiates a payment, the system authorizes the full amount from one payment method, captures it if authorization succeeds, and settles it to the merchant.
This flow is simple, deterministic, and easy to reason about. Either the payment worked or it didn't. Reconciliation is straightforward because there's exactly one authorization, one capture, and one settlement per order.
Linked payments break this simplicity. Now you have multiple authorizations for a single order, sequential dependencies between them (the second payment only happens if the first succeeds), partial failure scenarios (first payment succeeds, second fails), and split settlements (money comes from different sources). Each of these complexities introduces new failure modes.
Linked payments break this simplicity. Now you have multiple authorizations for a single order, sequential dependencies between them (the second payment only happens if the first succeeds), partial failure scenarios (first payment succeeds, second fails), and split settlements (money comes from different sources).
Here's what makes this architecturally challenging: each payment method at Razorpay has its own microservice and its own database.
Gift cards are managed by one service with its own data store, UPI by another, cards by yet another. In a monolithic architecture, coordinating sequential payments would be straightforward; you'd just use database transactions. But in a microservices architecture, maintaining consistency across multiple independent services with separate databases requires explicit orchestration, distributed state management, and careful handling of partial failures.
Each of these complexities introduces new failure modes that don't exist when everything lives in a single service.
Consider what happens if the first payment method (gift card) succeeds but the second (UPI) fails. You can't just tell the customer "payment failed, try again" because you've already captured ₹500 from their gift card. You need to reverse that capture, refund the gift card balance, and handle the coordination between two different payment providers who might have different reversal timelines and APIs.
The reconciliation complexity multiplies. Merchants need to understand which portion of an order's payment came from which source. Settlement reports need to break down gift card amounts separately from standard payment method amounts. Refunds become complicated because you need to potentially refund across multiple payment methods in the correct proportions. Tax calculations need to account for different payment sources that might have different tax treatments.
The LinkedPayments Architecture: Building for Complexity
Our solution treats linked payments as a first-class concept with explicit orchestration rather than trying to shoehorn it into existing single-payment flows.
The architecture has several key components designed specifically to handle the sequential, multi-source nature of payment chains.
The Payment Orchestrator is the brain of the system. It maintains the state machine for payment chains, coordinates sequencing across multiple payment methods, handles failure recovery and reversals, and ensures exactly-once semantics even when components fail and retry.
The orchestrator knows that gift cards must be attempted first (per business rules), subsequent methods can only proceed after previous ones capture successfully, and any failure in the chain triggers reversal of all completed payments.
Payment Method Handlers are specialized for their respective methods. The Gift Card Handler understands gift card balance checks, partial redemption logic, and expiry validation.
Each handler exposes a consistent interface (authorize, capture, reverse) but implements method-specific logic internally. This abstraction lets the Orchestrator treat all payment methods uniformly while each handler optimizes for its method's unique characteristics.
The data model is critical for maintaining consistency. Here's how it actually works: when an order is created, the LinkedPayment gets initialized by the customer. The order stores all payment_ids in the chain format within order_meta, indexed by order_id. When the primary payment method (UPI or card) is authorized, an event is sent to Kafka. A worker consumes this event, checks order_meta using the order_id to retrieve the array of stored payment_ids in chain format, and then authorizes the next payment in the sequence.
This event-driven approach ensures sequential processing while maintaining loose coupling between payment steps.
Redis provides distributed locking to prevent race conditions. When processing a payment chain, we acquire a lock on the order ID to ensure only one process attempts to advance the chain at a time. This prevents double-processing.
Handling Failure Scenarios: The Hard Part
The real complexity in LinkedPayments isn't the happy path; it's the failure scenarios. Payment systems are inherently distributed, which means failures can happen at every boundary.
Networks timeout, payment providers have outages, authorization succeeds but capture fails, reversals are requested but the provider is temporarily unavailable. The system needs to handle all of these gracefully without leaving orders in inconsistent states.
Scenario 1: Second payment fails after first succeeds. If a Customer attempts to pay ₹750 using ₹500 gift card and ₹250 UPI. Gift card authorization succeeds, but UPI authorization fails (insufficient balance or technical issue). Here's the critical design decision: we don't capture any payments until all authorizations in the chain succeed. This means we only have an authorization hold on the gift card, not an actual capture. The system simply reverses the authorization (releases the hold), and importantly, this reversal doesn't incur any charges to the merchant because no money was actually captured. The customer can retry the order with a different payment combination without any financial impact.
Scenario 2: Capture fails after authorization succeeds. Both payment methods authorize successfully, but capturing the second payment fails due to a provider outage. We can't just retry the capture indefinitely because authorizations expire (typically 7 days for cards, but gift cards might expire sooner). The system needs to reverse all authorizations that won't be captured and notify the customer to retry the entire order.
Scenario 3: Partial refund on linked payments. Customer requests a partial refund for ₹200 on a ₹750 order paid via ₹500 gift card and ₹250 UPI. Which payment method should we refund from? The system provides merchants with the flexibility to decide. Merchants can choose to refund from the gift card, refund from UPI, or even split the refund across both methods based on their business policies or customer preferences. The system calculates the specified refund distribution, triggers appropriate reversals for the chosen payment methods, and updates settlement records to reflect the new amounts. This flexibility lets merchants optimize for customer experience or operational efficiency based on their specific use case.
Scenario 4: Idempotency on retries. Network glitches can cause the same capture request to be sent multiple times. The system must recognize duplicate requests (using idempotency keys) and return the same result without double-capturing.
We handle these scenarios through careful state machine design. Each payment in the chain progresses through well-defined states (created, authorized, captured, failed, refunded) with explicit transitions. State transitions are atomic database operations with pessimistic locking to prevent concurrent modifications. The Orchestrator implements retry logic with exponential backoff for transient failures and circuit breakers for sustained provider outages.
The API Contract: Making It Simple for Merchants
Despite the internal complexity, we designed the merchant-facing API to be remarkably simple. Here's one of the key architectural advantages: merchants don't need to create anything special for linked payments orders.
This is crucial because you never know upfront whether a customer will combine two payments or use a single method. Forcing merchants to create different order types would be impractical. Instead, merchants create orders exactly like any normal payment:
Creating an order with linked payments:
POST /v1/orders
{
"amount": 7500,
"currency": "INR",
}
That's it. Just a standard order creation with amount and currency. The system returns an order ID and checkout URL.
The magic happens dynamically at checkout. When the customer reaches the payment page, they choose their payment method. If they select "Use Gift Card + Another Method," the LinkedPayments flow activates automatically. The system handles the entire chain (gift card authorization, then secondary payment method) without the merchant needing to have anticipated this during order creation. Everything is updated dynamically based on the customer's actual payment preference.
Webhook events keep merchants informed:
Webhook events keep merchants informed, but with an important design choice: we withhold authorization webhooks until all payments in the chain are authorized. This prevents merchants from receiving premature notifications that might suggest the order is ready to fulfill when subsequent payments could still fail.
{
"event": "payment.captured",
"order_id": "order_XYZ",
"linked_payment_id": "lp_001",
"method": "gift_card",
"amount": 5000,
}
Only after all authorizations succeed do merchants receive webhook notifications. When all Linked Payments have been captured, we send an order paid webhook notification. If any payment authorization fails, the order is not marked as "paid" and the amount which was authorized automatically reverses.
This event-driven approach lets merchants track progress without polling APIs. They can update their order status in real-time, show customers which payments completed, and handle failures gracefully.
Reconciliation and Settlement: Following the Money
Here's one of the most elegant aspects of the LinkedPayments architecture: reconciliation and settlement work out-of-the-box without requiring any changes. This wasn't accidental; it was a deliberate design choice.
The system was architected so that each linked payment behaves like an independent payment from the settlement perspective.
When ₹500 comes from a gift card and ₹250 from UPI, the existing settlement infrastructure treats them as two separate payment transactions associated with the same order. The gift card portion settles according to existing gift card program rules. The UPI portion settles through standard UPI settlement flows.
Merchants receive settlement reports that automatically break down amounts by payment method using the same reporting infrastructure they already use for regular payments. No new report formats, no special reconciliation processes, no additional integration work. The LinkedPayments logic is transparent to downstream settlement and reconciliation systems.
This design choice meant we could ship LinkedPayments without requiring merchants to update their financial operations, accounting integrations, or reconciliation workflows. It just works with their existing setup.
What We Got Right (And What We'd Do Differently)
Building LinkedPayments taught us several lessons about designing payment systems for composability.
State machines are essential for reliability. Explicit state transitions with validation make the system predictable and debuggable. When a payment gets stuck, we can look at its state and understand exactly where in the flow it stopped and what the next valid transitions are.
Idempotency isn't optional. Payment operations must be safely retryable. Using idempotency keys for every authorization, capture, and reversal request ensures that network issues don't cause double-processing. We learned to make idempotency keys deterministic based on order ID and sequence number so retries naturally use the same key.
Design for automatic reversal handling. Rather than building complex reversal logic as a separate concern, we ensured our design handled reversals automatically. If any payment in the chain gets stuck or fails, the system automatically reverses completed authorizations without requiring manual intervention or specialized reversal services. This design-first approach to failure handling proved more reliable than bolt-on reversal mechanisms.
Settlement works out-of-the-box. As mentioned earlier, we designed LinkedPayments so that settlement was supported from day one without requiring any changes to existing settlement infrastructure. Each linked payment leverages the standard settlement flows for its respective payment method. This meant zero additional complexity for settlement routing or reconciliation.
Testing linked payments requires scenario coverage. Unit tests aren't sufficient. We built integration test suites covering dozens of scenarios: all payments succeed, first fails, second fails, both fail, reversals succeed, reversals fail, partial refunds, full refunds. This comprehensive testing caught edge cases that would have been painful to discover in production.
If we were starting over, we'd invest even more heavily in observability from day one. The ability to trace a payment chain's execution across multiple services, understand decision points, and replay sequences for debugging is invaluable. We added this later but wish we'd built it into the initial architecture.
Real-World Impact: When Flexibility Drives Adoption
The business impact of LinkedPayments is currently focused on gift card programs, where we've seen measurable improvements in merchant adoption and customer behavior.
Merchants using gift card programs saw redemption rates increase. Customers who previously abandoned gift cards because they couldn't cover full purchases now use them confidently, knowing they can combine with other payment methods. This increased redemption drives customer loyalty and repeat purchases.
Average order values increased for merchants offering gift cards. When customers can apply gift card balances as partial payment, they're more willing to make larger purchases. The psychology of "I'm already getting ₹500 off" encourages adding more items to the cart.
Payment success rates improved because customers have more flexibility. If a card payment fails, they can try splitting it with a gift card and a smaller card payment. This fallback path converts what would have been failed checkouts into successful orders.
However, LinkedPayments was architected to support any combination of payment methods, not just gift cards.
The same infrastructure that enables gift card + UPI combinations can support scenarios we're actively exploring: store credit + card for e-commerce platforms with loyalty programs, wallet + netbanking for customers managing balances across multiple sources, corporate credit + personal card for expense reimbursement scenarios, and multiple cards for high-value purchases split across credit limits.
From a platform perspective, LinkedPayments positions Razorpay competitively for merchants running loyalty programs, gift card initiatives, or store credit systems.
These merchants need payment infrastructure that supports their business model, not just basic transaction processing. By providing this capability out of the box, we've differentiated ourselves from competitors who would require custom integration work.
The Broader Lesson: Composability as Infrastructure
LinkedPayments demonstrates a broader principle about building payment infrastructure: composability multiplies capability. When you design payment methods as modular, chainable units rather than isolated silos, you enable use cases you didn't originally anticipate.
We've already seen merchants using LinkedPayments for scenarios we never explicitly designed for. Subscription payments where customers apply account credit before charging the registered card.
This composability emerges naturally from the architecture. Because the Orchestrator treats payment methods uniformly and the state machine handles arbitrary sequencing, any combination of supported methods "just works" without requiring special case implementation.
The lesson applies beyond payments. When building platform capabilities, investing in composability early pays dividends. Each new composable unit you add doesn't just enable one new use case; it enables N new combinations with existing units. The value grows combinatorially rather than linearly.
The Bottom Line
LinkedPayments transforms payment acceptance from an all-or-nothing proposition to a flexible composition of sources. By treating payment methods as chainable building blocks with robust orchestration, failure handling, and settlement routing, we've enabled merchants to support complex payment scenarios that would otherwise require custom development.
The technical complexity is real. Payment chains introduce failure modes, latency challenges, reconciliation challenges, and settlement complications that don't exist in single-payment flows. However, the business value justifies this complexity. Merchants need this flexibility to run modern loyalty programs, gift card initiatives, and customer credit systems.
The architecture we've built demonstrates that composability is achievable in payment systems when you design explicitly for it. Clear state machines, comprehensive failure handling, idempotent operations, and observable execution create systems that remain reliable even as complexity increases.
If you're building payment infrastructure or financial systems that need to support multiple funding sources, the lessons from LinkedPayments apply directly. Design for composability from day one, make failure handling first-class, invest in state machine rigor, and build observability that lets you understand what's happening when things go wrong.
The future of payment systems isn't just supporting more payment methods; it's enabling flexible combinations of those methods to match how customers actually want to pay. LinkedPayments is our step toward that future, and the patterns we've discovered are worth considering for anyone building similar financial infrastructure.
editor: @paaarth96


Top comments (0)