Proration (a pure class in @duro/billing) computes the fair number.
The intuition
A period is a span of time the customer already paid for. Halfway through a month on a ₦5,000 plan, they’ve consumed ₦2,500 of value and have ₦2,500 of unused credit. If they switch to a ₦10,000 plan, the new plan’s unused portion costs ₦5,000. The difference is what changes hands.The computation
Proration.changePlan works in daily rates so any interval behaves consistently:
dailyRate(amount, periodDays) = amount / periodDaysunusedCredit = daysRemaining × oldDailyRatenewCharge = daysRemaining × newDailyRateresult = newCharge − unusedCredit
{ amount, kind } where kind is 'charge', 'credit', or 'none' — so the caller never has to interpret a sign.
| Change | daysRemaining | Result |
|---|---|---|
| ₦5k → ₦10k, half a month left | 15 / 30 | charge ₦2,500 |
| ₦10k → ₦5k, half a month left | 15 / 30 | credit ₦2,500 |
| same price | any | none |
Where the credit goes
An upgrade’s charge can be collected immediately or rolled into the next invoice; a downgrade’s credit lands on the subscription’sbalance field (integer kobo), which is netted against the next renewal. Money owed to the customer never disappears into a rounding gap — it’s a real balance carried on the row.
Proration is pure and unit-tested with the exact figures above (₦400k charge / ₦600k credit cases verified live during the build). Like the rest of
@duro/billing, it touches no database — the SubscriptionService feeds it amounts and periods and stores what it returns.