Add mid-cycle proration for plan changes

Go · Go · intermediate · greenfield

Proration for mid-cycle plan changes, in integer minor units throughout (no floats), with half-up rounding done in one place, `divRoundHalfUp`. Tests come in two groups: a table for the structural cases (nothing used, whole 28- and 31-day periods, a share that divides exactly) and a rounding table that covers rounding down, rounding up and both kinds of exact half, with the worked value next to each case. `go test ./internal/billing/` is green.

Go 1.22 subscription-billing service. Amounts are `int64` minor units (cents); plan prices are at most 1e9 minor units, so `amountMinor * usedDays` cannot overflow. Tests use only the standard `testing` package, in the same package as the code. Finance reviews the test tables, not the implementation.

Requirements

Files touched

--- internal/billing/prorate.go
+package billing
+
+// ProrateMinor returns the share of amountMinor that covers usedDays of a
+// billing period that is periodDays long, rounded half up to the minor unit.
+// Callers guarantee 0 <= amountMinor <= 1e9 and 0 <= usedDays <= periodDays,
+// with periodDays between 28 and 31.
+func ProrateMinor(amountMinor int64, usedDays, periodDays int) int64 {
+	return divRoundHalfUp(amountMinor*int64(usedDays), int64(periodDays))
+}
+
+// divRoundHalfUp divides num by den (num >= 0, den > 0) and rounds the
+// quotient half up.
+func divRoundHalfUp(num, den int64) int64 {
+	return (2*num + den) / (2 * den)
+}
+
--- internal/billing/prorate_test.go
+package billing
+
+import "testing"
+
+func TestProrateMinor(t *testing.T) {
+	tests := []struct {
+		name         string
+		amount       int64
+		used, period int
+		want         int64
+	}{
+		{"nothing used", 3100, 0, 31, 0},

Review this PR

Go practice