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.
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. Like the rest of
@duro/billing, it touches no database — SubscriptionService.changePlan feeds it amounts and periods and, on a downgrade, adds the returned credit to the subscription’s balance field (integer kobo). Cancelling an active subscription mid-period credits the unused portion to the same balance via Proration.unusedCredit.