System Design শেখো
সব কেস স্টাডি

Payment Processing (Stripe) ডিজাইন

15 মিনিটadvanced
এক নজরে
  • Payment processor merchant ও bank/card network-এর মাঝে বসে payment-কে একটা state machine হিসেবে চালায়।
  • Idempotency key, safe retry আর webhook দিয়ে অনিশ্চিত নেটওয়ার্ক ও third-party gateway সামলানো হয়।
  • PCI compliance-এর জন্য কাঁচা card data কখনো store হয় না; tokenization ব্যবহার হয়, আর reconciliation দিয়ে সব হিসাব মেলানো হয়।

Stripe-এর মতো একটা payment processor বানানো মানে দুই অনিশ্চিত জগতের মাঝে দাঁড়ানো: একদিকে হাজারো merchant, অন্যদিকে ধীর, কখনো timeout-করা bank ও card network। এখানে মূল চ্যালেঞ্জ technical নয় শুধু—এটা অনিশ্চয়তা সামলানো, যাতে একটাও double charge বা হারানো payment না হয়। চলো ডিজাইন করি।

১. সমস্যা বোঝা (Requirements)

Functional requirements:

  • Merchant একটা charge create করতে পারবে (amount, currency, payment method)।
  • Card/wallet/bank-এর মাধ্যমে payment authorize ও capture করা।
  • Refund, partial refund, dispute/chargeback handling।
  • Merchant-কে চূড়ান্ত ফলাফল জানানো (webhook)।

Non-functional requirements:

  • Correctness: কখনো double charge নয়, কখনো হারানো payment নয়।
  • Reliability: third-party gateway down/slow হলেও payment হারানো যাবে না।
  • Idempotency: যেকোনো retry নিরাপদ হতে হবে।
  • Security / PCI-DSS: কাঁচা card data নিরাপদে handle, scope সীমিত।
  • Eventual consistency মেনে নেওয়া: bank-এর উত্তর async, তাই চূড়ান্ত status পরে আসে।
সহজ উদাহরণ

ভাবো তুমি একটা কুরিয়ারে দামি পার্সেল পাঠালে কিন্তু confirmation SMS আসেনি। আবার পাঠাবে? না—আগে tracking number (idempotency key) দিয়ে check করবে আগেরটা গেছে কিনা। Payment-ও তেমন: "জানি না হয়েছে কিনা" অবস্থায় অন্ধভাবে আবার চার্জ করা মানে গ্রাহকের দুবার টাকা কাটা।

২. স্কেল আন্দাজ (Estimation)

Scale:
  Merchants           = 1,000,000
  Peak payments       = 10,000 TPS (Black Friday/সেল)
  Avg payments        = 1,000 TPS

Latency budget (charge API):
  Validate + risk     ~ 30 ms
  Gateway round-trip  ~ 500-2000 ms  (bank, আমাদের নিয়ন্ত্রণের বাইরে!)
  → তাই async + webhook মডেল লাগে, sync নয়

Storage:
  প্রতি payment record ~ 1 KB (state, attempts, metadata)
  10k TPS * 1 KB      = 10 MB/sec ≈ 850 GB/দিন (peak)
  Webhook delivery log + retry queue আলাদা

মূল শিক্ষা: latency-র বড় অংশ third-party bank-এর হাতে, যা আমরা নিয়ন্ত্রণ করতে পারি না। তাই সিস্টেমকে async ও fault-tolerant হতে হবে।

৩. API ডিজাইন

POST /v1/payment_intents
  Header: Idempotency-Key: "pi-key-7781"
  Body:   { amount, currency, payment_method, capture: auto|manual }
  → { id: "pi_abc", status: "requires_confirmation" }

POST /v1/payment_intents/{id}/confirm   → status এগোয়
POST /v1/payment_intents/{id}/capture   → authorized টাকা আদায়
POST /v1/refunds   { payment_intent, amount }

# Gateway → আমরা (inbound webhook)
POST /webhooks/gateway   { event, payment_ref, status, signature }

# আমরা → merchant (outbound webhook)
POST {merchant_url}   { type: "payment.succeeded", data: {...}, signature }

প্রতিটা mutating endpoint-এ Idempotency-Key ও প্রতিটা webhook-এ cryptographic signature লক্ষণীয়।

৪. ডেটা মডেল

Tableমূল ফিল্ডউদ্দেশ্য
payment_intentsid, merchant_id, amount, status, idempotency_key, created_atএকটা payment-এর জীবনচক্র (state machine)
payment_attemptsid, intent_id, gateway, gateway_ref, status, error_codeপ্রতিটা gateway call/retry
idempotencykey, intent_id, response, statusduplicate API call ধরা
webhook_eventsid, source, payload, signature, processedinbound webhook (dedup সহ)
webhook_deliveriesid, merchant_url, payload, attempts, next_retryoutbound delivery + retry
ledger_entriesentry_id, account, direction, amount, txn_iddouble-entry money movement

কেন strong consistency-র দিকে ঝোঁক? payment-এর state ও money movement-এ ভুল চলে না, তাই payment_intentsledger_entries SQL-এ atomic ভাবে রাখা হয়। তবে merchant-কে notification দেওয়া বা analytics—এগুলো eventually consistent হলেও চলে। তাই মূল truth strongly consistent, আর প্রান্তিক কাজ async।

৫. হাই-লেভেল ডিজাইন

একটা payment-এর যাত্রা:

  1. API layer: merchant payment_intent create করে। Idempotency check হয়।
  2. Risk / fraud engine: velocity, blacklist, ML score—সন্দেহজনক হলে block।
  3. Payment orchestrator: state machine চালায়, কোন gateway-তে যাবে ঠিক করে (routing—Visa/Mastercard/local bank)।
  4. Gateway adapter: নির্দিষ্ট bank/network-এর protocol-এ call করে। এই call timeout/fail হতে পারে।
  5. Result handling: sync response পেলে ভালো; নাহলে status processing-এ থাকে আর gateway পরে webhook দিয়ে চূড়ান্ত ফল জানায়।
  6. Ledger: payment succeed করলে double-entry entry লেখা হয় (merchant balance বাড়ে, fee কাটা হয়)।
  7. Outbound webhook: merchant-কে payment.succeeded জানানো হয়, retry queue দিয়ে নিশ্চিত delivery।
  8. Reconciliation: দিনশেষে gateway-র settlement file-এর সাথে আমাদের record মেলানো।

৬. গভীরে (Deep Dive)

State machine: payment-এর জীবনচক্র

Payment-কে একটা explicit state machine হিসেবে মডেল করা হয়, যাতে কোনো অবস্থা অস্পষ্ট না থাকে:

created → requires_confirmation → processing
processing → succeeded         (capture সফল)
processing → failed            (decline)
succeeded  → refunded | disputed

প্রতিটা transition নির্দিষ্ট ও log করা। সবচেয়ে কঠিন state হলো processing—আমরা gateway-কে বলেছি কিন্তু চূড়ান্ত উত্তর জানি না। এই uncertain state কখনোই "ধরে নিচ্ছি সফল" করা যাবে না; অপেক্ষা করতে হবে webhook বা reconciliation-এর।

Idempotency ও safe retry

Gateway call-এ timeout হলে আমরা জানি না charge হয়েছিল কিনা। অন্ধভাবে retry = double charge ঝুঁকি। দুই স্তরে সুরক্ষা:

  • আমাদের API-তে: merchant-এর idempotency key—একই key-তে একই payment_intent ফেরত, নতুন charge নয়।
  • Gateway-র দিকে: আমরাও gateway-কে আমাদের নিজস্ব idempotency reference পাঠাই, যাতে retry করলে gateway একই charge চেনে। তাই প্রতিটা retry-তে একই reference পাঠাতে হবে, নতুন নয়।

Retry policy: exponential backoff + jitter, এবং শুধু idempotent operation retry করা। চূড়ান্ত উত্তর না পেলে status processing-এ রেখে webhook/reconciliation-এর জন্য অপেক্ষা—অন্ধ retry নয়।

সাবধান

সবচেয়ে দামি bug: gateway-তে charge সফল হয়েছে কিন্তু response আসার আগেই network কেটে গেছে; তখন একই charge নতুন idempotency reference দিয়ে আবার পাঠালে গ্রাহকের দুবার টাকা কাটবে। কখনোই retry-তে নতুন idempotency reference generate করো না—reference টা request-এর সাথে একবার তৈরি করে সব attempt-এ একই রাখো। অনিশ্চিত হলে retry নয়, status query বা reconciliation।

Webhook: inbound ও outbound

Inbound (gateway → আমরা): bank async-ভাবে চূড়ান্ত ফল webhook-এ পাঠায়। ঝুঁকি: webhook duplicate বা out-of-order আসতে পারে। তাই—

  • প্রতিটা webhook-এর signature যাচাই করো (forgery ঠেকাতে)।
  • webhook_events-এ event_id দিয়ে dedup করো; একই event দুবার এলে দ্বিতীয়টা উপেক্ষা।
  • state machine-এ শুধু বৈধ transition apply করো (পুরনো/out-of-order event পিছিয়ে নিতে পারবে না)।

Outbound (আমরা → merchant): merchant-এর server down থাকতে পারে। তাই at-least-once delivery: retry queue + exponential backoff, কয়েক দিন পর্যন্ত চেষ্টা। merchant-কে নিজের প্রান্তে idempotent থাকতে বলা হয় (event_id দিয়ে dedup)। সাথে polling endpoint fallback হিসেবে।

PCI / Security ও tokenization

কাঁচা card number (PAN) যত কম জায়গায় থাকে, তত নিরাপদ ও PCI scope তত ছোট।

  • Card data সরাসরি একটা isolated vault-এ যায় (অনেক সময় client থেকে সরাসরি, আমাদের server bypass করে)।
  • Vault একটা token ফেরত দেয়; আমাদের পুরো system-এ শুধু এই token ঘোরে, আসল PAN নয়।
  • Encryption at rest ও in transit (TLS), কঠোর access control, audit log।

ফলে আমাদের main application PCI-DSS-এর সবচেয়ে কঠিন scope থেকে বেরিয়ে আসে—database breach হলেও কেবল token ফাঁস হয়, যা vault ছাড়া অর্থহীন।

৭. বটলনেক ও স্কেলিং

  • Third-party latency: bank ধীর/down—circuit breaker, timeout, এবং একাধিক gateway-তে fallback routing।
  • Webhook reliability: outbound delivery হলো বড় চ্যালেঞ্জ; dedicated durable queue (কয়েকদিনের retention), backoff, dead-letter queue।
  • Partition by merchant/account: ledger ও intent merchant_id দিয়ে shard করা যায়; কিন্তু money movement-এ cross-account atomicity দরকার।
  • Eventual consistency boundary: চূড়ান্ত money record strongly consistent, কিন্তু notification/analytics async—এই সীমা পরিষ্কার রাখা।
  • Reconciliation: দিনশেষে gateway settlement file-এর সাথে byte-by-byte মেলানো; mismatch = manual investigation queue।

৮. সারসংক্ষেপ

Payment processing মূলত অনিশ্চয়তা প্রকৌশল। মূল ধারণা চারটা: (১) প্রতিটা payment একটা explicit state machine, যেখানে processing মানে "এখনো নিশ্চিত নই"; (২) idempotency key (আমাদের ও gateway দুই দিকেই) দিয়ে safe retry ও double-charge প্রতিরোধ; (৩) webhook দিয়ে async চূড়ান্ত ফল, signature + dedup সহ; (৪) tokenization দিয়ে PCI scope কমানো। সবকিছুর নিচে reconciliation, যা সত্যের চূড়ান্ত মিলকরণ।

টিপস

ইন্টারভিউতে দেখাও তুমি বোঝো "timeout মানে failure নয়"। বেশিরভাগ candidate এই uncertain state ভুলে যায়। বলো: একই idempotency reference সব attempt-এ, processing state, webhook + reconciliation দিয়ে চূড়ান্তকরণ—তাহলেই বোঝা যায় তুমি real-world payment-এর কঠিন প্রান্তগুলো জানো।

মিনি কুইজ

1. Payment gateway-তে call করতে গিয়ে timeout হলে নিরাপদে retry করার পূর্বশর্ত কী?

2. Async payment-এর চূড়ান্ত ফলাফল merchant কীভাবে জানতে পারে?

3. PCI compliance-এর scope কমাতে কাঁচা card number-এর বদলে কী store করা হয়?