diff --git a/plans/plans.go b/plans/plans.go index 68b3365..b0f0d8b 100644 --- a/plans/plans.go +++ b/plans/plans.go @@ -472,13 +472,13 @@ plans: custom_domains: false sla: false # hobby_yearly mirrors hobby exactly — same limits + features. Only the - # billing period and price differ ($90/yr ≈ $7.50/mo, ~17% off vs $9 x 12). + # billing period and price differ ($97.20/yr ≈ $8.10/mo, 10% off vs $9 x 12). # The webhook upgrades teams to the "hobby" tier regardless of which # cycle the user paid on; this variant exists only so the checkout # handler can pick the right Razorpay plan_id at subscribe time. hobby_yearly: display_name: "Hobby (yearly)" - price_monthly_cents: 9000 + price_monthly_cents: 9720 billing_period: "yearly" trial_days: 14 limits: @@ -525,10 +525,10 @@ plans: alerts: true custom_domains: true sla: false - # pro_yearly mirrors pro exactly. $490/yr ≈ $40.83/mo (~17% off $49 x 12). + # pro_yearly mirrors pro exactly. $529.20/yr ≈ $44.10/mo (10% off $49 x 12). pro_yearly: display_name: "Pro (yearly)" - price_monthly_cents: 49000 + price_monthly_cents: 52920 billing_period: "yearly" trial_days: 0 limits: @@ -575,10 +575,10 @@ plans: alerts: true custom_domains: true sla: true - # team_yearly mirrors team exactly. $1990/yr ≈ $165.83/mo (~17% off $199 x 12). + # team_yearly mirrors team exactly. $2149.20/yr ≈ $179.10/mo (10% off $199 x 12). team_yearly: display_name: "Team (yearly)" - price_monthly_cents: 199000 + price_monthly_cents: 214920 billing_period: "yearly" trial_days: 0 limits: diff --git a/plans/plans_test.go b/plans/plans_test.go index 24adbab..a19cd57 100644 --- a/plans/plans_test.go +++ b/plans/plans_test.go @@ -177,6 +177,25 @@ func TestYearlyPrices_DiscountedVsMonthlyTimesTwelve(t *testing.T) { } } +// TestYearlyDiscountIsExactly10Percent locks the yearly-discount contract: +// (yearly / 12) / monthly must equal 0.9 within a small tolerance for each +// of hobby/pro/team. Future price changes that accidentally drift the +// discount (e.g. forgetting to re-derive the yearly cents from the new +// monthly) will fail this test. +func TestYearlyDiscountIsExactly10Percent(t *testing.T) { + r := plans.Default() + const tolerance = 0.01 + for _, base := range []string{"hobby", "pro", "team"} { + monthly := float64(r.Get(base).PriceMonthly) + yearly := float64(r.Get(base + "_yearly").PriceMonthly) + require.Greater(t, monthly, 0.0, "%s monthly price must be > 0", base) + ratio := (yearly / 12.0) / monthly + assert.InDelta(t, 0.9, ratio, tolerance, + "%s_yearly effective monthly / %s monthly must be 0.9 (10%% off); got %.4f (yearly=%d, monthly=%d)", + base, base, ratio, int(yearly), int(monthly)) + } +} + func TestValidatePromotion_ValidCode_ReturnsPromotion(t *testing.T) { yaml := ` plans: