পেমেন্ট দুই দিকে: গ্রাহক থেকে (রসিদ) এবং সরবরাহকারীকে (পেআউট)। চ্যানেল: গেটওয়ে, ব্যাঙ্ক ওয়্যার, নগদ, কাস্টমার ক্রেডিট, ভাউচার। প্রতি পেমেন্ট: idempotency, JE পোস্ট, রিকনসিলিয়েশন মার্ক।
Chapter 5.4 — Payments & Receipts
1. Purpose
This chapter defines the money-in and money-out modules: how customer payments are captured (cash, card, bank transfer, gateway, wallet credit), how supplier payouts are made, and how each event posts to the ledger. Reconciliation against bank statements is covered in Chapter 5.5; this chapter focuses on the in-platform event itself.
2. Why it matters
Payments are the only path by which AR/AP balances clear. Modelling them sloppily leads to: - Unapplied cash (received but not matched to invoice) - Double-applied receipts - FX gain/loss landing in the wrong account - Cash-drawer drift at retail branches - Failed payout reversals leaving phantom AP
3. Industry relevance
travoBooks handles the full payment-stack travel agencies need: walk-in cash, branch terminals, online gateway, corporate wire transfers, BSP wires (out), supplier-direct payouts (out), and customer wallet (internal credit).
4. Compliance considerations
- PCI DSS — gateway tokens only; no PAN storage; SAQ-A scope.
- AML / KYC — large cash receipts trigger threshold reporting.
- IFRS 9 — FX revaluation rules apply at receipt.
- Local cash-handling regulations — daily reconciliation, dual-control for large cash.
5. Business logic
5.1 Payment record (money-in)
| Field | Notes |
|---|---|
payment_id |
PK |
partner_id |
Tenant |
customer_id |
Payer |
payment_type |
cash, card, bank_transfer, gateway, wallet_credit, cheque |
gateway_provider |
stripe, sslcommerz, razorpay, manual, etc. |
gateway_transaction_id |
Provider reference |
transaction_currency |
|
transaction_amount |
Gross amount received |
fx_rate |
If non-functional |
functional_amount |
Translated |
received_at |
Effective receipt date |
cash_drawer_id |
If cash |
bank_account_id |
If wire/gateway |
received_by_user_id |
Cashier/agent |
state |
pending, cleared, failed, reversed, partial_refunded, refunded |
applied_amount |
Sum applied to invoices |
unapplied_amount |
Sitting as customer credit |
notes |
5.2 Receipt application
A payment can be applied to one or many invoices. The application table payment_invoice_applications links payment → invoice with applied_amount. Invoice's paid_amount and state flag (Paid / PartiallyPaid) recompute on each application.
Application order: - Oldest invoice first (default policy) - Explicit selection by accountant - Customer-instructed allocation (per remittance advice)
Unapplied amount becomes customer credit balance — usable on future invoices.
5.3 Receipt JE
For a BDT 100,000 cash receipt against Beta Corp:
Debit 1001 Cash on Hand 100,000 cash_drawer = DHK-01
Credit 1101 AR — Beta Corp 100,000 customer = beta_id
For a USD 5,000 wire receipt where functional currency is BDT (rate at receipt: 1 USD = 113):
Debit 1011 Bank — USD Account 5,000 USD fx_rate = 113.00; functional = 565,000 BDT
Credit 1101 AR — Beta Corp 5,000 USD functional = ? (depends on invoice's original FX rate)
If the invoice was raised at FX 110, the AR was 550,000 BDT functional. Receipt of 565,000 BDT functional creates a realised FX gain of 15,000 BDT posted to 4091 — Realised FX Gain.
5.4 Payout record (money-out)
| Field | Notes |
|---|---|
payout_id |
PK |
partner_id |
Tenant |
supplier_id |
Beneficiary |
payout_type |
bank_wire, bsp_wire, manual_card, cheque, gateway_payout |
currency |
|
amount |
|
state |
draft, pending_approval, approved, sent, cleared, failed, reversed |
bank_account_id |
Source |
supplier_bank_details_id |
Destination |
requested_by_user_id |
Maker |
approved_by_user_id |
Checker |
sent_at / cleared_at |
|
payment_file_id |
If batch file generated |
applied_invoices |
M-to-M with supplier invoices |
je_id |
5.5 Payout JE
Wire to Hotelbeds EUR 10,000 (functional BDT, rate 132 at payout):
Debit 2003 AP — Hotelbeds 10,000 EUR (1,320,000 BDT functional)
Credit 1012 Bank — EUR Account 10,000 EUR (1,320,000 BDT functional)
(Plus FX gain/loss if invoice was booked at different rate.)
5.6 Wallet / customer credit
Some refunds, overpayments, or promotional credits accumulate as customer credit balance. This is a liability (2105 — Customer Credit Liability) until used or expired.
Application: at invoice time, credit balance may be auto-applied per partner config.
5.7 BSP weekly wire
The BSP-payable account (2011) is settled by a single weekly/semi-monthly wire to the BSP clearing bank account. The wire posts:
Debit 2011 BSP Payable X
Credit 1013 Bank — BSP Settlement A/c X
Wire amount is determined by the BSP billing statement (imported per Ch 11.3). Discrepancies between our ledger and BSP statement trigger a reconciliation workflow.
5.8 Failed / reversed payments
Gateway chargebacks, NSF cheques, bank reversals are first-class events. They post:
Debit 1101 AR — Customer X (restoring AR)
Credit 1001/1011/etc X (reversing cash)
+ chargeback fee handling
The original payment record is not deleted — it's marked state = reversed with a reversed_by_payment_id link.
6. Inputs → processing → outputs
Capture cash receipt
Input: {customer_id, amount, currency, cash_drawer_id, apply_to_invoices[]}
Processing:
1. Permission check (payments.create.partner).
2. Validate cash drawer is open + assigned to user.
3. Insert payment row (state = cleared for cash).
4. Apply to invoices in order or as specified.
5. Update invoice paid/state.
6. Post JE.
7. Update customer balance.
8. Update cash drawer running total.
9. Issue receipt PDF.
Output: payment_id, receipt PDF URL.
Initiate supplier payout
Input: {supplier_id, currency, amount, invoices_to_apply[], bank_account_id}
Processing:
1. Permission check (suppliers.payout.create.partner).
2. Validate supplier active, bank details on file, amount ≤ open AP.
3. Insert payout row (state = pending_approval if above threshold, else approved).
4. Optionally generate bank-format payment file (SWIFT MT103, ACH NACHA, local format).
5. On approval, mark sent.
6. Post JE (cleared once bank confirms; for now AP is reduced and an "in-transit" account is debited).
7. Reconciliation matches bank statement to clear in-transit.
Output: payout_id, file download (if batch), audit trail.
7. Module dependencies
| Direction | Module |
|---|---|
| Depends on | Customer / Supplier, Invoicing, JE Engine, FX, Bank Accounts, Cash Drawer module |
| Depended on by | Reconciliation, Reporting, Dunning |
8. Security & permissions
| Permission | Allows |
|---|---|
payments.create.partner / .branch:{id} |
Capture receipt |
payments.apply.partner |
Apply unapplied cash |
payments.reverse.partner |
Reverse payment (maker-checker) |
payouts.create.partner |
Initiate payout |
payouts.approve.partner |
Approve payout |
payouts.cancel.partner |
Cancel before send |
cash_drawer.open.branch:{id} |
Open drawer |
cash_drawer.close.branch:{id} |
Close drawer |
cash_drawer.reconcile.branch:{id} |
Daily recon |
9. Validation rules
| Code | Condition |
|---|---|
PAYMENT_AMOUNT_INVALID |
≤ 0 |
PAYMENT_CURRENCY_UNSUPPORTED |
Not in partner currencies |
PAYMENT_DRAWER_CLOSED |
Cash drawer not open |
PAYMENT_DRAWER_WRONG_USER |
Drawer not assigned to caller |
PAYMENT_APPLY_EXCEEDS |
Apply amount > unapplied + invoice open balance |
PAYMENT_GATEWAY_REJECTED |
|
PAYMENT_DUPLICATE |
Same gateway_transaction_id |
PAYMENT_FX_RATE_MISSING |
No rate for transaction date |
PAYMENT_PERIOD_CLOSED |
Receipt date in closed period |
PAYOUT_BANK_DETAILS_INVALID |
|
PAYOUT_INSUFFICIENT_AP |
|
PAYOUT_REQUIRES_APPROVAL |
|
PAYOUT_DUAL_CONTROL_VIOLATION |
Maker = Checker |
10. Error handling
- Gateway timeouts → poll provider for status before declaring failure (avoids customer dispute).
- Cash overage at drawer close → write to
2106 — Cash Overage Liabilityfor accountant resolution. - Cash shortage at drawer close → write to
5081 — Cash Shortage Expensewith audit & approval.
11. Real-world examples
A — Apply against multiple invoices
Beta Corp wires BDT 250,000. Three open invoices: INV-501 (90,000), INV-502 (110,000), INV-503 (75,000). Total open: 275,000. - Auto-apply oldest first: INV-501 fully (90k), INV-502 fully (110k), INV-503 partially (50k). - INV-503 remaining: 25,000. - One JE; three application rows.
B — Multi-currency receipt with FX gain
Beta wires USD 5,000 against invoice raised at FX 110 (550k BDT functional). Receipt FX 113 → 565k BDT received. Realised FX gain 15k.
C — Cash drawer end-of-day
Cashier closes drawer; system-computed cash = 145,000; physical count = 144,800. Shortage 200. - 200 posted to 5081 Cash Shortage Expense. - Cashier signs off; manager approves.
12. Step-by-step workflow
13. Database tables touched
| Table | Role |
|---|---|
payments |
Money-in records |
payment_invoice_applications |
Application matrix |
payouts |
Money-out records |
payout_supplier_invoice_applications |
|
cash_drawers |
State + running totals |
cash_drawer_sessions |
Open/close events |
bank_accounts |
Sources/destinations |
customer_credit_balance |
Unapplied money |
journal_entries / _lines |
|
audit_logs |
14. Future scalability
- Tokenised card-on-file for repeat corporate clients.
- Real-time bank API receipt-matching (Open Banking / PSD2).
- Multi-currency wallet with FX hedging hooks.
- Auto-payout scheduling for trusted suppliers (avoid manual approval loop on routine payouts under limit).
15. Common pitfalls
- ⚠️ Applying gross amount when fees were deducted. Gateway fees should be a separate expense line, not silently reduce invoice apply.
- ⚠️ Using receipt-date FX rate on a settled invoice. The AR side should reverse at the invoice's original rate; the difference is realised FX.
- ⚠️ Allowing same user to capture + reverse without dual control. Dual control essential for reversal.
- 🔒 Storing PANs. Never. Tokens only.
- ⚠️ Forgetting BSP wire timing. BSP wires are deadline-driven; miss = penalty.