Payment Gateway
Sabse important concept IDEMPOTENCY hai. Network timeout par client retry karega — us waqt SAME idempotency key ke saath aayi request DOBARA charge nahi honi chahiye, pehle waale ka result return hona chahiye. Ye batana is problem ka core hai.
Multiple providers (Razorpay, Stripe, PayPal) ke liye PaymentProvider interface banao aur runtime par choose karo. Payment status ek State machine hai: INITIATED → PENDING → SUCCESS/FAILED → REFUNDED. Aur webhooks zaroor mention karo — payment async complete hoti hai, isliye provider callback bhejta hai aur wo callback bhi idempotent hona chahiye.
interface PaymentProvider {
PaymentResult charge(PaymentRequest req);
RefundResult refund(String txnId, Money amount);
}
class PaymentService {
PaymentResult pay(PaymentRequest req) {
// Idempotency: same key = same result, dobara charge NAHI
var existing = repo.findByIdempotencyKey(req.getIdempotencyKey());
if (existing.isPresent()) return existing.get().toResult();
var provider = providerFactory.select(req.getMethod());
var result = provider.charge(req);
repo.save(req.getIdempotencyKey(), result);
return result;
}
}- Idempotency key sabse important — retry par double charge kabhi nahi
- PaymentProvider interface se multiple gateways plug karo
- Status ek state machine hai; webhooks async aur duplicate aate hain
Client har payment attempt ke liye ek UNIQUE key generate karta hai (aksar orderId + attempt). Server us key ko result ke saath store karta hai. Wahi key dobara aaye to STORED result return hota hai, naya charge nahi.
Ek subtle case: request beech mein hai aur wahi key dobara aa gayi. Iske liye key ko "IN_PROGRESS" mark karo aur duplicate ko wait ya reject karao — warna do parallel charges ho sakte hain.
enum IdempotencyStatus { IN_PROGRESS, COMPLETED }
PaymentResult pay(PaymentRequest req) {
var claimed = repo.claim(req.getIdempotencyKey(), IN_PROGRESS);
if (!claimed) {
var existing = repo.find(req.getIdempotencyKey());
if (existing.status() == COMPLETED) return existing.result();
throw new PaymentInProgressException(); // duplicate parallel request
}
// ... actual charge, phir COMPLETED mark
}Payment async hoti hai — provider baad mein webhook bhejta hai. Webhook AT-LEAST-ONCE hota hai, matlab same event kai baar aa sakta hai. Har webhook ka eventId store karo aur duplicate ignore karo.
Webhook kabhi kabhi aata hi nahi (network issue). Isliye RECONCILIATION job zaroori hai — PENDING payments ko periodically provider se query karke status update karo. Ye batana production maturity dikhata hai.