Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

01 — Product Overview (Payments)

StudyLyon payments: processing, refunds, reconciliation, receipts, invoice-credit sync.


1. Purpose

Capture every payment received from parents/guardians (and any payer) against fee invoices, issue receipts, track refunds, and keep invoice dues (paidAmount, status) in sync. Per PRD §8, payment-gateway settlement is out of scope (Phase 1) → payments are recorded either from a gateway response or manually, and reconciled by the accountant.

2. Business goals

  • Record a payment with full audit context (payer, gateway, amount, invoice).
  • Generate a human-readable receipt for every completed payment.
  • Track full/partial refunds against a payment.
  • Keep invoice paidAmount / status (PAID / PARTIAL) accurate.
  • Support reconciliation of asynchronous gateway outcomes.

3. User goals

  • Accountant: record a cash/cheque/transfer payment fast; see all payments and receipts; refund when required.
  • Receptionist: take a counter payment against an invoice and hand over a receipt.
  • Org Admin: monitor collection and reconcile gateway responses.
  • Parent (forward-looking): see payment status and download receipts (self-service endpoint does not exist yet).

4. Stakeholders

Accountants, receptionists, org admins, platform billing (via payments.reconcile), parents (read), external gateways (stripe, paypal, flutterwave, paystack, razorpay, cash, bank_transfer, cheque per PaymentGateway).

5. Why this exists

Manual ledgers and split tools make fee collection untraceable. This module centralizes payments, receipts, refunds and reconciliation, emitting PaymentProcessed / PaymentRefunded domain events for realtime + audit.

6. Dependencies

  • Invoices (fees module) — link and partial/paid status
  • EventBus (events module) → PaymentProcessed / PaymentRefunded events to WS tenant room; note: these events are NOT routed to any BullMQ queue in event-queue-map.ts
  • Storage/files (not used today; receipts are data rows, not PDFs)
  • RBAC perms exist (payments.read/process/refund/reconcile, receipts.read) but are not yet enforced server-side (controller is JwtAuthGuard only)

7. Success metrics

  • Payment → receipt latency < 1 s (synchronous, same request)
  • Invoice status converges to PAID/PARTIAL on every payment/refund
  • Zero orphan receipts (receipt always linked to a payment)
  • Refund over-payment impossible (blocked server-side)

8. Edge cases

  • Refund attempt on non-COMPLETED payment → 409
  • Refund amount exceeding payment → 409
  • Partial refund then further refund → PARTIALLY_REFUNDED then REFUNDED
  • Revenue link: payment with no invoiceId (off-invoice) → no invoice link
  • Gateway async failure → PATCH reconcile sets FAILED/PENDING (client must re-fetch)
  • Duplicate transactionReference → unique index violation (500 today; retry-safety gap)
  • Receipt number generated from an in-process counter — not concurrency-safe across multiple API replicas
  • Concurrent payments for the same invoice: no lock; sumByInvoice is aggregation at link time (order-dependent final status)
  • Dual payment models (see 12_Assumptions)

9. Assumptions

  • Money stored as floating-point Number (amount, fee, refundedAmount) — financial apps should prefer minor units; flagged as an improvement, not corrected here.
  • Mobile client is a forward-looking spec (PRD excludes native apps from Phase 1).
  • Receipts are database rows; PDF/printable receipt via reports module (planned).
  • Parent self-service payment flows are (forward-looking) — no /payments/me endpoint.
  • Reconciliation is manual (PATCH :transactionRef/reconcile with {status} body).

10. Glossary

TermMeaning
transactionReferenceUnique TXN-{timestamp}-{uuid8}
gatewayPaymentGateway enum incl. offline methods (cash/cheque/bank_transfer)
receiptNumberRCP-{timestamp}-{counter}
reconcileSet payment status from gateway outcome string ("success"→COMPLETED, "failed"→FAILED, else PENDING)
InvoiceStatusPAID / PARTIAL / (per fees, incl. PENDING)