collab: Remove Stripe code (#36275)

Marshall Bowers created

This PR removes the code for integrating with Stripe from Collab.

All of these concerns are now handled by Cloud.

Release Notes:

- N/A

Change summary

Cargo.lock                                            | 159 ---
Cargo.toml                                            |  14 
crates/collab/Cargo.toml                              |   5 
crates/collab/k8s/collab.template.yml                 |   6 
crates/collab/src/api.rs                              |   1 
crates/collab/src/api/billing.rs                      |  59 -
crates/collab/src/db/tables/billing_subscription.rs   |  15 
crates/collab/src/lib.rs                              |  44 
crates/collab/src/main.rs                             |   7 
crates/collab/src/stripe_billing.rs                   | 156 ---
crates/collab/src/stripe_client.rs                    | 285 ------
crates/collab/src/stripe_client/fake_stripe_client.rs | 247 -----
crates/collab/src/stripe_client/real_stripe_client.rs | 612 ------------
crates/collab/src/tests.rs                            |   2 
crates/collab/src/tests/stripe_billing_tests.rs       | 123 --
crates/collab/src/tests/test_server.rs                |   5 
16 files changed, 2 insertions(+), 1,738 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1262,26 +1262,6 @@ dependencies = [
  "syn 2.0.101",
 ]
 
-[[package]]
-name = "async-stripe"
-version = "0.40.0"
-source = "git+https://github.com/zed-industries/async-stripe?rev=3672dd4efb7181aa597bf580bf5a2f5d23db6735#3672dd4efb7181aa597bf580bf5a2f5d23db6735"
-dependencies = [
- "chrono",
- "futures-util",
- "http-types",
- "hyper 0.14.32",
- "hyper-rustls 0.24.2",
- "serde",
- "serde_json",
- "serde_path_to_error",
- "serde_qs 0.10.1",
- "smart-default 0.6.0",
- "smol_str 0.1.24",
- "thiserror 1.0.69",
- "tokio",
-]
-
 [[package]]
 name = "async-tar"
 version = "0.5.0"
@@ -2083,12 +2063,6 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
 
-[[package]]
-name = "base64"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-
 [[package]]
 name = "base64"
 version = "0.21.7"
@@ -3281,7 +3255,6 @@ dependencies = [
  "anyhow",
  "assistant_context",
  "assistant_slash_command",
- "async-stripe",
  "async-trait",
  "async-tungstenite",
  "audio",
@@ -3308,7 +3281,6 @@ dependencies = [
  "dap_adapters",
  "dashmap 6.1.0",
  "debugger_ui",
- "derive_more 0.99.19",
  "editor",
  "envy",
  "extension",
@@ -3870,7 +3842,7 @@ dependencies = [
  "rustc-hash 1.1.0",
  "rustybuzz 0.14.1",
  "self_cell",
- "smol_str 0.2.2",
+ "smol_str",
  "swash",
  "sys-locale",
  "ttf-parser 0.21.1",
@@ -6374,17 +6346,6 @@ dependencies = [
  "windows-targets 0.48.5",
 ]
 
-[[package]]
-name = "getrandom"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
 [[package]]
 name = "getrandom"
 version = "0.2.15"
@@ -7988,27 +7949,6 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
 
-[[package]]
-name = "http-types"
-version = "2.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
-dependencies = [
- "anyhow",
- "async-channel 1.9.0",
- "base64 0.13.1",
- "futures-lite 1.13.0",
- "http 0.2.12",
- "infer",
- "pin-project-lite",
- "rand 0.7.3",
- "serde",
- "serde_json",
- "serde_qs 0.8.5",
- "serde_urlencoded",
- "url",
-]
-
 [[package]]
 name = "http_client"
 version = "0.1.0"
@@ -8487,12 +8427,6 @@ version = "2.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
 
-[[package]]
-name = "infer"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
-
 [[package]]
 name = "inherent"
 version = "1.0.12"
@@ -10269,7 +10203,7 @@ dependencies = [
  "num-traits",
  "range-map",
  "scroll",
- "smart-default 0.7.1",
+ "smart-default",
 ]
 
 [[package]]
@@ -13143,19 +13077,6 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
 
-[[package]]
-name = "rand"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
-dependencies = [
- "getrandom 0.1.16",
- "libc",
- "rand_chacha 0.2.2",
- "rand_core 0.5.1",
- "rand_hc",
-]
-
 [[package]]
 name = "rand"
 version = "0.8.5"
@@ -13177,16 +13098,6 @@ dependencies = [
  "rand_core 0.9.3",
 ]
 
-[[package]]
-name = "rand_chacha"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.5.1",
-]
-
 [[package]]
 name = "rand_chacha"
 version = "0.3.1"
@@ -13207,15 +13118,6 @@ dependencies = [
  "rand_core 0.9.3",
 ]
 
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-dependencies = [
- "getrandom 0.1.16",
-]
-
 [[package]]
 name = "rand_core"
 version = "0.6.4"
@@ -13234,15 +13136,6 @@ dependencies = [
  "getrandom 0.3.2",
 ]
 
-[[package]]
-name = "rand_hc"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
-dependencies = [
- "rand_core 0.5.1",
-]
-
 [[package]]
 name = "range-map"
 version = "0.2.0"
@@ -14897,28 +14790,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "serde_qs"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
-dependencies = [
- "percent-encoding",
- "serde",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "serde_qs"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa"
-dependencies = [
- "percent-encoding",
- "serde",
- "thiserror 1.0.69",
-]
-
 [[package]]
 name = "serde_repr"
 version = "0.1.20"
@@ -15295,17 +15166,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "smart-default"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "smart-default"
 version = "0.7.1"
@@ -15334,15 +15194,6 @@ dependencies = [
  "futures-lite 2.6.0",
 ]
 
-[[package]]
-name = "smol_str"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "smol_str"
 version = "0.2.2"
@@ -18191,12 +18042,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "wasi"
-version = "0.9.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
-
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"

Cargo.toml 🔗

@@ -667,20 +667,6 @@ workspace-hack = "0.1.0"
 yawc = { git = "https://github.com/deviant-forks/yawc", rev = "1899688f3e69ace4545aceb97b2a13881cf26142" }
 zstd = "0.11"
 
-[workspace.dependencies.async-stripe]
-git = "https://github.com/zed-industries/async-stripe"
-rev = "3672dd4efb7181aa597bf580bf5a2f5d23db6735"
-default-features = false
-features = [
-    "runtime-tokio-hyper-rustls",
-    "billing",
-    "checkout",
-    "events",
-    # The features below are only enabled to get the `events` feature to build.
-    "chrono",
-    "connect",
-]
-
 [workspace.dependencies.windows]
 version = "0.61"
 features = [

crates/collab/Cargo.toml 🔗

@@ -19,7 +19,6 @@ test-support = ["sqlite"]
 
 [dependencies]
 anyhow.workspace = true
-async-stripe.workspace = true
 async-trait.workspace = true
 async-tungstenite.workspace = true
 aws-config = { version = "1.1.5" }
@@ -33,7 +32,6 @@ clock.workspace = true
 cloud_llm_client.workspace = true
 collections.workspace = true
 dashmap.workspace = true
-derive_more.workspace = true
 envy = "0.4.2"
 futures.workspace = true
 gpui.workspace = true
@@ -134,6 +132,3 @@ util.workspace = true
 workspace = { workspace = true, features = ["test-support"] }
 worktree = { workspace = true, features = ["test-support"] }
 zlog.workspace = true
-
-[package.metadata.cargo-machete]
-ignored = ["async-stripe"]

crates/collab/k8s/collab.template.yml 🔗

@@ -219,12 +219,6 @@ spec:
                 secretKeyRef:
                   name: slack
                   key: panics_webhook
-            - name: STRIPE_API_KEY
-              valueFrom:
-                secretKeyRef:
-                  name: stripe
-                  key: api_key
-                  optional: true
             - name: COMPLETE_WITH_LANGUAGE_MODEL_RATE_LIMIT_PER_HOUR
               value: "1000"
             - name: SUPERMAVEN_ADMIN_API_KEY

crates/collab/src/api/billing.rs 🔗

@@ -1,59 +0,0 @@
-use std::sync::Arc;
-use stripe::SubscriptionStatus;
-
-use crate::AppState;
-use crate::db::billing_subscription::StripeSubscriptionStatus;
-use crate::db::{CreateBillingCustomerParams, billing_customer};
-use crate::stripe_client::{StripeClient, StripeCustomerId};
-
-impl From<SubscriptionStatus> for StripeSubscriptionStatus {
-    fn from(value: SubscriptionStatus) -> Self {
-        match value {
-            SubscriptionStatus::Incomplete => Self::Incomplete,
-            SubscriptionStatus::IncompleteExpired => Self::IncompleteExpired,
-            SubscriptionStatus::Trialing => Self::Trialing,
-            SubscriptionStatus::Active => Self::Active,
-            SubscriptionStatus::PastDue => Self::PastDue,
-            SubscriptionStatus::Canceled => Self::Canceled,
-            SubscriptionStatus::Unpaid => Self::Unpaid,
-            SubscriptionStatus::Paused => Self::Paused,
-        }
-    }
-}
-
-/// Finds or creates a billing customer using the provided customer.
-pub async fn find_or_create_billing_customer(
-    app: &Arc<AppState>,
-    stripe_client: &dyn StripeClient,
-    customer_id: &StripeCustomerId,
-) -> anyhow::Result<Option<billing_customer::Model>> {
-    // If we already have a billing customer record associated with the Stripe customer,
-    // there's nothing more we need to do.
-    if let Some(billing_customer) = app
-        .db
-        .get_billing_customer_by_stripe_customer_id(customer_id.0.as_ref())
-        .await?
-    {
-        return Ok(Some(billing_customer));
-    }
-
-    let customer = stripe_client.get_customer(customer_id).await?;
-
-    let Some(email) = customer.email else {
-        return Ok(None);
-    };
-
-    let Some(user) = app.db.get_user_by_email(&email).await? else {
-        return Ok(None);
-    };
-
-    let billing_customer = app
-        .db
-        .create_billing_customer(&CreateBillingCustomerParams {
-            user_id: user.id,
-            stripe_customer_id: customer.id.to_string(),
-        })
-        .await?;
-
-    Ok(Some(billing_customer))
-}

crates/collab/src/db/tables/billing_subscription.rs 🔗

@@ -1,5 +1,4 @@
 use crate::db::{BillingCustomerId, BillingSubscriptionId};
-use crate::stripe_client;
 use chrono::{Datelike as _, NaiveDate, Utc};
 use sea_orm::entity::prelude::*;
 use serde::Serialize;
@@ -160,17 +159,3 @@ pub enum StripeCancellationReason {
     #[sea_orm(string_value = "payment_failed")]
     PaymentFailed,
 }
-
-impl From<stripe_client::StripeCancellationDetailsReason> for StripeCancellationReason {
-    fn from(value: stripe_client::StripeCancellationDetailsReason) -> Self {
-        match value {
-            stripe_client::StripeCancellationDetailsReason::CancellationRequested => {
-                Self::CancellationRequested
-            }
-            stripe_client::StripeCancellationDetailsReason::PaymentDisputed => {
-                Self::PaymentDisputed
-            }
-            stripe_client::StripeCancellationDetailsReason::PaymentFailed => Self::PaymentFailed,
-        }
-    }
-}

crates/collab/src/lib.rs 🔗

@@ -7,8 +7,6 @@ pub mod llm;
 pub mod migrations;
 pub mod rpc;
 pub mod seed;
-pub mod stripe_billing;
-pub mod stripe_client;
 pub mod user_backfiller;
 
 #[cfg(test)]
@@ -27,16 +25,12 @@ use serde::Deserialize;
 use std::{path::PathBuf, sync::Arc};
 use util::ResultExt;
 
-use crate::stripe_billing::StripeBilling;
-use crate::stripe_client::{RealStripeClient, StripeClient};
-
 pub type Result<T, E = Error> = std::result::Result<T, E>;
 
 pub enum Error {
     Http(StatusCode, String, HeaderMap),
     Database(sea_orm::error::DbErr),
     Internal(anyhow::Error),
-    Stripe(stripe::StripeError),
 }
 
 impl From<anyhow::Error> for Error {
@@ -51,12 +45,6 @@ impl From<sea_orm::error::DbErr> for Error {
     }
 }
 
-impl From<stripe::StripeError> for Error {
-    fn from(error: stripe::StripeError) -> Self {
-        Self::Stripe(error)
-    }
-}
-
 impl From<axum::Error> for Error {
     fn from(error: axum::Error) -> Self {
         Self::Internal(error.into())
@@ -104,14 +92,6 @@ impl IntoResponse for Error {
                 );
                 (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
             }
-            Error::Stripe(error) => {
-                log::error!(
-                    "HTTP error {}: {:?}",
-                    StatusCode::INTERNAL_SERVER_ERROR,
-                    &error
-                );
-                (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
-            }
         }
     }
 }
@@ -122,7 +102,6 @@ impl std::fmt::Debug for Error {
             Error::Http(code, message, _headers) => (code, message).fmt(f),
             Error::Database(error) => error.fmt(f),
             Error::Internal(error) => error.fmt(f),
-            Error::Stripe(error) => error.fmt(f),
         }
     }
 }
@@ -133,7 +112,6 @@ impl std::fmt::Display for Error {
             Error::Http(code, message, _) => write!(f, "{code}: {message}"),
             Error::Database(error) => error.fmt(f),
             Error::Internal(error) => error.fmt(f),
-            Error::Stripe(error) => error.fmt(f),
         }
     }
 }
@@ -179,7 +157,6 @@ pub struct Config {
     pub zed_client_checksum_seed: Option<String>,
     pub slack_panics_webhook: Option<String>,
     pub auto_join_channel_id: Option<ChannelId>,
-    pub stripe_api_key: Option<String>,
     pub supermaven_admin_api_key: Option<Arc<str>>,
     pub user_backfiller_github_access_token: Option<Arc<str>>,
 }
@@ -234,7 +211,6 @@ impl Config {
             auto_join_channel_id: None,
             migrations_path: None,
             seed_path: None,
-            stripe_api_key: None,
             supermaven_admin_api_key: None,
             user_backfiller_github_access_token: None,
             kinesis_region: None,
@@ -269,11 +245,6 @@ pub struct AppState {
     pub llm_db: Option<Arc<LlmDatabase>>,
     pub livekit_client: Option<Arc<dyn livekit_api::Client>>,
     pub blob_store_client: Option<aws_sdk_s3::Client>,
-    /// This is a real instance of the Stripe client; we're working to replace references to this with the
-    /// [`StripeClient`] trait.
-    pub real_stripe_client: Option<Arc<stripe::Client>>,
-    pub stripe_client: Option<Arc<dyn StripeClient>>,
-    pub stripe_billing: Option<Arc<StripeBilling>>,
     pub executor: Executor,
     pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
     pub config: Config,
@@ -316,18 +287,11 @@ impl AppState {
         };
 
         let db = Arc::new(db);
-        let stripe_client = build_stripe_client(&config).map(Arc::new).log_err();
         let this = Self {
             db: db.clone(),
             llm_db,
             livekit_client,
             blob_store_client: build_blob_store_client(&config).await.log_err(),
-            stripe_billing: stripe_client
-                .clone()
-                .map(|stripe_client| Arc::new(StripeBilling::new(stripe_client))),
-            real_stripe_client: stripe_client.clone(),
-            stripe_client: stripe_client
-                .map(|stripe_client| Arc::new(RealStripeClient::new(stripe_client)) as _),
             executor,
             kinesis_client: if config.kinesis_access_key.is_some() {
                 build_kinesis_client(&config).await.log_err()
@@ -340,14 +304,6 @@ impl AppState {
     }
 }
 
-fn build_stripe_client(config: &Config) -> anyhow::Result<stripe::Client> {
-    let api_key = config
-        .stripe_api_key
-        .as_ref()
-        .context("missing stripe_api_key")?;
-    Ok(stripe::Client::new(api_key))
-}
-
 async fn build_blob_store_client(config: &Config) -> anyhow::Result<aws_sdk_s3::Client> {
     let keys = aws_sdk_s3::config::Credentials::new(
         config

crates/collab/src/main.rs 🔗

@@ -102,13 +102,6 @@ async fn main() -> Result<()> {
 
                 let state = AppState::new(config, Executor::Production).await?;
 
-                if let Some(stripe_billing) = state.stripe_billing.clone() {
-                    let executor = state.executor.clone();
-                    executor.spawn_detached(async move {
-                        stripe_billing.initialize().await.trace_err();
-                    });
-                }
-
                 if mode.is_collab() {
                     state.db.purge_old_embeddings().await.trace_err();
 

crates/collab/src/stripe_billing.rs 🔗

@@ -1,156 +0,0 @@
-use std::sync::Arc;
-
-use anyhow::anyhow;
-use collections::HashMap;
-use stripe::SubscriptionStatus;
-use tokio::sync::RwLock;
-
-use crate::Result;
-use crate::stripe_client::{
-    RealStripeClient, StripeAutomaticTax, StripeClient, StripeCreateSubscriptionItems,
-    StripeCreateSubscriptionParams, StripeCustomerId, StripePrice, StripePriceId,
-    StripeSubscription,
-};
-
-pub struct StripeBilling {
-    state: RwLock<StripeBillingState>,
-    client: Arc<dyn StripeClient>,
-}
-
-#[derive(Default)]
-struct StripeBillingState {
-    prices_by_lookup_key: HashMap<String, StripePrice>,
-}
-
-impl StripeBilling {
-    pub fn new(client: Arc<stripe::Client>) -> Self {
-        Self {
-            client: Arc::new(RealStripeClient::new(client.clone())),
-            state: RwLock::default(),
-        }
-    }
-
-    #[cfg(test)]
-    pub fn test(client: Arc<crate::stripe_client::FakeStripeClient>) -> Self {
-        Self {
-            client,
-            state: RwLock::default(),
-        }
-    }
-
-    pub fn client(&self) -> &Arc<dyn StripeClient> {
-        &self.client
-    }
-
-    pub async fn initialize(&self) -> Result<()> {
-        log::info!("StripeBilling: initializing");
-
-        let mut state = self.state.write().await;
-
-        let prices = self.client.list_prices().await?;
-
-        for price in prices {
-            if let Some(lookup_key) = price.lookup_key.clone() {
-                state.prices_by_lookup_key.insert(lookup_key, price);
-            }
-        }
-
-        log::info!("StripeBilling: initialized");
-
-        Ok(())
-    }
-
-    pub async fn zed_pro_price_id(&self) -> Result<StripePriceId> {
-        self.find_price_id_by_lookup_key("zed-pro").await
-    }
-
-    pub async fn zed_free_price_id(&self) -> Result<StripePriceId> {
-        self.find_price_id_by_lookup_key("zed-free").await
-    }
-
-    pub async fn find_price_id_by_lookup_key(&self, lookup_key: &str) -> Result<StripePriceId> {
-        self.state
-            .read()
-            .await
-            .prices_by_lookup_key
-            .get(lookup_key)
-            .map(|price| price.id.clone())
-            .ok_or_else(|| crate::Error::Internal(anyhow!("no price ID found for {lookup_key:?}")))
-    }
-
-    pub async fn find_price_by_lookup_key(&self, lookup_key: &str) -> Result<StripePrice> {
-        self.state
-            .read()
-            .await
-            .prices_by_lookup_key
-            .get(lookup_key)
-            .cloned()
-            .ok_or_else(|| crate::Error::Internal(anyhow!("no price found for {lookup_key:?}")))
-    }
-
-    /// Returns the Stripe customer associated with the provided email address, or creates a new customer, if one does
-    /// not already exist.
-    ///
-    /// Always returns a new Stripe customer if the email address is `None`.
-    pub async fn find_or_create_customer_by_email(
-        &self,
-        email_address: Option<&str>,
-    ) -> Result<StripeCustomerId> {
-        let existing_customer = if let Some(email) = email_address {
-            let customers = self.client.list_customers_by_email(email).await?;
-
-            customers.first().cloned()
-        } else {
-            None
-        };
-
-        let customer_id = if let Some(existing_customer) = existing_customer {
-            existing_customer.id
-        } else {
-            let customer = self
-                .client
-                .create_customer(crate::stripe_client::CreateCustomerParams {
-                    email: email_address,
-                })
-                .await?;
-
-            customer.id
-        };
-
-        Ok(customer_id)
-    }
-
-    pub async fn subscribe_to_zed_free(
-        &self,
-        customer_id: StripeCustomerId,
-    ) -> Result<StripeSubscription> {
-        let zed_free_price_id = self.zed_free_price_id().await?;
-
-        let existing_subscriptions = self
-            .client
-            .list_subscriptions_for_customer(&customer_id)
-            .await?;
-
-        let existing_active_subscription =
-            existing_subscriptions.into_iter().find(|subscription| {
-                subscription.status == SubscriptionStatus::Active
-                    || subscription.status == SubscriptionStatus::Trialing
-            });
-        if let Some(subscription) = existing_active_subscription {
-            return Ok(subscription);
-        }
-
-        let params = StripeCreateSubscriptionParams {
-            customer: customer_id,
-            items: vec![StripeCreateSubscriptionItems {
-                price: Some(zed_free_price_id),
-                quantity: Some(1),
-            }],
-            automatic_tax: Some(StripeAutomaticTax { enabled: true }),
-        };
-
-        let subscription = self.client.create_subscription(params).await?;
-
-        Ok(subscription)
-    }
-}

crates/collab/src/stripe_client.rs 🔗

@@ -1,285 +0,0 @@
-#[cfg(test)]
-mod fake_stripe_client;
-mod real_stripe_client;
-
-use std::collections::HashMap;
-use std::sync::Arc;
-
-use anyhow::Result;
-use async_trait::async_trait;
-
-#[cfg(test)]
-pub use fake_stripe_client::*;
-pub use real_stripe_client::*;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display, Serialize)]
-pub struct StripeCustomerId(pub Arc<str>);
-
-#[derive(Debug, Clone)]
-pub struct StripeCustomer {
-    pub id: StripeCustomerId,
-    pub email: Option<String>,
-}
-
-#[derive(Debug)]
-pub struct CreateCustomerParams<'a> {
-    pub email: Option<&'a str>,
-}
-
-#[derive(Debug)]
-pub struct UpdateCustomerParams<'a> {
-    pub email: Option<&'a str>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
-pub struct StripeSubscriptionId(pub Arc<str>);
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscription {
-    pub id: StripeSubscriptionId,
-    pub customer: StripeCustomerId,
-    // TODO: Create our own version of this enum.
-    pub status: stripe::SubscriptionStatus,
-    pub current_period_end: i64,
-    pub current_period_start: i64,
-    pub items: Vec<StripeSubscriptionItem>,
-    pub cancel_at: Option<i64>,
-    pub cancellation_details: Option<StripeCancellationDetails>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
-pub struct StripeSubscriptionItemId(pub Arc<str>);
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscriptionItem {
-    pub id: StripeSubscriptionItemId,
-    pub price: Option<StripePrice>,
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct StripeCancellationDetails {
-    pub reason: Option<StripeCancellationDetailsReason>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCancellationDetailsReason {
-    CancellationRequested,
-    PaymentDisputed,
-    PaymentFailed,
-}
-
-#[derive(Debug)]
-pub struct StripeCreateSubscriptionParams {
-    pub customer: StripeCustomerId,
-    pub items: Vec<StripeCreateSubscriptionItems>,
-    pub automatic_tax: Option<StripeAutomaticTax>,
-}
-
-#[derive(Debug)]
-pub struct StripeCreateSubscriptionItems {
-    pub price: Option<StripePriceId>,
-    pub quantity: Option<u64>,
-}
-
-#[derive(Debug, Clone)]
-pub struct UpdateSubscriptionParams {
-    pub items: Option<Vec<UpdateSubscriptionItems>>,
-    pub trial_settings: Option<StripeSubscriptionTrialSettings>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct UpdateSubscriptionItems {
-    pub price: Option<StripePriceId>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscriptionTrialSettings {
-    pub end_behavior: StripeSubscriptionTrialSettingsEndBehavior,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscriptionTrialSettingsEndBehavior {
-    pub missing_payment_method: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod {
-    Cancel,
-    CreateInvoice,
-    Pause,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
-pub struct StripePriceId(pub Arc<str>);
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripePrice {
-    pub id: StripePriceId,
-    pub unit_amount: Option<i64>,
-    pub lookup_key: Option<String>,
-    pub recurring: Option<StripePriceRecurring>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripePriceRecurring {
-    pub meter: Option<String>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display, Deserialize)]
-pub struct StripeMeterId(pub Arc<str>);
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct StripeMeter {
-    pub id: StripeMeterId,
-    pub event_name: String,
-}
-
-#[derive(Debug, Serialize)]
-pub struct StripeCreateMeterEventParams<'a> {
-    pub identifier: &'a str,
-    pub event_name: &'a str,
-    pub payload: StripeCreateMeterEventPayload<'a>,
-    pub timestamp: Option<i64>,
-}
-
-#[derive(Debug, Serialize)]
-pub struct StripeCreateMeterEventPayload<'a> {
-    pub value: u64,
-    pub stripe_customer_id: &'a StripeCustomerId,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeBillingAddressCollection {
-    Auto,
-    Required,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeCustomerUpdate {
-    pub address: Option<StripeCustomerUpdateAddress>,
-    pub name: Option<StripeCustomerUpdateName>,
-    pub shipping: Option<StripeCustomerUpdateShipping>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCustomerUpdateAddress {
-    Auto,
-    Never,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCustomerUpdateName {
-    Auto,
-    Never,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCustomerUpdateShipping {
-    Auto,
-    Never,
-}
-
-#[derive(Debug, Default)]
-pub struct StripeCreateCheckoutSessionParams<'a> {
-    pub customer: Option<&'a StripeCustomerId>,
-    pub client_reference_id: Option<&'a str>,
-    pub mode: Option<StripeCheckoutSessionMode>,
-    pub line_items: Option<Vec<StripeCreateCheckoutSessionLineItems>>,
-    pub payment_method_collection: Option<StripeCheckoutSessionPaymentMethodCollection>,
-    pub subscription_data: Option<StripeCreateCheckoutSessionSubscriptionData>,
-    pub success_url: Option<&'a str>,
-    pub billing_address_collection: Option<StripeBillingAddressCollection>,
-    pub customer_update: Option<StripeCustomerUpdate>,
-    pub tax_id_collection: Option<StripeTaxIdCollection>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCheckoutSessionMode {
-    Payment,
-    Setup,
-    Subscription,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeCreateCheckoutSessionLineItems {
-    pub price: Option<String>,
-    pub quantity: Option<u64>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCheckoutSessionPaymentMethodCollection {
-    Always,
-    IfRequired,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeCreateCheckoutSessionSubscriptionData {
-    pub metadata: Option<HashMap<String, String>>,
-    pub trial_period_days: Option<u32>,
-    pub trial_settings: Option<StripeSubscriptionTrialSettings>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeTaxIdCollection {
-    pub enabled: bool,
-}
-
-#[derive(Debug, Clone)]
-pub struct StripeAutomaticTax {
-    pub enabled: bool,
-}
-
-#[derive(Debug)]
-pub struct StripeCheckoutSession {
-    pub url: Option<String>,
-}
-
-#[async_trait]
-pub trait StripeClient: Send + Sync {
-    async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>>;
-
-    async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer>;
-
-    async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer>;
-
-    async fn update_customer(
-        &self,
-        customer_id: &StripeCustomerId,
-        params: UpdateCustomerParams<'_>,
-    ) -> Result<StripeCustomer>;
-
-    async fn list_subscriptions_for_customer(
-        &self,
-        customer_id: &StripeCustomerId,
-    ) -> Result<Vec<StripeSubscription>>;
-
-    async fn get_subscription(
-        &self,
-        subscription_id: &StripeSubscriptionId,
-    ) -> Result<StripeSubscription>;
-
-    async fn create_subscription(
-        &self,
-        params: StripeCreateSubscriptionParams,
-    ) -> Result<StripeSubscription>;
-
-    async fn update_subscription(
-        &self,
-        subscription_id: &StripeSubscriptionId,
-        params: UpdateSubscriptionParams,
-    ) -> Result<()>;
-
-    async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()>;
-
-    async fn list_prices(&self) -> Result<Vec<StripePrice>>;
-
-    async fn list_meters(&self) -> Result<Vec<StripeMeter>>;
-
-    async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()>;
-
-    async fn create_checkout_session(
-        &self,
-        params: StripeCreateCheckoutSessionParams<'_>,
-    ) -> Result<StripeCheckoutSession>;
-}

crates/collab/src/stripe_client/fake_stripe_client.rs 🔗

@@ -1,247 +0,0 @@
-use std::sync::Arc;
-
-use anyhow::{Result, anyhow};
-use async_trait::async_trait;
-use chrono::{Duration, Utc};
-use collections::HashMap;
-use parking_lot::Mutex;
-use uuid::Uuid;
-
-use crate::stripe_client::{
-    CreateCustomerParams, StripeBillingAddressCollection, StripeCheckoutSession,
-    StripeCheckoutSessionMode, StripeCheckoutSessionPaymentMethodCollection, StripeClient,
-    StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
-    StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
-    StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate,
-    StripeMeter, StripeMeterId, StripePrice, StripePriceId, StripeSubscription,
-    StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId, StripeTaxIdCollection,
-    UpdateCustomerParams, UpdateSubscriptionParams,
-};
-
-#[derive(Debug, Clone)]
-pub struct StripeCreateMeterEventCall {
-    pub identifier: Arc<str>,
-    pub event_name: Arc<str>,
-    pub value: u64,
-    pub stripe_customer_id: StripeCustomerId,
-    pub timestamp: Option<i64>,
-}
-
-#[derive(Debug, Clone)]
-pub struct StripeCreateCheckoutSessionCall {
-    pub customer: Option<StripeCustomerId>,
-    pub client_reference_id: Option<String>,
-    pub mode: Option<StripeCheckoutSessionMode>,
-    pub line_items: Option<Vec<StripeCreateCheckoutSessionLineItems>>,
-    pub payment_method_collection: Option<StripeCheckoutSessionPaymentMethodCollection>,
-    pub subscription_data: Option<StripeCreateCheckoutSessionSubscriptionData>,
-    pub success_url: Option<String>,
-    pub billing_address_collection: Option<StripeBillingAddressCollection>,
-    pub customer_update: Option<StripeCustomerUpdate>,
-    pub tax_id_collection: Option<StripeTaxIdCollection>,
-}
-
-pub struct FakeStripeClient {
-    pub customers: Arc<Mutex<HashMap<StripeCustomerId, StripeCustomer>>>,
-    pub subscriptions: Arc<Mutex<HashMap<StripeSubscriptionId, StripeSubscription>>>,
-    pub update_subscription_calls:
-        Arc<Mutex<Vec<(StripeSubscriptionId, UpdateSubscriptionParams)>>>,
-    pub prices: Arc<Mutex<HashMap<StripePriceId, StripePrice>>>,
-    pub meters: Arc<Mutex<HashMap<StripeMeterId, StripeMeter>>>,
-    pub create_meter_event_calls: Arc<Mutex<Vec<StripeCreateMeterEventCall>>>,
-    pub create_checkout_session_calls: Arc<Mutex<Vec<StripeCreateCheckoutSessionCall>>>,
-}
-
-impl FakeStripeClient {
-    pub fn new() -> Self {
-        Self {
-            customers: Arc::new(Mutex::new(HashMap::default())),
-            subscriptions: Arc::new(Mutex::new(HashMap::default())),
-            update_subscription_calls: Arc::new(Mutex::new(Vec::new())),
-            prices: Arc::new(Mutex::new(HashMap::default())),
-            meters: Arc::new(Mutex::new(HashMap::default())),
-            create_meter_event_calls: Arc::new(Mutex::new(Vec::new())),
-            create_checkout_session_calls: Arc::new(Mutex::new(Vec::new())),
-        }
-    }
-}
-
-#[async_trait]
-impl StripeClient for FakeStripeClient {
-    async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
-        Ok(self
-            .customers
-            .lock()
-            .values()
-            .filter(|customer| customer.email.as_deref() == Some(email))
-            .cloned()
-            .collect())
-    }
-
-    async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer> {
-        self.customers
-            .lock()
-            .get(customer_id)
-            .cloned()
-            .ok_or_else(|| anyhow!("no customer found for {customer_id:?}"))
-    }
-
-    async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
-        let customer = StripeCustomer {
-            id: StripeCustomerId(format!("cus_{}", Uuid::new_v4()).into()),
-            email: params.email.map(|email| email.to_string()),
-        };
-
-        self.customers
-            .lock()
-            .insert(customer.id.clone(), customer.clone());
-
-        Ok(customer)
-    }
-
-    async fn update_customer(
-        &self,
-        customer_id: &StripeCustomerId,
-        params: UpdateCustomerParams<'_>,
-    ) -> Result<StripeCustomer> {
-        let mut customers = self.customers.lock();
-        if let Some(customer) = customers.get_mut(customer_id) {
-            if let Some(email) = params.email {
-                customer.email = Some(email.to_string());
-            }
-            Ok(customer.clone())
-        } else {
-            Err(anyhow!("no customer found for {customer_id:?}"))
-        }
-    }
-
-    async fn list_subscriptions_for_customer(
-        &self,
-        customer_id: &StripeCustomerId,
-    ) -> Result<Vec<StripeSubscription>> {
-        let subscriptions = self
-            .subscriptions
-            .lock()
-            .values()
-            .filter(|subscription| subscription.customer == *customer_id)
-            .cloned()
-            .collect();
-
-        Ok(subscriptions)
-    }
-
-    async fn get_subscription(
-        &self,
-        subscription_id: &StripeSubscriptionId,
-    ) -> Result<StripeSubscription> {
-        self.subscriptions
-            .lock()
-            .get(subscription_id)
-            .cloned()
-            .ok_or_else(|| anyhow!("no subscription found for {subscription_id:?}"))
-    }
-
-    async fn create_subscription(
-        &self,
-        params: StripeCreateSubscriptionParams,
-    ) -> Result<StripeSubscription> {
-        let now = Utc::now();
-
-        let subscription = StripeSubscription {
-            id: StripeSubscriptionId(format!("sub_{}", Uuid::new_v4()).into()),
-            customer: params.customer,
-            status: stripe::SubscriptionStatus::Active,
-            current_period_start: now.timestamp(),
-            current_period_end: (now + Duration::days(30)).timestamp(),
-            items: params
-                .items
-                .into_iter()
-                .map(|item| StripeSubscriptionItem {
-                    id: StripeSubscriptionItemId(format!("si_{}", Uuid::new_v4()).into()),
-                    price: item
-                        .price
-                        .and_then(|price_id| self.prices.lock().get(&price_id).cloned()),
-                })
-                .collect(),
-            cancel_at: None,
-            cancellation_details: None,
-        };
-
-        self.subscriptions
-            .lock()
-            .insert(subscription.id.clone(), subscription.clone());
-
-        Ok(subscription)
-    }
-
-    async fn update_subscription(
-        &self,
-        subscription_id: &StripeSubscriptionId,
-        params: UpdateSubscriptionParams,
-    ) -> Result<()> {
-        let subscription = self.get_subscription(subscription_id).await?;
-
-        self.update_subscription_calls
-            .lock()
-            .push((subscription.id, params));
-
-        Ok(())
-    }
-
-    async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()> {
-        // TODO: Implement fake subscription cancellation.
-        let _ = subscription_id;
-
-        Ok(())
-    }
-
-    async fn list_prices(&self) -> Result<Vec<StripePrice>> {
-        let prices = self.prices.lock().values().cloned().collect();
-
-        Ok(prices)
-    }
-
-    async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
-        let meters = self.meters.lock().values().cloned().collect();
-
-        Ok(meters)
-    }
-
-    async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
-        self.create_meter_event_calls
-            .lock()
-            .push(StripeCreateMeterEventCall {
-                identifier: params.identifier.into(),
-                event_name: params.event_name.into(),
-                value: params.payload.value,
-                stripe_customer_id: params.payload.stripe_customer_id.clone(),
-                timestamp: params.timestamp,
-            });
-
-        Ok(())
-    }
-
-    async fn create_checkout_session(
-        &self,
-        params: StripeCreateCheckoutSessionParams<'_>,
-    ) -> Result<StripeCheckoutSession> {
-        self.create_checkout_session_calls
-            .lock()
-            .push(StripeCreateCheckoutSessionCall {
-                customer: params.customer.cloned(),
-                client_reference_id: params.client_reference_id.map(|id| id.to_string()),
-                mode: params.mode,
-                line_items: params.line_items,
-                payment_method_collection: params.payment_method_collection,
-                subscription_data: params.subscription_data,
-                success_url: params.success_url.map(|url| url.to_string()),
-                billing_address_collection: params.billing_address_collection,
-                customer_update: params.customer_update,
-                tax_id_collection: params.tax_id_collection,
-            });
-
-        Ok(StripeCheckoutSession {
-            url: Some("https://checkout.stripe.com/c/pay/cs_test_1".to_string()),
-        })
-    }
-}

crates/collab/src/stripe_client/real_stripe_client.rs 🔗

@@ -1,612 +0,0 @@
-use std::str::FromStr as _;
-use std::sync::Arc;
-
-use anyhow::{Context as _, Result, anyhow};
-use async_trait::async_trait;
-use serde::{Deserialize, Serialize};
-use stripe::{
-    CancellationDetails, CancellationDetailsReason, CheckoutSession, CheckoutSessionMode,
-    CheckoutSessionPaymentMethodCollection, CreateCheckoutSession, CreateCheckoutSessionLineItems,
-    CreateCheckoutSessionSubscriptionData, CreateCheckoutSessionSubscriptionDataTrialSettings,
-    CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior,
-    CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod,
-    CreateCustomer, CreateSubscriptionAutomaticTax, Customer, CustomerId, ListCustomers, Price,
-    PriceId, Recurring, Subscription, SubscriptionId, SubscriptionItem, SubscriptionItemId,
-    UpdateCustomer, UpdateSubscriptionItems, UpdateSubscriptionTrialSettings,
-    UpdateSubscriptionTrialSettingsEndBehavior,
-    UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
-};
-
-use crate::stripe_client::{
-    CreateCustomerParams, StripeAutomaticTax, StripeBillingAddressCollection,
-    StripeCancellationDetails, StripeCancellationDetailsReason, StripeCheckoutSession,
-    StripeCheckoutSessionMode, StripeCheckoutSessionPaymentMethodCollection, StripeClient,
-    StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
-    StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
-    StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate,
-    StripeCustomerUpdateAddress, StripeCustomerUpdateName, StripeCustomerUpdateShipping,
-    StripeMeter, StripePrice, StripePriceId, StripePriceRecurring, StripeSubscription,
-    StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId,
-    StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
-    StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, StripeTaxIdCollection,
-    UpdateCustomerParams, UpdateSubscriptionParams,
-};
-
-pub struct RealStripeClient {
-    client: Arc<stripe::Client>,
-}
-
-impl RealStripeClient {
-    pub fn new(client: Arc<stripe::Client>) -> Self {
-        Self { client }
-    }
-}
-
-#[async_trait]
-impl StripeClient for RealStripeClient {
-    async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
-        let response = Customer::list(
-            &self.client,
-            &ListCustomers {
-                email: Some(email),
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(response
-            .data
-            .into_iter()
-            .map(StripeCustomer::from)
-            .collect())
-    }
-
-    async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer> {
-        let customer_id = customer_id.try_into()?;
-
-        let customer = Customer::retrieve(&self.client, &customer_id, &[]).await?;
-
-        Ok(StripeCustomer::from(customer))
-    }
-
-    async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
-        let customer = Customer::create(
-            &self.client,
-            CreateCustomer {
-                email: params.email,
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(StripeCustomer::from(customer))
-    }
-
-    async fn update_customer(
-        &self,
-        customer_id: &StripeCustomerId,
-        params: UpdateCustomerParams<'_>,
-    ) -> Result<StripeCustomer> {
-        let customer = Customer::update(
-            &self.client,
-            &customer_id.try_into()?,
-            UpdateCustomer {
-                email: params.email,
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(StripeCustomer::from(customer))
-    }
-
-    async fn list_subscriptions_for_customer(
-        &self,
-        customer_id: &StripeCustomerId,
-    ) -> Result<Vec<StripeSubscription>> {
-        let customer_id = customer_id.try_into()?;
-
-        let subscriptions = stripe::Subscription::list(
-            &self.client,
-            &stripe::ListSubscriptions {
-                customer: Some(customer_id),
-                status: None,
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(subscriptions
-            .data
-            .into_iter()
-            .map(StripeSubscription::from)
-            .collect())
-    }
-
-    async fn get_subscription(
-        &self,
-        subscription_id: &StripeSubscriptionId,
-    ) -> Result<StripeSubscription> {
-        let subscription_id = subscription_id.try_into()?;
-
-        let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
-
-        Ok(StripeSubscription::from(subscription))
-    }
-
-    async fn create_subscription(
-        &self,
-        params: StripeCreateSubscriptionParams,
-    ) -> Result<StripeSubscription> {
-        let customer_id = params.customer.try_into()?;
-
-        let mut create_subscription = stripe::CreateSubscription::new(customer_id);
-        create_subscription.items = Some(
-            params
-                .items
-                .into_iter()
-                .map(|item| stripe::CreateSubscriptionItems {
-                    price: item.price.map(|price| price.to_string()),
-                    quantity: item.quantity,
-                    ..Default::default()
-                })
-                .collect(),
-        );
-        create_subscription.automatic_tax = params.automatic_tax.map(Into::into);
-
-        let subscription = Subscription::create(&self.client, create_subscription).await?;
-
-        Ok(StripeSubscription::from(subscription))
-    }
-
-    async fn update_subscription(
-        &self,
-        subscription_id: &StripeSubscriptionId,
-        params: UpdateSubscriptionParams,
-    ) -> Result<()> {
-        let subscription_id = subscription_id.try_into()?;
-
-        stripe::Subscription::update(
-            &self.client,
-            &subscription_id,
-            stripe::UpdateSubscription {
-                items: params.items.map(|items| {
-                    items
-                        .into_iter()
-                        .map(|item| UpdateSubscriptionItems {
-                            price: item.price.map(|price| price.to_string()),
-                            ..Default::default()
-                        })
-                        .collect()
-                }),
-                trial_settings: params.trial_settings.map(Into::into),
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(())
-    }
-
-    async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()> {
-        let subscription_id = subscription_id.try_into()?;
-
-        Subscription::cancel(
-            &self.client,
-            &subscription_id,
-            stripe::CancelSubscription {
-                invoice_now: None,
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(())
-    }
-
-    async fn list_prices(&self) -> Result<Vec<StripePrice>> {
-        let response = stripe::Price::list(
-            &self.client,
-            &stripe::ListPrices {
-                limit: Some(100),
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        Ok(response.data.into_iter().map(StripePrice::from).collect())
-    }
-
-    async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
-        #[derive(Serialize)]
-        struct Params {
-            #[serde(skip_serializing_if = "Option::is_none")]
-            limit: Option<u64>,
-        }
-
-        let response = self
-            .client
-            .get_query::<stripe::List<StripeMeter>, _>(
-                "/billing/meters",
-                Params { limit: Some(100) },
-            )
-            .await?;
-
-        Ok(response.data)
-    }
-
-    async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
-        #[derive(Deserialize)]
-        struct StripeMeterEvent {
-            pub identifier: String,
-        }
-
-        let identifier = params.identifier;
-        match self
-            .client
-            .post_form::<StripeMeterEvent, _>("/billing/meter_events", params)
-            .await
-        {
-            Ok(_event) => Ok(()),
-            Err(stripe::StripeError::Stripe(error)) => {
-                if error.http_status == 400
-                    && error
-                        .message
-                        .as_ref()
-                        .map_or(false, |message| message.contains(identifier))
-                {
-                    Ok(())
-                } else {
-                    Err(anyhow!(stripe::StripeError::Stripe(error)))
-                }
-            }
-            Err(error) => Err(anyhow!("failed to create meter event: {error:?}")),
-        }
-    }
-
-    async fn create_checkout_session(
-        &self,
-        params: StripeCreateCheckoutSessionParams<'_>,
-    ) -> Result<StripeCheckoutSession> {
-        let params = params.try_into()?;
-        let session = CheckoutSession::create(&self.client, params).await?;
-
-        Ok(session.into())
-    }
-}
-
-impl From<CustomerId> for StripeCustomerId {
-    fn from(value: CustomerId) -> Self {
-        Self(value.as_str().into())
-    }
-}
-
-impl TryFrom<StripeCustomerId> for CustomerId {
-    type Error = anyhow::Error;
-
-    fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
-        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
-    }
-}
-
-impl TryFrom<&StripeCustomerId> for CustomerId {
-    type Error = anyhow::Error;
-
-    fn try_from(value: &StripeCustomerId) -> Result<Self, Self::Error> {
-        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
-    }
-}
-
-impl From<Customer> for StripeCustomer {
-    fn from(value: Customer) -> Self {
-        StripeCustomer {
-            id: value.id.into(),
-            email: value.email,
-        }
-    }
-}
-
-impl From<SubscriptionId> for StripeSubscriptionId {
-    fn from(value: SubscriptionId) -> Self {
-        Self(value.as_str().into())
-    }
-}
-
-impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
-    type Error = anyhow::Error;
-
-    fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
-        Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
-    }
-}
-
-impl From<Subscription> for StripeSubscription {
-    fn from(value: Subscription) -> Self {
-        Self {
-            id: value.id.into(),
-            customer: value.customer.id().into(),
-            status: value.status,
-            current_period_start: value.current_period_start,
-            current_period_end: value.current_period_end,
-            items: value.items.data.into_iter().map(Into::into).collect(),
-            cancel_at: value.cancel_at,
-            cancellation_details: value.cancellation_details.map(Into::into),
-        }
-    }
-}
-
-impl From<CancellationDetails> for StripeCancellationDetails {
-    fn from(value: CancellationDetails) -> Self {
-        Self {
-            reason: value.reason.map(Into::into),
-        }
-    }
-}
-
-impl From<CancellationDetailsReason> for StripeCancellationDetailsReason {
-    fn from(value: CancellationDetailsReason) -> Self {
-        match value {
-            CancellationDetailsReason::CancellationRequested => Self::CancellationRequested,
-            CancellationDetailsReason::PaymentDisputed => Self::PaymentDisputed,
-            CancellationDetailsReason::PaymentFailed => Self::PaymentFailed,
-        }
-    }
-}
-
-impl From<SubscriptionItemId> for StripeSubscriptionItemId {
-    fn from(value: SubscriptionItemId) -> Self {
-        Self(value.as_str().into())
-    }
-}
-
-impl From<SubscriptionItem> for StripeSubscriptionItem {
-    fn from(value: SubscriptionItem) -> Self {
-        Self {
-            id: value.id.into(),
-            price: value.price.map(Into::into),
-        }
-    }
-}
-
-impl From<StripeAutomaticTax> for CreateSubscriptionAutomaticTax {
-    fn from(value: StripeAutomaticTax) -> Self {
-        Self {
-            enabled: value.enabled,
-            liability: None,
-        }
-    }
-}
-
-impl From<StripeSubscriptionTrialSettings> for UpdateSubscriptionTrialSettings {
-    fn from(value: StripeSubscriptionTrialSettings) -> Self {
-        Self {
-            end_behavior: value.end_behavior.into(),
-        }
-    }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehavior>
-    for UpdateSubscriptionTrialSettingsEndBehavior
-{
-    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
-        Self {
-            missing_payment_method: value.missing_payment_method.into(),
-        }
-    }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
-    for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
-{
-    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
-        match value {
-            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
-            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
-                Self::CreateInvoice
-            }
-            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
-        }
-    }
-}
-
-impl From<PriceId> for StripePriceId {
-    fn from(value: PriceId) -> Self {
-        Self(value.as_str().into())
-    }
-}
-
-impl TryFrom<StripePriceId> for PriceId {
-    type Error = anyhow::Error;
-
-    fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
-        Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
-    }
-}
-
-impl From<Price> for StripePrice {
-    fn from(value: Price) -> Self {
-        Self {
-            id: value.id.into(),
-            unit_amount: value.unit_amount,
-            lookup_key: value.lookup_key,
-            recurring: value.recurring.map(StripePriceRecurring::from),
-        }
-    }
-}
-
-impl From<Recurring> for StripePriceRecurring {
-    fn from(value: Recurring) -> Self {
-        Self { meter: value.meter }
-    }
-}
-
-impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSession<'a> {
-    type Error = anyhow::Error;
-
-    fn try_from(value: StripeCreateCheckoutSessionParams<'a>) -> Result<Self, Self::Error> {
-        Ok(Self {
-            customer: value
-                .customer
-                .map(|customer_id| customer_id.try_into())
-                .transpose()?,
-            client_reference_id: value.client_reference_id,
-            mode: value.mode.map(Into::into),
-            line_items: value
-                .line_items
-                .map(|line_items| line_items.into_iter().map(Into::into).collect()),
-            payment_method_collection: value.payment_method_collection.map(Into::into),
-            subscription_data: value.subscription_data.map(Into::into),
-            success_url: value.success_url,
-            billing_address_collection: value.billing_address_collection.map(Into::into),
-            customer_update: value.customer_update.map(Into::into),
-            tax_id_collection: value.tax_id_collection.map(Into::into),
-            ..Default::default()
-        })
-    }
-}
-
-impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
-    fn from(value: StripeCheckoutSessionMode) -> Self {
-        match value {
-            StripeCheckoutSessionMode::Payment => Self::Payment,
-            StripeCheckoutSessionMode::Setup => Self::Setup,
-            StripeCheckoutSessionMode::Subscription => Self::Subscription,
-        }
-    }
-}
-
-impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
-    fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
-        Self {
-            price: value.price,
-            quantity: value.quantity,
-            ..Default::default()
-        }
-    }
-}
-
-impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
-    fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
-        match value {
-            StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
-            StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
-        }
-    }
-}
-
-impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
-    fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
-        Self {
-            trial_period_days: value.trial_period_days,
-            trial_settings: value.trial_settings.map(Into::into),
-            metadata: value.metadata,
-            ..Default::default()
-        }
-    }
-}
-
-impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
-    fn from(value: StripeSubscriptionTrialSettings) -> Self {
-        Self {
-            end_behavior: value.end_behavior.into(),
-        }
-    }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehavior>
-    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
-{
-    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
-        Self {
-            missing_payment_method: value.missing_payment_method.into(),
-        }
-    }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
-    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
-{
-    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
-        match value {
-            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
-            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
-                Self::CreateInvoice
-            }
-            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
-        }
-    }
-}
-
-impl From<CheckoutSession> for StripeCheckoutSession {
-    fn from(value: CheckoutSession) -> Self {
-        Self { url: value.url }
-    }
-}
-
-impl From<StripeBillingAddressCollection> for stripe::CheckoutSessionBillingAddressCollection {
-    fn from(value: StripeBillingAddressCollection) -> Self {
-        match value {
-            StripeBillingAddressCollection::Auto => {
-                stripe::CheckoutSessionBillingAddressCollection::Auto
-            }
-            StripeBillingAddressCollection::Required => {
-                stripe::CheckoutSessionBillingAddressCollection::Required
-            }
-        }
-    }
-}
-
-impl From<StripeCustomerUpdateAddress> for stripe::CreateCheckoutSessionCustomerUpdateAddress {
-    fn from(value: StripeCustomerUpdateAddress) -> Self {
-        match value {
-            StripeCustomerUpdateAddress::Auto => {
-                stripe::CreateCheckoutSessionCustomerUpdateAddress::Auto
-            }
-            StripeCustomerUpdateAddress::Never => {
-                stripe::CreateCheckoutSessionCustomerUpdateAddress::Never
-            }
-        }
-    }
-}
-
-impl From<StripeCustomerUpdateName> for stripe::CreateCheckoutSessionCustomerUpdateName {
-    fn from(value: StripeCustomerUpdateName) -> Self {
-        match value {
-            StripeCustomerUpdateName::Auto => stripe::CreateCheckoutSessionCustomerUpdateName::Auto,
-            StripeCustomerUpdateName::Never => {
-                stripe::CreateCheckoutSessionCustomerUpdateName::Never
-            }
-        }
-    }
-}
-
-impl From<StripeCustomerUpdateShipping> for stripe::CreateCheckoutSessionCustomerUpdateShipping {
-    fn from(value: StripeCustomerUpdateShipping) -> Self {
-        match value {
-            StripeCustomerUpdateShipping::Auto => {
-                stripe::CreateCheckoutSessionCustomerUpdateShipping::Auto
-            }
-            StripeCustomerUpdateShipping::Never => {
-                stripe::CreateCheckoutSessionCustomerUpdateShipping::Never
-            }
-        }
-    }
-}
-
-impl From<StripeCustomerUpdate> for stripe::CreateCheckoutSessionCustomerUpdate {
-    fn from(value: StripeCustomerUpdate) -> Self {
-        stripe::CreateCheckoutSessionCustomerUpdate {
-            address: value.address.map(Into::into),
-            name: value.name.map(Into::into),
-            shipping: value.shipping.map(Into::into),
-        }
-    }
-}
-
-impl From<StripeTaxIdCollection> for stripe::CreateCheckoutSessionTaxIdCollection {
-    fn from(value: StripeTaxIdCollection) -> Self {
-        stripe::CreateCheckoutSessionTaxIdCollection {
-            enabled: value.enabled,
-        }
-    }
-}

crates/collab/src/tests.rs 🔗

@@ -8,7 +8,6 @@ mod channel_buffer_tests;
 mod channel_guest_tests;
 mod channel_message_tests;
 mod channel_tests;
-// mod debug_panel_tests;
 mod editor_tests;
 mod following_tests;
 mod git_tests;
@@ -18,7 +17,6 @@ mod random_channel_buffer_tests;
 mod random_project_collaboration_tests;
 mod randomized_test_helpers;
 mod remote_editing_collaboration_tests;
-mod stripe_billing_tests;
 mod test_server;
 
 use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};

crates/collab/src/tests/stripe_billing_tests.rs 🔗

@@ -1,123 +0,0 @@
-use std::sync::Arc;
-
-use pretty_assertions::assert_eq;
-
-use crate::stripe_billing::StripeBilling;
-use crate::stripe_client::{FakeStripeClient, StripePrice, StripePriceId, StripePriceRecurring};
-
-fn make_stripe_billing() -> (StripeBilling, Arc<FakeStripeClient>) {
-    let stripe_client = Arc::new(FakeStripeClient::new());
-    let stripe_billing = StripeBilling::test(stripe_client.clone());
-
-    (stripe_billing, stripe_client)
-}
-
-#[gpui::test]
-async fn test_initialize() {
-    let (stripe_billing, stripe_client) = make_stripe_billing();
-
-    // Add test prices
-    let price1 = StripePrice {
-        id: StripePriceId("price_1".into()),
-        unit_amount: Some(1_000),
-        lookup_key: Some("zed-pro".to_string()),
-        recurring: None,
-    };
-    let price2 = StripePrice {
-        id: StripePriceId("price_2".into()),
-        unit_amount: Some(0),
-        lookup_key: Some("zed-free".to_string()),
-        recurring: None,
-    };
-    let price3 = StripePrice {
-        id: StripePriceId("price_3".into()),
-        unit_amount: Some(500),
-        lookup_key: None,
-        recurring: Some(StripePriceRecurring {
-            meter: Some("meter_1".to_string()),
-        }),
-    };
-    stripe_client
-        .prices
-        .lock()
-        .insert(price1.id.clone(), price1);
-    stripe_client
-        .prices
-        .lock()
-        .insert(price2.id.clone(), price2);
-    stripe_client
-        .prices
-        .lock()
-        .insert(price3.id.clone(), price3);
-
-    // Initialize the billing system
-    stripe_billing.initialize().await.unwrap();
-
-    // Verify that prices can be found by lookup key
-    let zed_pro_price_id = stripe_billing.zed_pro_price_id().await.unwrap();
-    assert_eq!(zed_pro_price_id.to_string(), "price_1");
-
-    let zed_free_price_id = stripe_billing.zed_free_price_id().await.unwrap();
-    assert_eq!(zed_free_price_id.to_string(), "price_2");
-
-    // Verify that a price can be found by lookup key
-    let zed_pro_price = stripe_billing
-        .find_price_by_lookup_key("zed-pro")
-        .await
-        .unwrap();
-    assert_eq!(zed_pro_price.id.to_string(), "price_1");
-    assert_eq!(zed_pro_price.unit_amount, Some(1_000));
-
-    // Verify that finding a non-existent lookup key returns an error
-    let result = stripe_billing
-        .find_price_by_lookup_key("non-existent")
-        .await;
-    assert!(result.is_err());
-}
-
-#[gpui::test]
-async fn test_find_or_create_customer_by_email() {
-    let (stripe_billing, stripe_client) = make_stripe_billing();
-
-    // Create a customer with an email that doesn't yet correspond to a customer.
-    {
-        let email = "user@example.com";
-
-        let customer_id = stripe_billing
-            .find_or_create_customer_by_email(Some(email))
-            .await
-            .unwrap();
-
-        let customer = stripe_client
-            .customers
-            .lock()
-            .get(&customer_id)
-            .unwrap()
-            .clone();
-        assert_eq!(customer.email.as_deref(), Some(email));
-    }
-
-    // Create a customer with an email that corresponds to an existing customer.
-    {
-        let email = "user2@example.com";
-
-        let existing_customer_id = stripe_billing
-            .find_or_create_customer_by_email(Some(email))
-            .await
-            .unwrap();
-
-        let customer_id = stripe_billing
-            .find_or_create_customer_by_email(Some(email))
-            .await
-            .unwrap();
-        assert_eq!(customer_id, existing_customer_id);
-
-        let customer = stripe_client
-            .customers
-            .lock()
-            .get(&customer_id)
-            .unwrap()
-            .clone();
-        assert_eq!(customer.email.as_deref(), Some(email));
-    }
-}

crates/collab/src/tests/test_server.rs 🔗

@@ -1,4 +1,3 @@
-use crate::stripe_client::FakeStripeClient;
 use crate::{
     AppState, Config,
     db::{NewUserParams, UserId, tests::TestDb},
@@ -569,9 +568,6 @@ impl TestServer {
             llm_db: None,
             livekit_client: Some(Arc::new(livekit_test_server.create_api_client())),
             blob_store_client: None,
-            real_stripe_client: None,
-            stripe_client: Some(Arc::new(FakeStripeClient::new())),
-            stripe_billing: None,
             executor,
             kinesis_client: None,
             config: Config {
@@ -608,7 +604,6 @@ impl TestServer {
                 auto_join_channel_id: None,
                 migrations_path: None,
                 seed_path: None,
-                stripe_api_key: None,
                 supermaven_admin_api_key: None,
                 user_backfiller_github_access_token: None,
                 kinesis_region: None,