Tasks completed: 2/2 - Task 1: Payment model with FIFO allocation and void - Task 2: Payment APIs, outstanding report, and 29 passing tests Phase 2 (Subscriber and Billing Core) complete — 265/265 tests passing SUMMARY: .planning/phases/02-subscriber-and-billing-core/02-05-SUMMARY.md
8.4 KiB
8.4 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | duration | completed | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-subscriber-and-billing-core | 05 | payments |
|
|
|
|
|
|
|
|
8min | 2026-03-04 |
Phase 2 Plan 05: Payment System Summary
FIFO cash/bank payment recording with idempotency, partial/full/overpayment tracking, reversing-JE voids, and outstanding balance report — completing the ISP revenue cycle
Performance
- Duration: 8 min
- Started: 2026-03-04T15:44:52Z
- Completed: 2026-03-04T15:52:55Z
- Tasks: 2/2
- Files modified: 12
Accomplishments
- Payment model with FIFO allocation, idempotency key, void support, and journal entry links
- PaymentService: recordPayment() with FIFO, voidPayment() with reversing JE, getSubscriberPaymentHistory()
- OutstandingReportService showing who owes what — the core product value
- 7 REST API routes for payment recording, history, balance, void, and outstanding report
- 29 integration tests covering all payment scenarios (full/partial/overpayment, FIFO, idempotency, void, tenant isolation)
- Full regression: 265/265 tests pass
Task Commits
Each task was committed atomically:
- Task 1: Payment model + PaymentService with FIFO allocation -
6b91e67(feat) - Task 2: Payment APIs + outstanding report + tests -
bbfc9d6(feat)
Plan metadata: (created below)
Files Created/Modified
prisma/schema.prisma- Added PaymentMethod/PaymentStatus enums, Payment and PaymentAllocation models, relationssrc/lib/prisma-tenant.ts- Added "payment" and "paymentAllocation" to TENANT_SCOPED_MODELS and withTenantContext()prisma/migrations/20260304154606_add_payment_model/migration.sql- DB migration for Payment and PaymentAllocation tablessrc/lib/services/payment-service.ts- recordPayment() FIFO, voidPayment() reversing JE, getSubscriberPaymentHistory()src/lib/services/outstanding-report-service.ts- getOutstandingReport() with amount/date/status filterssrc/app/api/payments/route.ts- POST record payment, GET list paymentssrc/app/api/payments/[id]/route.ts- GET single payment detailsrc/app/api/payments/[id]/void/route.ts- POST void paymentsrc/app/api/subscribers/[id]/payments/route.ts- GET subscriber payment historysrc/app/api/subscribers/[id]/balance/route.ts- GET subscriber outstanding balancesrc/app/api/reports/outstanding/route.ts- GET outstanding balance reportsrc/lib/__tests__/payment.test.ts- 29 integration tests
Decisions Made
- PaymentAllocation as separate model: Enables per-invoice allocation queries and void recalculation. Each allocation is a row: paymentId + invoiceId + amount.
- FIFO by dueDate ASC: Oldest due date allocated first — matches standard ISP billing practice.
- Overpayment to subscriber.creditBalance: Same pattern as invoice.amountPaid — always updated atomically in same transaction as journal entry. CreditService from 02-04 is callable for future credit-to-invoice application.
- Void recalculation via subtraction: voidPayment() subtracts allocation.amount from invoice.amountPaid — safe even when multiple payments partially cover the same invoice.
- Outstanding report computed in JS: Prisma does not support computed fields in WHERE/ORDER BY. totalAmount-amountPaid computed in process after fetching invoices. Acceptable for ISP scale (hundreds, not millions of records).
- Test invoiceCounter for periodStart uniqueness: Invoice schema has @@unique([tenantId, subscriberId, periodStart]). Test helper uses a monotonic counter to generate unique periodStart per invoice, avoiding constraint violations when creating multiple invoices per subscriber in tests.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Fixed unique periodStart constraint in test helper
- Found during: Task 2 (first test run — 6 tests failed with PrismaClientKnownRequestError)
- Issue: Test
createInvoice()helper used a fixedperiodStart: new Date(Date.UTC(2026, 1, 1))for all invoices. Multiple invoices for the same subscriber violated@@unique([tenantId, subscriberId, periodStart]). - Fix: Added
invoiceCountervariable; eachcreateInvoice()call usesnew Date(Date.UTC(2020, 0, invoiceCounter))as unique periodStart. - Files modified:
src/lib/__tests__/payment.test.ts - Verification: All 29 tests pass after fix
- Committed in:
bbfc9d6(Task 2 commit)
Total deviations: 1 auto-fixed (Rule 1 - Bug in test helper) Impact on plan: Minimal. Test helper bug; no production code affected. Fix is a standard test-isolation technique.
Issues Encountered
None beyond the test helper bug documented above.
User Setup Required
None - no external service configuration required.
Next Phase Readiness
- Phase 2 (Subscriber and Billing Core) is complete: COA, JournalEntryService, Subscriber, BillingEngine, PaymentService all done.
- 265 total tests passing across all Phase 1 and Phase 2 plans.
- Phase 3 (Collections and Routing) can begin: collector routes, payment collection, zone-based assignment.
- Key context for Phase 3: collectors will use PaymentService.recordPayment() to record collected payments; subscriber.zone field is already on the schema.
Phase: 02-subscriber-and-billing-core Completed: 2026-03-04