Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 1 | //! Cloudflare's subscriptions: what g1t pays each month whatever it uses |
| 2 | //! (Workers Paid, add-ons), read from Cloudflare each day. | |
| 3 | //! | |
| 4 | //! They are not on the billable-usage bill, so the costs statement and | |
| 5 | //! g1t's own spend add them as a fixed cost. Read with the bill's token | |
| 6 | //! (`CLOUDFLARE_BILLING_TOKEN`, Account: Billing Read) and kept in | |
| 7 | //! `cf_subscriptions`; until a read has worked, `CLOUDFLARE_FIXED_MONTHLY_MICROS` | |
| 8 | //! stands in as an estimate. | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 9 | //! |
| 10 | //! The same read says when the billing cycle starts (`current_period_start`): | |
| 11 | //! the day of the month every cycle starts on (`cycle`), which is when the | |
| 12 | //! usage bill's included amounts start again. Until it is read, | |
| 13 | //! `CLOUDFLARE_BILLING_DAY` says. | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 14 | |
| 15 | use g1t_contracts::billing::FixedCost; | |
| 16 | use g1t_contracts::time::rfc3339; | |
| 17 | use g1t_kit::now_ms; | |
| 18 | use serde::Deserialize; | |
| 19 | use serde_json::Value; | |
| 20 | use worker::Result; | |
| 21 | ||
| 22 | use crate::Billing; | |
| 23 | ||
| 24 | /// The states of a subscription that is paid for (or will be). | |
| 25 | const PAID: [&str; 4] = ["Paid", "Provisioned", "AwaitingPayment", "Trial"]; | |
| 26 | ||
| 27 | /// Each subscription that costs money, at what it comes to a month. | |
| 28 | pub(crate) fn monthly(body: &Value) -> Vec<FixedCost> { | |
| 29 | let mut out = Vec::new(); | |
| 30 | for s in body["result"].as_array().into_iter().flatten() { | |
| 31 | let state = s["state"].as_str().unwrap_or("Paid"); | |
| 32 | if !PAID.contains(&state) { | |
| 33 | continue; | |
| 34 | } | |
| 35 | let price = s["price"].as_f64().unwrap_or(0.0); | |
| 36 | let months = match s["frequency"].as_str().unwrap_or("monthly") { | |
| 37 | "weekly" => 12.0 / 52.0, | |
| 38 | "quarterly" => 3.0, | |
| 39 | "yearly" => 12.0, | |
| 40 | _ => 1.0, | |
| 41 | }; | |
| 42 | let monthly_micros = (price / months * 1_000_000.0).round() as i64; | |
| 43 | if monthly_micros <= 0 { | |
| 44 | continue; | |
| 45 | } | |
| 46 | let name = s["rate_plan"]["public_name"] | |
| 47 | .as_str() | |
| 48 | .or_else(|| s["product"]["name"].as_str()) | |
| 49 | .or_else(|| s["rate_plan"]["id"].as_str()) | |
| 50 | .unwrap_or("Subscription") | |
| 51 | .to_owned(); | |
| 52 | out.push(FixedCost { name, monthly_micros }); | |
| 53 | } | |
| 54 | out | |
| 55 | } | |
| 56 | ||
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 57 | /// When the current billing cycle started, from the first paid monthly |
| 58 | /// subscription that says (`current_period_start`), as YYYY-MM-DD. | |
| 59 | pub(crate) fn period_start(body: &Value) -> Option<String> { | |
| 60 | body["result"] | |
| 61 | .as_array()? | |
| 62 | .iter() | |
| 63 | .filter(|s| PAID.contains(&s["state"].as_str().unwrap_or("Paid"))) | |
| 64 | .filter(|s| s["frequency"].as_str().unwrap_or("monthly") == "monthly") | |
| 65 | .find_map(|s| s["current_period_start"].as_str().filter(|d| crate::cycle::anchor_of(d).is_some())) | |
| 66 | .map(|d| d[..10].to_owned()) | |
| 67 | } | |
| 68 | ||
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 69 | /// What the fixed cost is, and where the figure came from. |
| 70 | pub(crate) struct Fixed { | |
| 71 | pub monthly_micros: i64, | |
| 72 | /// `cloudflare` (read from Cloudflare) or `estimate` (the variable). | |
| 73 | pub source: &'static str, | |
| 74 | pub read_at: Option<String>, | |
| 75 | pub items: Vec<FixedCost>, | |
| 76 | } | |
| 77 | ||
| 78 | impl Billing { | |
| 79 | /// Reads the subscriptions and keeps them; an error is returned for the | |
| 80 | /// run to log, and the last read (or the estimate) stays. | |
| 81 | pub(crate) async fn read_subscriptions(&self, keeper: &crate::keeper::Keeper) -> Result<usize> { | |
| 82 | let body = keeper.subscriptions_body().await?; | |
| 83 | let items = monthly(&body); | |
| 84 | let total: i64 = items.iter().map(|i| i.monthly_micros).sum(); | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 85 | let cycle_start = period_start(&body); |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 86 | self.db |
| 87 | .prepare( | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 88 | "INSERT INTO cf_subscriptions (id, monthly_micros, detail, read_at, cycle_start) VALUES ('current', ?1, ?2, ?3, ?4) |
| 89 | ON CONFLICT (id) DO UPDATE SET monthly_micros = excluded.monthly_micros, detail = excluded.detail, read_at = excluded.read_at, | |
| 90 | cycle_start = COALESCE(excluded.cycle_start, cf_subscriptions.cycle_start)", | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 91 | ) |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 92 | .bind(&[ |
| 93 | (total as f64).into(), | |
| 94 | serde_json::to_string(&items)?.into(), | |
| 95 | rfc3339(now_ms()).into(), | |
| 96 | cycle_start.map_or(worker::wasm_bindgen::JsValue::NULL, |d| d.into()), | |
| 97 | ])? | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 98 | .run() |
| 99 | .await?; | |
| 100 | Ok(items.len()) | |
| 101 | } | |
| 102 | ||
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 103 | /// The day of the month Cloudflare's billing cycle starts on: from the |
| 104 | /// subscriptions as last read, else `CLOUDFLARE_BILLING_DAY`, else the 1st. | |
| 105 | pub(crate) async fn cycle_anchor(&self) -> Result<u32> { | |
| 106 | #[derive(Deserialize)] | |
| 107 | struct Row { | |
| 108 | cycle_start: Option<String>, | |
| 109 | } | |
| 110 | let read = self | |
| 111 | .db | |
| 112 | .prepare("SELECT cycle_start FROM cf_subscriptions WHERE id = 'current'") | |
| 113 | .first::<Row>(None) | |
| 114 | .await? | |
| 115 | .and_then(|r| r.cycle_start) | |
| 116 | .and_then(|d| crate::cycle::anchor_of(&d)); | |
| 117 | let configured = self.env.var("CLOUDFLARE_BILLING_DAY").ok().and_then(|v| v.to_string().trim().parse::<u32>().ok()).filter(|d| (1..=31).contains(d)); | |
| 118 | Ok(read.or(configured).unwrap_or(crate::cycle::DEFAULT_ANCHOR)) | |
| 119 | } | |
| 120 | ||
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 121 | /// Cloudflare's subscriptions as last read, else the estimate. |
| 122 | pub(crate) async fn fixed_monthly(&self, estimate: i64) -> Result<Fixed> { | |
| 123 | #[derive(Deserialize)] | |
| 124 | struct Row { | |
| 125 | monthly_micros: i64, | |
| 126 | detail: String, | |
| 127 | read_at: String, | |
| 128 | } | |
| 129 | let row = self | |
| 130 | .db | |
| 131 | .prepare("SELECT monthly_micros, detail, read_at FROM cf_subscriptions WHERE id = 'current'") | |
| 132 | .first::<Row>(None) | |
| 133 | .await?; | |
| 134 | Ok(match row { | |
| 135 | Some(row) => Fixed { | |
| 136 | monthly_micros: row.monthly_micros, | |
| 137 | source: "cloudflare", | |
| 138 | read_at: Some(row.read_at), | |
| 139 | items: serde_json::from_str(&row.detail).unwrap_or_default(), | |
| 140 | }, | |
| 141 | None => Fixed { monthly_micros: estimate, source: "estimate", read_at: None, items: Vec::new() }, | |
| 142 | }) | |
| 143 | } | |
| 144 | } | |
| 145 | ||
| 146 | #[cfg(test)] | |
| 147 | mod tests { | |
| 148 | use super::*; | |
| 149 | use serde_json::json; | |
| 150 | ||
| 151 | #[test] | |
| 152 | fn paid_subscriptions_come_to_a_month_each() { | |
| 153 | let body = json!({ "result": [ | |
| 154 | { "state": "Paid", "price": 5.0, "frequency": "monthly", "rate_plan": { "public_name": "Workers Paid" } }, | |
| 155 | { "state": "Paid", "price": 240.0, "frequency": "yearly", "product": { "name": "Zone Pro" } }, | |
| 156 | { "state": "Cancelled", "price": 20.0, "frequency": "monthly", "rate_plan": { "public_name": "Old" } }, | |
| 157 | { "state": "Paid", "price": 0.0, "frequency": "monthly", "rate_plan": { "public_name": "Free" } }, | |
| 158 | ] }); | |
| 159 | let items = monthly(&body); | |
| 160 | assert_eq!( | |
| 161 | items, | |
| 162 | vec![ | |
| 163 | FixedCost { name: "Workers Paid".into(), monthly_micros: 5_000_000 }, | |
| 164 | FixedCost { name: "Zone Pro".into(), monthly_micros: 20_000_000 }, | |
| 165 | ] | |
| 166 | ); | |
| 167 | } | |
| 168 | ||
| 169 | #[test] | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 170 | fn the_cycle_starts_when_the_monthly_subscription_renews() { |
| 171 | let body = json!({ "result": [ | |
| 172 | { "state": "Cancelled", "price": 5.0, "frequency": "monthly", "current_period_start": "2026-09-01T00:00:00Z" }, | |
| 173 | { "state": "Paid", "price": 240.0, "frequency": "yearly", "current_period_start": "2026-03-15T00:00:00Z" }, | |
| 174 | { "state": "Paid", "price": 5.0, "frequency": "monthly", "current_period_start": "2026-09-28T07:12:00Z" }, | |
| 175 | ] }); | |
| 176 | assert_eq!(period_start(&body).as_deref(), Some("2026-09-28")); | |
| 177 | assert_eq!(period_start(&json!({ "result": [{ "state": "Paid", "price": 5.0 }] })), None); | |
| 178 | } | |
| 179 | ||
| 180 | #[test] | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 181 | fn nothing_answered_is_nothing_paid() { |
| 182 | assert!(monthly(&json!({ "result": [] })).is_empty()); | |
| 183 | assert!(monthly(&json!({})).is_empty()); | |
| 184 | } | |
| 185 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.