[
{"name": "Stripe's example: 10 to 20 a month, halfway through a 30 day period", "args": [{"minor": 1000, "currency": "USD"}, {"minor": 2000, "currency": "USD"}, "2026-04-01", "2026-05-01", "2026-04-16", "half-up"], "expect": {"totalDays": 30, "remainingDays": 15, "credit": {"minor": -500, "currency": "USD"}, "charge": {"minor": 1000, "currency": "USD"}, "net": {"minor": 500, "currency": "USD"}}},
{"name": "upgrade 10 days into a 31 day January, half-up", "args": [{"minor": 999, "currency": "GBP"}, {"minor": 1999, "currency": "GBP"}, "2026-01-01", "2026-02-01", "2026-01-11", "half-up"], "expect": {"totalDays": 31, "remainingDays": 21, "credit": {"minor": -677, "currency": "GBP"}, "charge": {"minor": 1354, "currency": "GBP"}, "net": {"minor": 677, "currency": "GBP"}}},
{"name": "the same upgrade rounded down: both lines shrink, so the net moves", "args": [{"minor": 999, "currency": "GBP"}, {"minor": 1999, "currency": "GBP"}, "2026-01-01", "2026-02-01", "2026-01-11", "down"], "expect": {"totalDays": 31, "remainingDays": 21, "credit": {"minor": -676, "currency": "GBP"}, "charge": {"minor": 1354, "currency": "GBP"}, "net": {"minor": 678, "currency": "GBP"}}},
{"name": "the same upgrade rounded up: both lines grow away from zero", "args": [{"minor": 999, "currency": "GBP"}, {"minor": 1999, "currency": "GBP"}, "2026-01-01", "2026-02-01", "2026-01-11", "up"], "expect": {"totalDays": 31, "remainingDays": 21, "credit": {"minor": -677, "currency": "GBP"}, "charge": {"minor": 1355, "currency": "GBP"}, "net": {"minor": 678, "currency": "GBP"}}},
{"name": "a downgrade halfway through February leaves the customer in credit", "args": [{"minor": 5000, "currency": "GBP"}, {"minor": 2000, "currency": "GBP"}, "2026-02-01", "2026-03-01", "2026-02-15", "half-up"], "expect": {"totalDays": 28, "remainingDays": 14, "credit": {"minor": -2500, "currency": "GBP"}, "charge": {"minor": 1000, "currency": "GBP"}, "net": {"minor": -1500, "currency": "GBP"}}},
{"name": "a change on the first day of the period re-prices all of it", "args": [{"minor": 3000, "currency": "GBP"}, {"minor": 6000, "currency": "GBP"}, "2026-06-01", "2026-07-01", "2026-06-01", "half-up"], "expect": {"totalDays": 30, "remainingDays": 30, "credit": {"minor": -3000, "currency": "GBP"}, "charge": {"minor": 6000, "currency": "GBP"}, "net": {"minor": 3000, "currency": "GBP"}}},
{"name": "a change on the period end re-prices nothing", "args": [{"minor": 3000, "currency": "GBP"}, {"minor": 6000, "currency": "GBP"}, "2026-06-01", "2026-07-01", "2026-07-01", "half-up"], "expect": {"totalDays": 30, "remainingDays": 0, "credit": {"minor": 0, "currency": "GBP"}, "charge": {"minor": 0, "currency": "GBP"}, "net": {"minor": 0, "currency": "GBP"}}},
{"name": "an annual plan changed on 1 July of a leap year: 184 of 366 days remain", "args": [{"minor": 12000, "currency": "GBP"}, {"minor": 24000, "currency": "GBP"}, "2024-01-01", "2025-01-01", "2024-07-01", "half-up"], "expect": {"totalDays": 366, "remainingDays": 184, "credit": {"minor": -6033, "currency": "GBP"}, "charge": {"minor": 12066, "currency": "GBP"}, "net": {"minor": 6033, "currency": "GBP"}}},
{"name": "a half-penny credit rounds to even under half-even", "args": [{"minor": 5, "currency": "GBP"}, {"minor": 15, "currency": "GBP"}, "2026-01-01", "2026-01-03", "2026-01-02", "half-even"], "expect": {"totalDays": 2, "remainingDays": 1, "credit": {"minor": -2, "currency": "GBP"}, "charge": {"minor": 8, "currency": "GBP"}, "net": {"minor": 6, "currency": "GBP"}}},
{"name": "the same half-penny credit rounds away from zero under half-up", "args": [{"minor": 5, "currency": "GBP"}, {"minor": 15, "currency": "GBP"}, "2026-01-01", "2026-01-03", "2026-01-02", "half-up"], "expect": {"totalDays": 2, "remainingDays": 1, "credit": {"minor": -3, "currency": "GBP"}, "charge": {"minor": 8, "currency": "GBP"}, "net": {"minor": 5, "currency": "GBP"}}},
{"name": "yen has no minor unit, so the credit rounds to whole yen", "args": [{"minor": 1000, "currency": "JPY"}, {"minor": 3000, "currency": "JPY"}, "2026-04-01", "2026-05-01", "2026-04-21", "half-up"], "expect": {"totalDays": 30, "remainingDays": 10, "credit": {"minor": -333, "currency": "JPY"}, "charge": {"minor": 1000, "currency": "JPY"}, "net": {"minor": 667, "currency": "JPY"}}},
{"name": "moving to a free plan is a credit and no charge", "args": [{"minor": 4900, "currency": "GBP"}, {"minor": 0, "currency": "GBP"}, "2026-09-01", "2026-10-01", "2026-09-21", "half-up"], "expect": {"totalDays": 30, "remainingDays": 10, "credit": {"minor": -1633, "currency": "GBP"}, "charge": {"minor": 0, "currency": "GBP"}, "net": {"minor": -1633, "currency": "GBP"}}},
{"name": "prices in different currencies are an error", "args": [{"minor": 1000, "currency": "GBP"}, {"minor": 2000, "currency": "EUR"}, "2026-04-01", "2026-05-01", "2026-04-16", "half-up"], "expectError": "currency mismatch"},
{"name": "a change date before the period is an error", "args": [{"minor": 1000, "currency": "GBP"}, {"minor": 2000, "currency": "GBP"}, "2026-04-01", "2026-05-01", "2026-03-31", "half-up"], "expectError": "change date must be within the billing period"},
{"name": "a change date after the period end is an error", "args": [{"minor": 1000, "currency": "GBP"}, {"minor": 2000, "currency": "GBP"}, "2026-04-01", "2026-05-01", "2026-05-02", "half-up"], "expectError": "change date must be within the billing period"},
{"name": "an empty period is an error", "args": [{"minor": 1000, "currency": "GBP"}, {"minor": 2000, "currency": "GBP"}, "2026-04-01", "2026-04-01", "2026-04-01", "half-up"], "expectError": "period end must be after period start"},
{"name": "an unknown rounding mode is an error", "args": [{"minor": 1000, "currency": "GBP"}, {"minor": 2000, "currency": "GBP"}, "2026-04-01", "2026-05-01", "2026-04-16", "nearest"], "expectError": "unknown rounding mode"}
]