- Shipped
- May 28, 2026 at 9:01 PM UTC
- Author
- Kamo
- Commit
- 0c59054
Money movement (the core P1): - LoanPaymentPostingService: markSucceededAndPost / markFailed / markReturnedAndReverse / markDisbursementSucceeded / markDrawDisbursed. Allocation order locked: fees → interest → principal. Status auto-advances DELINQUENT ↔ ACTIVE ↔ PAID_OFF based on resulting balance. - StripeWebhookService is no longer log-only: payment_intent.succeeded / payment_failed / charge.refunded (ACH return) / dispute.created+closed / payout.paid+failed all advance the ledger via PostingService. - NachaReturnCode + RetryScheduler: NACHA-compliant R-code routing — R01/R09 retry up to 2× / 5 days apart, R07/R10/R29 revoke the mandate, exhausted retries flip LoanPaymentMethod → REQUIRES_REVERIFY. Commerce-vertical materialization: - **************** now creates Engagement (PERSONAL_LOANS, COMMITTED), Commitment (ACTIVE, with pricing+terms JSON snapshots), Order (AUTHORIZED, subtotal=principal), and threads their IDs back onto Loan.commitmentUid / engagementUid / originatingOrderUid in the same transaction. Cron jobs (real logic, not stubs): - CoolingOffAdvanceJob: enqueues a PENDING DISBURSEMENT_OUT payment for every PENDING_DISBURSEMENT loan whose cooling-off elapsed. - InterestAccrualJob: daily simple-interest accrual on principalBalance for ACTIVE + DELINQUENT loans. - StatementGenerationJob: installment per-due-date statement, LOC per-cycle statement with ADB approximation + annual fee assessment + auto-pay scheduling. - AutoDebitRunJob: fires PENDING auto-debits + DISBURSEMENT_OUT payouts + NACHA retries via StripeConnectService with idempotency keys. - ReconciliationJob: nightly re-sum of fees/principal/balance from the ledger; drift correction logged + audited. - ReminderDispatchJob: pulls SCHEDULED notifications past their scheduledFor and delivers via NotificationDispatchService. Notifications: - **************** real HTTP POST to EmailService (/api/email/transactional/send) and VOIPService (/api/voip/sms/send, /api/voip/voice/send), templateKey = "loan.{kind_lowercase}". Validation: - LoanProductValidationService: enforces APR ≤ state cap (per kind), prohibited-kind checks, min/max-principal sanity. Hooked into LoanProductService.create + update. Charge-off: - LoanChargeOffService.queue() lists D120 loans; .confirm() flips status to CHARGED_OFF and writes a CHARGE_OFF_RECORDED audit event. - LoanChargeOffController exposes **************** Audit browse: - LoanAuditEventController exposes ****************