পাঠ্যক্রম খুলুন
অগ্রসর পর্যায়

Payment System: idempotency ও double-entry ledger

Payment System and Double-Entry Ledger

এই পাঠের কুইজ ↓৬ মিনিট পড়াPHP 8.3+ · Laravel 12
এই পাঠের সূচিপত্র

এই পাঠে যা শিখবেন

  • 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 লোড হচ্ছে…

Payment design uncertain outcome-কে স্বীকার করে; stable operation key, verified events, balanced ledger ও reconciliation মিলিয়ে correctness গড়ে।
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 দেখুন।

php
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 অস্পষ্ট হয়।

আরও পড়ুন

এই পাঠের কুইজ

এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।

এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।

জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।

সব বিষয় মিলিয়ে অনুশীলন করুন →

অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।