How do you design the payment processing component for an online store?
Fast recall
Quick revision
Core answer
Strategy design pattern for runtime algorithm selection, understanding of payment transaction lifecycle and external gateway integration, separation of payment concerns from order logic, and awareness of security considerations like not storing sensitive data
Hardcoding payment logic inside Order class with if-else for each method, storing raw credit card numbers in database violating PCI compliance, not modeling payment status as a state machine, or making payment calls synchronous without handling timeouts and retries
Mention idempotency keys for safe retries, webhook handling for asynchronous payment confirmations, PCI DSS compliance by tokenizing cards, handling partial refunds, and integrating with multiple gateways like Stripe and Razorpay behind the strategy interface
Interview-ready guide
Interview answer
Payments are a textbook Strategy situation: there are many payment methods — credit card, debit, UPI, wallet — but they all sit behind one operation. So I'd define a PaymentProcessor interface with initiate, verify, and refund, and write a concrete processor per method. A PaymentService holds a map from payment method to processor and just delegates, so adding a new method like a buy-now-pay-later option is a new class, not a change to the checkout flow.
The Payment entity itself records the payment id, the order id, the method, the amount as a BigDecimal, a status that moves through pending, success, failed, and refunded, the external transaction id returned by the gateway, and timestamps.
Two production requirements are idempotency and asynchronous confirmation. Idempotency is critical: networks retry, and if the same payment request hits the service twice you must not charge the customer twice. So the client sends an idempotency key, and the service checks whether it's already processed that key — if so, it returns the existing result instead of charging again. And payments are asynchronous; the gateway usually returns pending, and you confirm success later through a webhook or by polling with verify. A common fresher mistake is assuming the payment succeeds synchronously and immediately marking the order paid. Modelling the pending state and reconciling it afterward is what makes the design realistic.
How do you design the payment processing component for an online store
Direct Answer
Design a PaymentProcessor interface with methods like initiatePayment, verifyPayment, and refundPayment. Implement concrete processors for each payment method: CreditCardProcessor, DebitCardProcessor, UPIProcessor, WalletProcessor. The Payment entity stores paymentId, orderId, method (enum), amount, status (PENDING/SUCCESS/FAILED/REFUNDED), externalTransactionId from gateway, and timestamps. Order class holds a Payment reference. Use the Strategy pattern—OrderService picks the correct processor implementation based on user's selected payment method, keeping payment logic decoupled from Order.
Deep Dive & Architecture
Payment is a stateful record
A Payment keeps the order id, amount, method, gateway reference, idempotency key, and status. Transitions such as PENDING → SUCCEEDED or PENDING → FAILED are validated so a late callback cannot move a refunded payment back to success.
The gateway stays behind an interface
PaymentProcessor implementations translate the application's request into provider-specific calls. For example, a UPI processor may return a pending result while a wallet processor may complete immediately. The order service depends on the interface and stores the provider's external transaction id for later verification.
Retries return the same result
Before charging, the service stores the idempotency key with a uniqueness constraint. Repeating the same request returns the existing payment instead of creating a second charge. Gateway callbacks are authenticated, recorded, and processed idempotently as well. The order becomes paid only after a trusted success result; timeouts remain pending until reconciliation confirms the final state.
Code Example
interface PaymentProcessor {
PaymentResult initiate(Payment payment);
PaymentResult verify(String externalTxnId);
PaymentResult refund(Payment payment);
}
class UpiProcessor implements PaymentProcessor {
public PaymentResult initiate(Payment p) { /* call UPI gateway */ return PaymentResult.pending(); }
public PaymentResult verify(String id) { /* poll gateway */ return PaymentResult.success(); }
public PaymentResult refund(Payment p) { return PaymentResult.refunded(); }
}
class PaymentService {
private final Map<PaymentMethod, PaymentProcessor> processors;
PaymentResult pay(Order order, PaymentMethod method, String idempotencyKey) {
// idempotency: same key -> return the existing result, never double-charge
if (repo.exists(idempotencyKey)) return repo.resultFor(idempotencyKey);
Payment payment = repo.create(order, method, idempotencyKey);
return processors.get(method).initiate(payment);
}
}
The idempotency key makes a retried request safe — the customer is charged at most once.
Common Mistakes & Pitfalls
- No idempotency — a network retry double-charges the customer. Use an idempotency key.
- if-else per payment method — use Strategy; each gateway is a class.
doublefor amount — useBigDecimal.- Assuming synchronous success — gateways are async; model PENDING and verify later via webhook/poll.
Check your understanding
Answer these without looking back at the article.
- How do you design the product review and rating system?
- Design an online shopping system like Amazon. What are the core classes?
- How do you design the order state machine in the shopping system?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.