collab: Remove Stripe code (#36275)
This PR removes the code for integrating with Stripe from Collab. All of these concerns are now handled by Cloud. Release Notes: - N/A
This commit is contained in:
parent
3e0a755486
commit
239e479aed
16 changed files with 2 additions and 1738 deletions
159
Cargo.lock
generated
159
Cargo.lock
generated
|
@ -1262,26 +1262,6 @@ dependencies = [
|
||||||
"syn 2.0.101",
|
"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]]
|
[[package]]
|
||||||
name = "async-tar"
|
name = "async-tar"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -2083,12 +2063,6 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
|
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
|
@ -3281,7 +3255,6 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assistant_context",
|
"assistant_context",
|
||||||
"assistant_slash_command",
|
"assistant_slash_command",
|
||||||
"async-stripe",
|
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
"audio",
|
"audio",
|
||||||
|
@ -3308,7 +3281,6 @@ dependencies = [
|
||||||
"dap_adapters",
|
"dap_adapters",
|
||||||
"dashmap 6.1.0",
|
"dashmap 6.1.0",
|
||||||
"debugger_ui",
|
"debugger_ui",
|
||||||
"derive_more 0.99.19",
|
|
||||||
"editor",
|
"editor",
|
||||||
"envy",
|
"envy",
|
||||||
"extension",
|
"extension",
|
||||||
|
@ -3870,7 +3842,7 @@ dependencies = [
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
"rustybuzz 0.14.1",
|
"rustybuzz 0.14.1",
|
||||||
"self_cell",
|
"self_cell",
|
||||||
"smol_str 0.2.2",
|
"smol_str",
|
||||||
"swash",
|
"swash",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
"ttf-parser 0.21.1",
|
"ttf-parser 0.21.1",
|
||||||
|
@ -6374,17 +6346,6 @@ dependencies = [
|
||||||
"windows-targets 0.48.5",
|
"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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -7988,27 +7949,6 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
|
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]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -8487,12 +8427,6 @@ version = "2.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "infer"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inherent"
|
name = "inherent"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
@ -10269,7 +10203,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"range-map",
|
"range-map",
|
||||||
"scroll",
|
"scroll",
|
||||||
"smart-default 0.7.1",
|
"smart-default",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -13143,19 +13077,6 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
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]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
@ -13177,16 +13098,6 @@ dependencies = [
|
||||||
"rand_core 0.9.3",
|
"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]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -13207,15 +13118,6 @@ dependencies = [
|
||||||
"rand_core 0.9.3",
|
"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]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
@ -13234,15 +13136,6 @@ dependencies = [
|
||||||
"getrandom 0.3.2",
|
"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]]
|
[[package]]
|
||||||
name = "range-map"
|
name = "range-map"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -14897,28 +14790,6 @@ dependencies = [
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "serde_repr"
|
name = "serde_repr"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
|
@ -15295,17 +15166,6 @@ dependencies = [
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "smart-default"
|
name = "smart-default"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -15334,15 +15194,6 @@ dependencies = [
|
||||||
"futures-lite 2.6.0",
|
"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]]
|
[[package]]
|
||||||
name = "smol_str"
|
name = "smol_str"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -18191,12 +18042,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -667,20 +667,6 @@ workspace-hack = "0.1.0"
|
||||||
yawc = { git = "https://github.com/deviant-forks/yawc", rev = "1899688f3e69ace4545aceb97b2a13881cf26142" }
|
yawc = { git = "https://github.com/deviant-forks/yawc", rev = "1899688f3e69ace4545aceb97b2a13881cf26142" }
|
||||||
zstd = "0.11"
|
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]
|
[workspace.dependencies.windows]
|
||||||
version = "0.61"
|
version = "0.61"
|
||||||
features = [
|
features = [
|
||||||
|
|
|
@ -19,7 +19,6 @@ test-support = ["sqlite"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-stripe.workspace = true
|
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
async-tungstenite.workspace = true
|
async-tungstenite.workspace = true
|
||||||
aws-config = { version = "1.1.5" }
|
aws-config = { version = "1.1.5" }
|
||||||
|
@ -33,7 +32,6 @@ clock.workspace = true
|
||||||
cloud_llm_client.workspace = true
|
cloud_llm_client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
dashmap.workspace = true
|
dashmap.workspace = true
|
||||||
derive_more.workspace = true
|
|
||||||
envy = "0.4.2"
|
envy = "0.4.2"
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
@ -134,6 +132,3 @@ util.workspace = true
|
||||||
workspace = { workspace = true, features = ["test-support"] }
|
workspace = { workspace = true, features = ["test-support"] }
|
||||||
worktree = { workspace = true, features = ["test-support"] }
|
worktree = { workspace = true, features = ["test-support"] }
|
||||||
zlog.workspace = true
|
zlog.workspace = true
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
|
||||||
ignored = ["async-stripe"]
|
|
||||||
|
|
|
@ -219,12 +219,6 @@ spec:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: slack
|
name: slack
|
||||||
key: panics_webhook
|
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
|
- name: COMPLETE_WITH_LANGUAGE_MODEL_RATE_LIMIT_PER_HOUR
|
||||||
value: "1000"
|
value: "1000"
|
||||||
- name: SUPERMAVEN_ADMIN_API_KEY
|
- name: SUPERMAVEN_ADMIN_API_KEY
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod billing;
|
|
||||||
pub mod contributors;
|
pub mod contributors;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod extensions;
|
pub mod extensions;
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::db::{BillingCustomerId, BillingSubscriptionId};
|
use crate::db::{BillingCustomerId, BillingSubscriptionId};
|
||||||
use crate::stripe_client;
|
|
||||||
use chrono::{Datelike as _, NaiveDate, Utc};
|
use chrono::{Datelike as _, NaiveDate, Utc};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -160,17 +159,3 @@ pub enum StripeCancellationReason {
|
||||||
#[sea_orm(string_value = "payment_failed")]
|
#[sea_orm(string_value = "payment_failed")]
|
||||||
PaymentFailed,
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ pub mod llm;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod seed;
|
pub mod seed;
|
||||||
pub mod stripe_billing;
|
|
||||||
pub mod stripe_client;
|
|
||||||
pub mod user_backfiller;
|
pub mod user_backfiller;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -27,16 +25,12 @@ use serde::Deserialize;
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
use util::ResultExt;
|
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 type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Http(StatusCode, String, HeaderMap),
|
Http(StatusCode, String, HeaderMap),
|
||||||
Database(sea_orm::error::DbErr),
|
Database(sea_orm::error::DbErr),
|
||||||
Internal(anyhow::Error),
|
Internal(anyhow::Error),
|
||||||
Stripe(stripe::StripeError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<anyhow::Error> for Error {
|
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 {
|
impl From<axum::Error> for Error {
|
||||||
fn from(error: axum::Error) -> Self {
|
fn from(error: axum::Error) -> Self {
|
||||||
Self::Internal(error.into())
|
Self::Internal(error.into())
|
||||||
|
@ -104,14 +92,6 @@ impl IntoResponse for Error {
|
||||||
);
|
);
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
|
(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::Http(code, message, _headers) => (code, message).fmt(f),
|
||||||
Error::Database(error) => error.fmt(f),
|
Error::Database(error) => error.fmt(f),
|
||||||
Error::Internal(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::Http(code, message, _) => write!(f, "{code}: {message}"),
|
||||||
Error::Database(error) => error.fmt(f),
|
Error::Database(error) => error.fmt(f),
|
||||||
Error::Internal(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 zed_client_checksum_seed: Option<String>,
|
||||||
pub slack_panics_webhook: Option<String>,
|
pub slack_panics_webhook: Option<String>,
|
||||||
pub auto_join_channel_id: Option<ChannelId>,
|
pub auto_join_channel_id: Option<ChannelId>,
|
||||||
pub stripe_api_key: Option<String>,
|
|
||||||
pub supermaven_admin_api_key: Option<Arc<str>>,
|
pub supermaven_admin_api_key: Option<Arc<str>>,
|
||||||
pub user_backfiller_github_access_token: Option<Arc<str>>,
|
pub user_backfiller_github_access_token: Option<Arc<str>>,
|
||||||
}
|
}
|
||||||
|
@ -234,7 +211,6 @@ impl Config {
|
||||||
auto_join_channel_id: None,
|
auto_join_channel_id: None,
|
||||||
migrations_path: None,
|
migrations_path: None,
|
||||||
seed_path: None,
|
seed_path: None,
|
||||||
stripe_api_key: None,
|
|
||||||
supermaven_admin_api_key: None,
|
supermaven_admin_api_key: None,
|
||||||
user_backfiller_github_access_token: None,
|
user_backfiller_github_access_token: None,
|
||||||
kinesis_region: None,
|
kinesis_region: None,
|
||||||
|
@ -269,11 +245,6 @@ pub struct AppState {
|
||||||
pub llm_db: Option<Arc<LlmDatabase>>,
|
pub llm_db: Option<Arc<LlmDatabase>>,
|
||||||
pub livekit_client: Option<Arc<dyn livekit_api::Client>>,
|
pub livekit_client: Option<Arc<dyn livekit_api::Client>>,
|
||||||
pub blob_store_client: Option<aws_sdk_s3::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 executor: Executor,
|
||||||
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
|
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
@ -316,18 +287,11 @@ impl AppState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = Arc::new(db);
|
let db = Arc::new(db);
|
||||||
let stripe_client = build_stripe_client(&config).map(Arc::new).log_err();
|
|
||||||
let this = Self {
|
let this = Self {
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
llm_db,
|
llm_db,
|
||||||
livekit_client,
|
livekit_client,
|
||||||
blob_store_client: build_blob_store_client(&config).await.log_err(),
|
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,
|
executor,
|
||||||
kinesis_client: if config.kinesis_access_key.is_some() {
|
kinesis_client: if config.kinesis_access_key.is_some() {
|
||||||
build_kinesis_client(&config).await.log_err()
|
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> {
|
async fn build_blob_store_client(config: &Config) -> anyhow::Result<aws_sdk_s3::Client> {
|
||||||
let keys = aws_sdk_s3::config::Credentials::new(
|
let keys = aws_sdk_s3::config::Credentials::new(
|
||||||
config
|
config
|
||||||
|
|
|
@ -102,13 +102,6 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let state = AppState::new(config, Executor::Production).await?;
|
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() {
|
if mode.is_collab() {
|
||||||
state.db.purge_old_embeddings().await.trace_err();
|
state.db.purge_old_embeddings().await.trace_err();
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>;
|
|
||||||
}
|
|
|
@ -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()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ mod channel_buffer_tests;
|
||||||
mod channel_guest_tests;
|
mod channel_guest_tests;
|
||||||
mod channel_message_tests;
|
mod channel_message_tests;
|
||||||
mod channel_tests;
|
mod channel_tests;
|
||||||
// mod debug_panel_tests;
|
|
||||||
mod editor_tests;
|
mod editor_tests;
|
||||||
mod following_tests;
|
mod following_tests;
|
||||||
mod git_tests;
|
mod git_tests;
|
||||||
|
@ -18,7 +17,6 @@ mod random_channel_buffer_tests;
|
||||||
mod random_project_collaboration_tests;
|
mod random_project_collaboration_tests;
|
||||||
mod randomized_test_helpers;
|
mod randomized_test_helpers;
|
||||||
mod remote_editing_collaboration_tests;
|
mod remote_editing_collaboration_tests;
|
||||||
mod stripe_billing_tests;
|
|
||||||
mod test_server;
|
mod test_server;
|
||||||
|
|
||||||
use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
|
use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::stripe_client::FakeStripeClient;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, Config,
|
AppState, Config,
|
||||||
db::{NewUserParams, UserId, tests::TestDb},
|
db::{NewUserParams, UserId, tests::TestDb},
|
||||||
|
@ -569,9 +568,6 @@ impl TestServer {
|
||||||
llm_db: None,
|
llm_db: None,
|
||||||
livekit_client: Some(Arc::new(livekit_test_server.create_api_client())),
|
livekit_client: Some(Arc::new(livekit_test_server.create_api_client())),
|
||||||
blob_store_client: None,
|
blob_store_client: None,
|
||||||
real_stripe_client: None,
|
|
||||||
stripe_client: Some(Arc::new(FakeStripeClient::new())),
|
|
||||||
stripe_billing: None,
|
|
||||||
executor,
|
executor,
|
||||||
kinesis_client: None,
|
kinesis_client: None,
|
||||||
config: Config {
|
config: Config {
|
||||||
|
@ -608,7 +604,6 @@ impl TestServer {
|
||||||
auto_join_channel_id: None,
|
auto_join_channel_id: None,
|
||||||
migrations_path: None,
|
migrations_path: None,
|
||||||
seed_path: None,
|
seed_path: None,
|
||||||
stripe_api_key: None,
|
|
||||||
supermaven_admin_api_key: None,
|
supermaven_admin_api_key: None,
|
||||||
user_backfiller_github_access_token: None,
|
user_backfiller_github_access_token: None,
|
||||||
kinesis_region: None,
|
kinesis_region: None,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue