Payment System: idempotency ও double-entry ledger
Payment System and Double-Entry Ledger
এই পাঠের সূচিপত্র
এই পাঠে যা শিখবেন
- Payment state machine ও unknown outcome বোঝা
- Idempotency, verified webhook ও reconciliation জোড়া লাগানো
- Double-entry ledger invariant চিনতে পারা
সমস্যা দিয়ে শুরু
Customer pay চাপলেন, provider charge করল, কিন্তু Laravel response পাওয়ার আগে connection গেল। Retry করলে আবার charge হতে পারে; paid না করলে customer অভিযোগ করেন। Requirements: initiate payment, query status, verified asynchronous update, refund এবং audit। Card data নিজের database-এ রাখার প্রয়োজন প্রথম scope-এ নেই। Correctness-এর লক্ষ্য duplicate business charge কমানো এবং uncertain state reliableভাবে resolve করা, magical exactly-once network call নয়।
সহজ করে বুঝি
Payment attempt pending, succeeded, failed বা reconciliation-required state-এ থাকতে পারে। Stable idempotency key provider contract অনুযায়ী duplicate request সামলায়। Webhook signature authenticity যাচাই করে; event ID dedupe এবং monotonic/conditional transition ordering সামলায়। Double-entry ledger-এ প্রতিটি journal-এর debit ও credit সমান হয়; payment status row accounting ledger-এর বিকল্প নয়। Amount integer minor unit বা exact decimal-এ, currencyসহ রাখুন।
পরিচিত একটি উপমা
ব্যাংকে টাকা পাঠানোর receipt হারালে আবার টাকা পাঠানো প্রথম পদক্ষেপ নয়। Reference number দিয়ে প্রথম transfer-এর ফল খুঁজতে হয়। খাতায় এক account থেকে কমা এবং অন্য account-এ বাড়া মিললে অর্থের চলাচল audit করা যায়।
নকশাটি দেখুন
Diagram লোড হচ্ছে…
Diagram-এর text flow দেখুন
flowchart TD U[Payment request and stable key] --> A[Attempt record] A --> P[Provider request] P -->|Timeout| X[Unknown outcome] P --> W[Signed webhook] W --> D[Verify dedupe transition] X --> R[Reconciliation worker] R --> D D --> L[(Balanced ledger transaction)]
Attempt আগে durableভাবে চিহ্নিত হয়। Provider request-এর ফল timeout হলে unknown state থাকে। Signed webhook ও status query সেই operation resolve করে। Successful transition এবং ledger journal local transaction-এ consistentভাবে লিখুন। Duplicate webhook নতুন journal তৈরি করবে না। Provider call আর local commit এক distributed atomic unit নয়, তাই periodic reconciliation বাদ দেওয়া যায় না।
PHP ও Laravel-এ প্রয়োগ
Assumption: দিনে ৫০ হাজার payment, average প্রায় ০.৫৮ initiation QPS; webhook, retry এবং peak আলাদা যোগ হবে। POST /payment-attempts key নেবে; GET /payment-attempts/{id} owner-only। payment_attempts-এ operation_key unique, provider_ref, amount_minor, currency, status; provider_events-এ event_id unique; journals/entries accounting records। নিচের HTTP fragment-এর $operationKey durable persisted, same operation-এ stable। Endpoint ও credentials server-configured; real provider-specific contract দেখুন।
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.gateway.secret'))
->withHeaders(['Idempotency-Key' => $operationKey])
->connectTimeout(2)->timeout(8)
->post(config('services.gateway.create_url'), [
'amount' => $amountMinor,
'currency' => $currency,
'reference' => $attemptId,
]);
$response->throw();সুবিধা, সীমাবদ্ধতা ও trade-off
Provider idempotency retry safer করে কিন্তু retention, payload matching ও scope provider-specific। Webhook asynchronous, তাই UI pending state দেখাবে। Ledger audit শক্তিশালী করে, schema ও reconciliation complexity বাড়ায়। Refund original charge মুছে ফেলা নয়; আলাদা financial event। Client-visible immediate confirmation এবং reliable final state-এর মধ্যে latency trade-off পরিষ্কারভাবে জানাতে হবে।
সাধারণ ভুল
- Timeout মানেই charge হয়নি ধরা।
- Webhook payload signature যাচাইয়ের আগে paid করা।
- Refund-এ original ledger row delete করা।
আরও একটু গভীরে
Webhook processing raw payload ও signature verification provider SDK অনুযায়ী করুন। Event ordering guaranteed ধরে নেবেন না; payment object-এর current verified status query কাজে লাগতে পারে। External event persist করে দ্রুত acknowledgment দিন, heavy work queue-তে। Ledger posting unique business reference দিয়ে dedupe করুন; balanced entries এবং currency rules transaction-এ enforce করুন। Daily settlement report-এর সঙ্গে local records reconcile করলে silent mismatch ধরা পড়ে।
মনে রাখুন
Payment design uncertain outcome-কে স্বীকার করে; stable operation key, verified events, balanced ledger ও reconciliation মিলিয়ে correctness গড়ে।
নিজে চেষ্টা করুন
একই charge request দুইবার এবং webhook তিনবার এলে expected records লিখুন। Provider success-এর পরে local crash হলে recovery path আঁকুন। Balanced journal-এর debit/credit invariant নিয়ে database integration test design করুন।
নিজের বোঝা যাচাই করুন
1. Timeout হলে retry charge?
আগের result অজানা। Stable operation key ও provider status/reconciliation contract অনুসরণ করুন; নতুন charge হিসেবে blind retry নয়।
2. Idempotency key প্রতিবার নতুন?
একই business operation retry-এ stable থাকবে। নতুন key provider-এর কাছে নতুন operation হতে পারে।
3. Webhook একবার আসে?
ধরে নেওয়া যায় না। Duplicate এবং out-of-order handling দরকার; event receipt ও business transition আলাদাভাবে dedupe করুন।
4. Ledger কি payment status?
না। Status workflow state; ledger অর্থের accounting movement। দুইটি consistentভাবে সম্পর্কিত রাখতে হয়।
5. Refund কীভাবে লিখব?
নতুন compensating financial entry ও provider operation হিসেবে। Historical audit মুছে দিলে investigation ও balance অস্পষ্ট হয়।
আরও পড়ুন
এই পাঠের কুইজ
এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।
এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।
জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।
অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।