Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

Merge main into project-descriptions

syntaqxcommitted Parents37ddaa31b4e637Browse files
3 files+35−90/3 viewed
+1−1
179179 }
180180 about={
181181 on
182− ? `Billing period ${period}. One price for the workspace, never per seat.`
182+ ? `Usage month ${period}${subscription?.periodEnd ? `; the plan renews ${endsOn(subscription.periodEnd)}` : ""}. One price for the workspace, never per seat.`
183183 : "The forge is free: repositories, issues, pull requests, reviews, search and the audit log. Checks, workflows and deployments need the plan, or the trial."
184184 }
185185 aside={
+9−1
901901 ("subscription_data[metadata][workspace]", workspace.to_owned()),
902902 ("subscription_data[metadata][feature]", feature.to_owned()),
903903 ];
904− fields.extend(page_line(monthly_cents, format!("g1t {title} for {workspace}"), None, true));
904+ fields.extend(page_line(monthly_cents, plan_line_name(title, workspace), None, true));
905905 fields.extend(checkout_tax_fields(customer));
906906 if let Some(customer) = customer {
907907 fields.push(("customer", customer.to_owned()));
909909 fields
910910 }
911911
912+/// What a plan is called on Stripe's page and the receipts: "g1t plan for
913+/// acme", "g1t Deployments plan for acme"; never "g1t g1t".
914+pub(crate) fn plan_line_name(title: &str, workspace: &str) -> String {
915+ if title == "g1t" { format!("g1t plan for {workspace}") } else { format!("g1t {title} plan for {workspace}") }
916+}
917+
912918 /// A card check's page's fields: setup mode, nothing charged, so nothing
913919 /// to tax. Setup mode takes no line items and no amount. The billing
914920 /// address is asked for all the same, and copied onto the customer once
11961202 fn untaxed_requests_lose_only_automatic_tax() {
11971203 let body = form(&[("mode", "payment".to_owned()), ("automatic_tax[enabled]", "true".to_owned()), ("tax_id_collection[enabled]", "true".to_owned())]);
11981204 assert_eq!(without_automatic_tax(&body), form(&[("mode", "payment".to_owned()), ("tax_id_collection[enabled]", "true".to_owned())]));
1205+ assert_eq!(plan_line_name("g1t", "acme"), "g1t plan for acme");
1206+ assert_eq!(plan_line_name("Deployments", "acme"), "g1t Deployments plan for acme");
11991207 assert!(taxed_path("/checkout/sessions") && taxed_path("/invoices") && taxed_path("/subscriptions"));
12001208 // An enrollment converting to the plan turns tax on by updating it.
12011209 assert!(taxed_path("/subscriptions/sub_1") && taxed_path("/subscriptions/sub_1?expand[]=latest_invoice"));
+25−7
6464 }
6565 }
6666
67+/// A value to bind, or SQL NULL. `Option::into` gives JavaScript
68+/// `undefined` for None, which D1 refuses ("Type 'undefined' not
69+/// supported"): a customer without a card, or a replay that finished.
70+fn or_null<T: Into<worker::wasm_bindgen::JsValue>>(value: Option<T>) -> worker::wasm_bindgen::JsValue {
71+ value.map_or(worker::wasm_bindgen::JsValue::NULL, Into::into)
72+}
73+
6774 impl Billing {
6875 /// The workspace's saved card as last synced. Stripe is asked only when
6976 /// it never was, or g1t changed the card since; a Stripe that does not
130137 )
131138 .bind(&[
132139 customer.into(),
133− card.as_ref().map(|c| c.brand.clone()).into(),
134− card.as_ref().map(|c| c.last4.clone()).into(),
135− card.as_ref().map(|c| c.exp_month as f64).into(),
136− card.as_ref().map(|c| c.exp_year as f64).into(),
140+ or_null(card.as_ref().map(|c| c.brand.clone())),
141+ or_null(card.as_ref().map(|c| c.last4.clone())),
142+ or_null(card.as_ref().map(|c| c.exp_month as f64)),
143+ or_null(card.as_ref().map(|c| c.exp_year as f64)),
137144 rfc3339(now_ms()).into(),
138145 ])?
139146 .run()
249256 .bind(&[
250257 mode.into(), (through as f64).into(), rfc3339(now_ms()).into(),
251258 crate::optional(if complete { None } else { after.as_deref() }),
252− (!complete).then_some(since as f64).into(),
253− (!complete).then_some(until as f64).into(),
254− if complete { None } else { first_failed.map(|v| v as f64) }.into(),
259+ or_null((!complete).then_some(since as f64)),
260+ or_null((!complete).then_some(until as f64)),
261+ or_null(if complete { None } else { first_failed.map(|v| v as f64) }),
255262 ])?
256263 .run()
257264 .await?;
354361
355362 #[cfg(test)]
356363 mod tests {
364+
365+ #[test]
366+ fn no_binding_turns_none_into_undefined() {
367+ // D1 refuses `undefined`; every optional value goes through or_null.
368+ let source = include_str!("stripe_sync.rs");
369+ let body = source.split("#[cfg(test)]").next().unwrap();
370+ for line in body.lines().filter(|l| l.trim_end().ends_with(".into(),")) {
371+ assert!(!line.contains("then_some(") && !line.contains(".map(|"), "{line}");
372+ }
373+ }
374+
357375 use super::*;
358376
359377 #[test]

Comments

Sign in to comment.