A line of credit billing cycle is fundamentally different from an installment loan. Understanding this structure is critical for migration because Peach must initialize balance buckets correctly at cutover — and post-migration, these mechanics govern how balances move, when interest accrues, and when a borrower becomes overdue.
This page explains how LOC billing cycles work. To run a migration, see the LOC migration procedure.
Every LOC billing cycle is divided into periods defined by four key dates:
| Date | What happens |
|---|---|
| startDate | Period begins. New balance tracking starts. |
| endDate | Period ends. Last day of the billing period. |
| statementDate | Statement generated. Always endDate + 1. Non-due balances move to due. |
| dueDate | Payment deadline. Full balance must be paid by this date for grace (if applicable). |
| dueDate + 1 | Enforcement. Unpaid due amounts move to overdue. Grace eligibility checked. |
In Peach, statementDate must always equal endDate + 1 day. This is a hard validation rule — if they don't match, migration will fail with "Period statement date should be one day after end date."
Unlike installment loans where a single payment obligation comes due on one date, LOCs move balances through buckets on specific dates.
Between startDate and statementDate, all accrued interest and posted transactions sit in the non-due bucket. On statementDate, the system moves the remaining non-due balance to the due bucket. The amount moved equals the remainingAmount from the obligation for that period.
During this step, outstanding non-due interest is truncated to the nearest cent. Any fractional cents are moved to a forgone_interest_rounding bucket and are not recovered. This is a key difference from systems that round interest at a different cadence.
After statementDate, the balance sits in the due bucket. For LOCs, this is typically 2–3 weeks (dueDate - statementDate + 1 days). On dueDate + 1, any remaining due balance moves to overdue.
| Loan type | Non-due → due | Time in "due" | Due → overdue |
|---|---|---|---|
| Line of credit | Statement date | 2–3 weeks | Due date + 1 |
| Installment | Due date | 1 day | Due date + 1 |
This is the most important structural difference between LOCs and installment loans. For installment loans, balances move from non-due to due on the due date and become overdue just one day later. For LOCs, there's a substantial window between "due" and "overdue."
An obligation tracks how much the borrower must pay in a specific billing period. It answers: "How much is owed, how much has been paid, and how much remains?"
The obligation amount is computed from expected payments — forecasts of what the borrower should pay based on the loan's schedule and fee structure:
obligationAmount = sum(expectedPayment.amount)
where expectedPayment.period_id = obligation.period_idExpected payments include: one PeriodicPayment (principal + interest), any number of DynamicFee entries (e.g., periodic servicing fees), and optionally one OriginationFee or DrawFee.
The obligation also tracks payment progress:
fulfilledAmount = paymentsAmount + serviceCreditsAmount
remainingAmount = obligationAmount - fulfilledAmountThe fulfilledAmount calculation respects the allowPrepayments loan type setting. If prepayments are allowed, a borrower's early payment can satisfy future obligations. If prepayments are not allowed, excess payments are tracked as overpaymentsAmount but don't reduce future remainingAmount.
The refresh_obligations() function recomputes all obligations whenever a relevant event occurs (payment, service credit, refund, etc.).
During migration, you must seed Peach's ledger with the correct balance buckets. Your legacy system's balances must be broken down into non-due, due, and overdue — per fee type (principal, interest, periodic fees, late fees, origination fees, draw fees, etc.). If these breakdowns are incorrect, post-migration DLM will move the wrong amounts at statement date, causing balance discrepancies.
You also need to provide the correct obligationAmount for the migration period (reconstructed from your legacy system's billing data). For overdue loans, you must include migratedDaysOverdue and migratedOverdueAmount so that Peach can correctly track the borrower's delinquency status going forward.
Day 1 (Aug 1): startDate — Period begins
├─ Interest accrues daily in non-due bucket
├─ Purchases post to non-due bucket
└─ Borrower can make payments (reduce non-due)
Day 31 (Aug 31): endDate — Period ends
Day 32 (Sep 1): statementDate — Statement generated
├─ Non-due balance moves to due bucket
├─ Interest truncated (fractional cents → forgone_interest_rounding)
├─ Obligation sealed (obligationAmount set)
└─ Statement mailed/emailed to borrower
Days 32–53: Due period (balance in "due" for ~3 weeks)
├─ Borrower makes payments (reduce due)
└─ Grace: payments effectiveDate → statementDate if eligible
Day 53 (Sep 22): dueDate — Payment deadline
└─ Last day to pay for grace eligibility
Day 54 (Sep 23): dueDate + 1 — Enforcement
├─ Remaining due balance moves to overdue
├─ Grace eligibility checked (revoke or reinstate)
├─ loan_overdue event fires (if overdue)
├─ Late fees may be charged
└─ Acceleration/charge-off threshold checked- LOC migration procedure — Apply these mechanics when seeding balances.
- How grace periods work for lines of credit — How grace interacts with the balance-movement timeline.
- How LOC draws work — How draws carry the balances that move through these buckets.