@@ -1,21 +1,15 @@
use std::sync::Arc;
use anyhow::anyhow;
-use chrono::Utc;
use collections::HashMap;
use stripe::SubscriptionStatus;
use tokio::sync::RwLock;
-use uuid::Uuid;
use crate::Result;
-use crate::db::billing_subscription::SubscriptionKind;
use crate::stripe_client::{
- RealStripeClient, StripeAutomaticTax, StripeClient, StripeCreateMeterEventParams,
- StripeCreateMeterEventPayload, StripeCreateSubscriptionItems, StripeCreateSubscriptionParams,
- StripeCustomerId, StripePrice, StripePriceId, StripeSubscription, StripeSubscriptionId,
- StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, UpdateSubscriptionItems,
- UpdateSubscriptionParams,
+ RealStripeClient, StripeAutomaticTax, StripeClient, StripeCreateSubscriptionItems,
+ StripeCreateSubscriptionParams, StripeCustomerId, StripePrice, StripePriceId,
+ StripeSubscription,
};
pub struct StripeBilling {
@@ -94,30 +88,6 @@ impl StripeBilling {
.ok_or_else(|| crate::Error::Internal(anyhow!("no price found for {lookup_key:?}")))
}
- pub async fn determine_subscription_kind(
- &self,
- subscription: &StripeSubscription,
- ) -> Option<SubscriptionKind> {
- let zed_pro_price_id = self.zed_pro_price_id().await.ok()?;
- let zed_free_price_id = self.zed_free_price_id().await.ok()?;
-
- subscription.items.iter().find_map(|item| {
- let price = item.price.as_ref()?;
-
- if price.id == zed_pro_price_id {
- Some(if subscription.status == SubscriptionStatus::Trialing {
- SubscriptionKind::ZedProTrial
- } else {
- SubscriptionKind::ZedPro
- })
- } else if price.id == zed_free_price_id {
- Some(SubscriptionKind::ZedFree)
- } else {
- None
- }
- })
- }
-
/// Returns the Stripe customer associated with the provided email address, or creates a new customer, if one does
/// not already exist.
///
@@ -150,65 +120,6 @@ impl StripeBilling {
Ok(customer_id)
}
- pub async fn subscribe_to_price(
- &self,
- subscription_id: &StripeSubscriptionId,
- price: &StripePrice,
- ) -> Result<()> {
- let subscription = self.client.get_subscription(subscription_id).await?;
-
- if subscription_contains_price(&subscription, &price.id) {
- return Ok(());
- }
-
- const BILLING_THRESHOLD_IN_CENTS: i64 = 20 * 100;
-
- let price_per_unit = price.unit_amount.unwrap_or_default();
- let _units_for_billing_threshold = BILLING_THRESHOLD_IN_CENTS / price_per_unit;
-
- self.client
- .update_subscription(
- subscription_id,
- UpdateSubscriptionParams {
- items: Some(vec![UpdateSubscriptionItems {
- price: Some(price.id.clone()),
- }]),
- trial_settings: Some(StripeSubscriptionTrialSettings {
- end_behavior: StripeSubscriptionTrialSettingsEndBehavior {
- missing_payment_method: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel
- },
- }),
- },
- )
- .await?;
-
- Ok(())
- }
-
- pub async fn bill_model_request_usage(
- &self,
- customer_id: &StripeCustomerId,
- event_name: &str,
- requests: i32,
- ) -> Result<()> {
- let timestamp = Utc::now().timestamp();
- let idempotency_key = Uuid::new_v4();
-
- self.client
- .create_meter_event(StripeCreateMeterEventParams {
- identifier: &format!("model_requests/{}", idempotency_key),
- event_name,
- payload: StripeCreateMeterEventPayload {
- value: requests as u64,
- stripe_customer_id: customer_id,
- },
- timestamp: Some(timestamp),
- })
- .await?;
-
- Ok(())
- }
-
pub async fn subscribe_to_zed_free(
&self,
customer_id: StripeCustomerId,
@@ -243,14 +154,3 @@ impl StripeBilling {
Ok(subscription)
}
}
-
-fn subscription_contains_price(
- subscription: &StripeSubscription,
- price_id: &StripePriceId,
-) -> bool {
- subscription.items.iter().any(|item| {
- item.price
- .as_ref()
- .map_or(false, |price| price.id == *price_id)
- })
-}
@@ -1,14 +1,9 @@
use std::sync::Arc;
-use chrono::{Duration, Utc};
use pretty_assertions::assert_eq;
use crate::stripe_billing::StripeBilling;
-use crate::stripe_client::{
- FakeStripeClient, StripeCustomerId, StripeMeter, StripeMeterId, StripePrice, StripePriceId,
- StripePriceRecurring, StripeSubscription, StripeSubscriptionId, StripeSubscriptionItem,
- StripeSubscriptionItemId, UpdateSubscriptionItems,
-};
+use crate::stripe_client::{FakeStripeClient, StripePrice, StripePriceId, StripePriceRecurring};
fn make_stripe_billing() -> (StripeBilling, Arc<FakeStripeClient>) {
let stripe_client = Arc::new(FakeStripeClient::new());
@@ -21,24 +16,6 @@ fn make_stripe_billing() -> (StripeBilling, Arc<FakeStripeClient>) {
async fn test_initialize() {
let (stripe_billing, stripe_client) = make_stripe_billing();
- // Add test meters
- let meter1 = StripeMeter {
- id: StripeMeterId("meter_1".into()),
- event_name: "event_1".to_string(),
- };
- let meter2 = StripeMeter {
- id: StripeMeterId("meter_2".into()),
- event_name: "event_2".to_string(),
- };
- stripe_client
- .meters
- .lock()
- .insert(meter1.id.clone(), meter1);
- stripe_client
- .meters
- .lock()
- .insert(meter2.id.clone(), meter2);
-
// Add test prices
let price1 = StripePrice {
id: StripePriceId("price_1".into()),
@@ -144,217 +121,3 @@ async fn test_find_or_create_customer_by_email() {
assert_eq!(customer.email.as_deref(), Some(email));
}
}
-
-#[gpui::test]
-async fn test_subscribe_to_price() {
- let (stripe_billing, stripe_client) = make_stripe_billing();
-
- let price = StripePrice {
- id: StripePriceId("price_test".into()),
- unit_amount: Some(2000),
- lookup_key: Some("test-price".to_string()),
- recurring: None,
- };
- stripe_client
- .prices
- .lock()
- .insert(price.id.clone(), price.clone());
-
- let now = Utc::now();
- let subscription = StripeSubscription {
- id: StripeSubscriptionId("sub_test".into()),
- customer: StripeCustomerId("cus_test".into()),
- status: stripe::SubscriptionStatus::Active,
- current_period_start: now.timestamp(),
- current_period_end: (now + Duration::days(30)).timestamp(),
- items: vec![],
- cancel_at: None,
- cancellation_details: None,
- };
- stripe_client
- .subscriptions
- .lock()
- .insert(subscription.id.clone(), subscription.clone());
-
- stripe_billing
- .subscribe_to_price(&subscription.id, &price)
- .await
- .unwrap();
-
- let update_subscription_calls = stripe_client
- .update_subscription_calls
- .lock()
- .iter()
- .map(|(id, params)| (id.clone(), params.clone()))
- .collect::<Vec<_>>();
- assert_eq!(update_subscription_calls.len(), 1);
- assert_eq!(update_subscription_calls[0].0, subscription.id);
- assert_eq!(
- update_subscription_calls[0].1.items,
- Some(vec![UpdateSubscriptionItems {
- price: Some(price.id.clone())
- }])
- );
-
- // Subscribing to a price that is already on the subscription is a no-op.
- {
- let now = Utc::now();
- let subscription = StripeSubscription {
- id: StripeSubscriptionId("sub_test".into()),
- customer: StripeCustomerId("cus_test".into()),
- status: stripe::SubscriptionStatus::Active,
- current_period_start: now.timestamp(),
- current_period_end: (now + Duration::days(30)).timestamp(),
- items: vec![StripeSubscriptionItem {
- id: StripeSubscriptionItemId("si_test".into()),
- price: Some(price.clone()),
- }],
- cancel_at: None,
- cancellation_details: None,
- };
- stripe_client
- .subscriptions
- .lock()
- .insert(subscription.id.clone(), subscription.clone());
-
- stripe_billing
- .subscribe_to_price(&subscription.id, &price)
- .await
- .unwrap();
-
- assert_eq!(stripe_client.update_subscription_calls.lock().len(), 1);
- }
-}
-
-#[gpui::test]
-async fn test_subscribe_to_zed_free() {
- let (stripe_billing, stripe_client) = make_stripe_billing();
-
- let zed_pro_price = StripePrice {
- id: StripePriceId("price_1".into()),
- unit_amount: Some(0),
- lookup_key: Some("zed-pro".to_string()),
- recurring: None,
- };
- stripe_client
- .prices
- .lock()
- .insert(zed_pro_price.id.clone(), zed_pro_price.clone());
- let zed_free_price = StripePrice {
- id: StripePriceId("price_2".into()),
- unit_amount: Some(0),
- lookup_key: Some("zed-free".to_string()),
- recurring: None,
- };
- stripe_client
- .prices
- .lock()
- .insert(zed_free_price.id.clone(), zed_free_price.clone());
-
- stripe_billing.initialize().await.unwrap();
-
- // Customer is subscribed to Zed Free when not already subscribed to a plan.
- {
- let customer_id = StripeCustomerId("cus_no_plan".into());
-
- let subscription = stripe_billing
- .subscribe_to_zed_free(customer_id)
- .await
- .unwrap();
-
- assert_eq!(subscription.items[0].price.as_ref(), Some(&zed_free_price));
- }
-
- // Customer is not subscribed to Zed Free when they already have an active subscription.
- {
- let customer_id = StripeCustomerId("cus_active_subscription".into());
-
- let now = Utc::now();
- let existing_subscription = StripeSubscription {
- id: StripeSubscriptionId("sub_existing_active".into()),
- customer: customer_id.clone(),
- status: stripe::SubscriptionStatus::Active,
- current_period_start: now.timestamp(),
- current_period_end: (now + Duration::days(30)).timestamp(),
- items: vec![StripeSubscriptionItem {
- id: StripeSubscriptionItemId("si_test".into()),
- price: Some(zed_pro_price.clone()),
- }],
- cancel_at: None,
- cancellation_details: None,
- };
- stripe_client.subscriptions.lock().insert(
- existing_subscription.id.clone(),
- existing_subscription.clone(),
- );
-
- let subscription = stripe_billing
- .subscribe_to_zed_free(customer_id)
- .await
- .unwrap();
-
- assert_eq!(subscription, existing_subscription);
- }
-
- // Customer is not subscribed to Zed Free when they already have a trial subscription.
- {
- let customer_id = StripeCustomerId("cus_trial_subscription".into());
-
- let now = Utc::now();
- let existing_subscription = StripeSubscription {
- id: StripeSubscriptionId("sub_existing_trial".into()),
- customer: customer_id.clone(),
- status: stripe::SubscriptionStatus::Trialing,
- current_period_start: now.timestamp(),
- current_period_end: (now + Duration::days(14)).timestamp(),
- items: vec![StripeSubscriptionItem {
- id: StripeSubscriptionItemId("si_test".into()),
- price: Some(zed_pro_price.clone()),
- }],
- cancel_at: None,
- cancellation_details: None,
- };
- stripe_client.subscriptions.lock().insert(
- existing_subscription.id.clone(),
- existing_subscription.clone(),
- );
-
- let subscription = stripe_billing
- .subscribe_to_zed_free(customer_id)
- .await
- .unwrap();
-
- assert_eq!(subscription, existing_subscription);
- }
-}
-
-#[gpui::test]
-async fn test_bill_model_request_usage() {
- let (stripe_billing, stripe_client) = make_stripe_billing();
-
- let customer_id = StripeCustomerId("cus_test".into());
-
- stripe_billing
- .bill_model_request_usage(&customer_id, "some_model/requests", 73)
- .await
- .unwrap();
-
- let create_meter_event_calls = stripe_client
- .create_meter_event_calls
- .lock()
- .iter()
- .cloned()
- .collect::<Vec<_>>();
- assert_eq!(create_meter_event_calls.len(), 1);
- assert!(
- create_meter_event_calls[0]
- .identifier
- .starts_with("model_requests/")
- );
- assert_eq!(create_meter_event_calls[0].stripe_customer_id, customer_id);
- assert_eq!(
- create_meter_event_calls[0].event_name.as_ref(),
- "some_model/requests"
- );
- assert_eq!(create_meter_event_calls[0].value, 73);
-}