Skip to main content

max / makenotwork

6.7 KB · 171 lines History Blame Raw
1 //! SyncKit developer billing: pricing formula and constants.
2 //!
3 //! Two modes:
4 //!
5 //! bulk — price = storage_gb_cap × $0.03
6 //! per_key — price = key_cap × gb_per_key × $0.03
7 //!
8 //! Both are pure GB-based pricing. Egress and ingress are absorbed by the
9 //! storage rate's ~2× margin against Cloudflare R2 ($0.015/GB) where SyncKit
10 //! blobs are hosted.
11 //!
12 //! Invoices are floored at a Stripe-fee-cover threshold so we never lose money
13 //! on a transaction. See `BASE_FLOOR_CENTS` for the math.
14
15 /// Storage rate in cents per GB per month. Calibrated to ~2× R2 cost
16 /// ($0.015/GB storage, $0 egress on R2). The 2× margin spread absorbs any
17 /// ingress/egress cost variance, so we don't need a separate egress price.
18 pub const STORAGE_RATE_CENTS_PER_GB: f64 = 3.0;
19
20 /// Stripe-fee-cover floor in cents. Stripe charges 2.9% + $0.30 per
21 /// successful charge. We pick the smallest invoice `F` (cents) such that the
22 /// remainder after Stripe fees is non-negative:
23 ///
24 /// F × (1 − 0.029) − 30 ≥ 0 ⇒ F ≥ 30 / 0.971 ⇒ F ≥ 30.9¢
25 ///
26 /// Round up to 31¢. At the floor, MNW nets ~$0 — covered, not profitable.
27 pub const BASE_FLOOR_CENTS: i64 = 31;
28
29 /// Warning thresholds (percent of storage cap). Matches CHECK constraint on
30 /// `sync_app_usage_current.last_warning_pct`. Only storage is enforced, so
31 /// these thresholds apply to storage usage only.
32 pub const WARNING_THRESHOLDS_PCT: &[i16] = &[75, 90, 100];
33
34 /// Compute the monthly Stripe invoice amount in cents for a given knob set.
35 ///
36 /// In bulk mode: `storage_gb_cap` is set, others are `None`.
37 /// In per_key mode: `key_cap` and `gb_per_key` are set, `storage_gb_cap` is `None`.
38 ///
39 /// Floors at `BASE_FLOOR_CENTS` so we never invoice below the Stripe-fee
40 /// break-even amount.
41 pub fn monthly_price_cents(
42 enforcement_mode: &str,
43 storage_gb_cap: Option<u32>,
44 key_cap: Option<u32>,
45 gb_per_key: Option<u32>,
46 ) -> i64 {
47 let gb: f64 = match enforcement_mode {
48 "bulk" => storage_gb_cap.map(f64::from).unwrap_or(0.0),
49 "per_key" => {
50 let k = key_cap.map(f64::from).unwrap_or(0.0);
51 let g = gb_per_key.map(f64::from).unwrap_or(0.0);
52 k * g
53 }
54 _ => 0.0,
55 };
56 let raw = (gb * STORAGE_RATE_CENTS_PER_GB).ceil() as i64;
57 raw.max(BASE_FLOOR_CENTS)
58 }
59
60 /// Storage cap in bytes for the given GB cap.
61 pub fn storage_cap_bytes(storage_gb: u32) -> i64 {
62 i64::from(storage_gb) * 1024 * 1024 * 1024
63 }
64
65 #[cfg(test)]
66 mod tests {
67 use super::*;
68
69 #[test]
70 fn bulk_mode_pricing() {
71 // 100 GB bulk → 100 × 3 = 300 cents.
72 assert_eq!(monthly_price_cents("bulk", Some(100), None, None), 300);
73 // 1000 GB → $30.
74 assert_eq!(monthly_price_cents("bulk", Some(1000), None, None), 3000);
75 }
76
77 #[test]
78 fn per_key_mode_pricing() {
79 // 50 keys × 2 GB = 100 GB equivalent → 300 cents. Matches 100 GB bulk.
80 assert_eq!(monthly_price_cents("per_key", None, Some(50), Some(2)), 300);
81 // 1000 keys × 1 GB → $30.
82 assert_eq!(monthly_price_cents("per_key", None, Some(1000), Some(1)), 3000);
83 }
84
85 #[test]
86 fn floor_kicks_in_for_small_accounts() {
87 // 1 GB bulk → 3¢ raw, floored to 31¢.
88 assert_eq!(monthly_price_cents("bulk", Some(1), None, None), 31);
89 // 10 GB → 30¢, also floored to 31¢ (one cent short).
90 assert_eq!(monthly_price_cents("bulk", Some(10), None, None), 31);
91 // 11 GB → 33¢, above floor.
92 assert_eq!(monthly_price_cents("bulk", Some(11), None, None), 33);
93 // 1 key × 1 GB → 3¢ raw, floored.
94 assert_eq!(monthly_price_cents("per_key", None, Some(1), Some(1)), 31);
95 }
96
97 #[test]
98 fn heavy_workload_pricing() {
99 // 10 TB bulk → 10240 × 3 = 30720¢ = $307.20.
100 assert_eq!(monthly_price_cents("bulk", Some(10_240), None, None), 30_720);
101 // 10k keys × 1 GB → same.
102 assert_eq!(monthly_price_cents("per_key", None, Some(10_000), Some(1)), 30_000);
103 }
104
105 #[test]
106 fn missing_knobs_drop_to_floor() {
107 // Mode is set but no knobs provided — should hit the floor.
108 assert_eq!(monthly_price_cents("bulk", None, None, None), BASE_FLOOR_CENTS);
109 assert_eq!(monthly_price_cents("per_key", None, None, None), BASE_FLOOR_CENTS);
110 }
111
112 #[test]
113 fn unknown_mode_drops_to_floor() {
114 // Defensive: an unrecognized mode shouldn't blow up; it lands at the floor.
115 assert_eq!(monthly_price_cents("unknown", Some(100), None, None), BASE_FLOOR_CENTS);
116 }
117
118 #[test]
119 fn floor_amount_covers_stripe_fee() {
120 // 31¢ × 0.971 = 30.10¢, minus 30¢ fixed fee = 0.10¢ net. Verifies the
121 // documented math: the floor covers Stripe's fee with ~0 margin.
122 let net = (BASE_FLOOR_CENTS as f64) * 0.971 - 30.0;
123 assert!(net >= 0.0, "floor must net ≥ 0 after Stripe fees, got {net}");
124 assert!(net < 1.0, "floor should be tight, not overshoot — got {net}");
125 }
126
127 #[test]
128 fn storage_cap_in_bytes() {
129 assert_eq!(storage_cap_bytes(10), 10 * 1024 * 1024 * 1024);
130 }
131
132 // ── Edge cases (test-fuzz) ──
133
134 #[test]
135 fn pricing_at_u32_max_does_not_panic() {
136 // u32::MAX GB × 3¢ ≈ 1.3e10 cents, fits in i64. The cast must not panic.
137 let p = monthly_price_cents("bulk", Some(u32::MAX), None, None);
138 assert!(p > 0, "huge price should be positive, got {p}");
139 }
140
141 #[test]
142 fn per_key_pricing_at_u32_max_saturates_cleanly() {
143 // u32::MAX × u32::MAX overflows f64 precision but Rust's f64-as-i64 cast
144 // saturates at i64::MAX rather than UB. Must not panic.
145 let p = monthly_price_cents("per_key", None, Some(u32::MAX), Some(u32::MAX));
146 assert!(p > 0, "saturated price should still be positive, got {p}");
147 }
148
149 #[test]
150 fn storage_cap_at_u32_max_fits_in_i64() {
151 // u32::MAX × 2^30 = ~4.6e18, well under i64::MAX (~9.2e18).
152 let bytes = storage_cap_bytes(u32::MAX);
153 assert!(bytes > 0, "u32::MAX GB should produce a positive i64");
154 assert_eq!(bytes, (u32::MAX as i64) * 1024 * 1024 * 1024);
155 }
156
157 #[test]
158 fn bulk_with_zero_gb_drops_to_floor() {
159 // Defensive: validate_knobs rejects gb=0 at the route layer, but the
160 // pure function should still produce the floor rather than 0.
161 assert_eq!(monthly_price_cents("bulk", Some(0), None, None), BASE_FLOOR_CENTS);
162 }
163
164 #[test]
165 fn per_key_one_dimension_zero_drops_to_floor() {
166 // If only one of key_cap/gb_per_key is 0, the product is 0 → floor.
167 assert_eq!(monthly_price_cents("per_key", None, Some(0), Some(10)), BASE_FLOOR_CENTS);
168 assert_eq!(monthly_price_cents("per_key", None, Some(10), Some(0)), BASE_FLOOR_CENTS);
169 }
170 }
171