collab: Remove `POST /billing/subscriptions/manage` endpoint (#34986)

Marshall Bowers created

This PR removes the `POST /billing/subscriptions/manage` endpoint, as it
has been moved to `cloud.zed.dev`.

Release Notes:

- N/A

Change summary

crates/collab/src/api/billing.rs | 272 ---------------------------------
1 file changed, 7 insertions(+), 265 deletions(-)

Detailed changes

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

@@ -5,16 +5,8 @@ use collections::{HashMap, HashSet};
 use reqwest::StatusCode;
 use sea_orm::ActiveValue;
 use serde::{Deserialize, Serialize};
-use std::{str::FromStr, sync::Arc, time::Duration};
-use stripe::{
-    BillingPortalSession, CancellationDetailsReason, CreateBillingPortalSession,
-    CreateBillingPortalSessionFlowData, CreateBillingPortalSessionFlowDataAfterCompletion,
-    CreateBillingPortalSessionFlowDataAfterCompletionRedirect,
-    CreateBillingPortalSessionFlowDataSubscriptionUpdateConfirm,
-    CreateBillingPortalSessionFlowDataSubscriptionUpdateConfirmItems,
-    CreateBillingPortalSessionFlowDataType, CustomerId, EventObject, EventType, ListEvents,
-    PaymentMethod, Subscription, SubscriptionId, SubscriptionStatus,
-};
+use std::{sync::Arc, time::Duration};
+use stripe::{CancellationDetailsReason, EventObject, EventType, ListEvents, SubscriptionStatus};
 use util::{ResultExt, maybe};
 use zed_llm_client::LanguageModelProvider;
 
@@ -31,7 +23,7 @@ use crate::{AppState, Error, Result};
 use crate::{db::UserId, llm::db::LlmDatabase};
 use crate::{
     db::{
-        BillingSubscriptionId, CreateBillingCustomerParams, CreateBillingSubscriptionParams,
+        CreateBillingCustomerParams, CreateBillingSubscriptionParams,
         CreateProcessedStripeEventParams, UpdateBillingCustomerParams,
         UpdateBillingSubscriptionParams, billing_customer,
     },
@@ -39,260 +31,10 @@ use crate::{
 };
 
 pub fn router() -> Router {
-    Router::new()
-        .route(
-            "/billing/subscriptions/manage",
-            post(manage_billing_subscription),
-        )
-        .route(
-            "/billing/subscriptions/sync",
-            post(sync_billing_subscription),
-        )
-}
-
-#[derive(Debug, PartialEq, Deserialize)]
-#[serde(rename_all = "snake_case")]
-enum ManageSubscriptionIntent {
-    /// The user intends to manage their subscription.
-    ///
-    /// This will open the Stripe billing portal without putting the user in a specific flow.
-    ManageSubscription,
-    /// The user intends to update their payment method.
-    UpdatePaymentMethod,
-    /// The user intends to upgrade to Zed Pro.
-    UpgradeToPro,
-    /// The user intends to cancel their subscription.
-    Cancel,
-    /// The user intends to stop the cancellation of their subscription.
-    StopCancellation,
-}
-
-#[derive(Debug, Deserialize)]
-struct ManageBillingSubscriptionBody {
-    github_user_id: i32,
-    intent: ManageSubscriptionIntent,
-    /// The ID of the subscription to manage.
-    subscription_id: BillingSubscriptionId,
-    redirect_to: Option<String>,
-}
-
-#[derive(Debug, Serialize)]
-struct ManageBillingSubscriptionResponse {
-    billing_portal_session_url: Option<String>,
-}
-
-/// Initiates a Stripe customer portal session for managing a billing subscription.
-async fn manage_billing_subscription(
-    Extension(app): Extension<Arc<AppState>>,
-    extract::Json(body): extract::Json<ManageBillingSubscriptionBody>,
-) -> Result<Json<ManageBillingSubscriptionResponse>> {
-    let user = app
-        .db
-        .get_user_by_github_user_id(body.github_user_id)
-        .await?
-        .context("user not found")?;
-
-    let Some(stripe_client) = app.real_stripe_client.clone() else {
-        log::error!("failed to retrieve Stripe client");
-        Err(Error::http(
-            StatusCode::NOT_IMPLEMENTED,
-            "not supported".into(),
-        ))?
-    };
-
-    let Some(stripe_billing) = app.stripe_billing.clone() else {
-        log::error!("failed to retrieve Stripe billing object");
-        Err(Error::http(
-            StatusCode::NOT_IMPLEMENTED,
-            "not supported".into(),
-        ))?
-    };
-
-    let customer = app
-        .db
-        .get_billing_customer_by_user_id(user.id)
-        .await?
-        .context("billing customer not found")?;
-    let customer_id = CustomerId::from_str(&customer.stripe_customer_id)
-        .context("failed to parse customer ID")?;
-
-    let subscription = app
-        .db
-        .get_billing_subscription_by_id(body.subscription_id)
-        .await?
-        .context("subscription not found")?;
-    let subscription_id = SubscriptionId::from_str(&subscription.stripe_subscription_id)
-        .context("failed to parse subscription ID")?;
-
-    if body.intent == ManageSubscriptionIntent::StopCancellation {
-        let updated_stripe_subscription = Subscription::update(
-            &stripe_client,
-            &subscription_id,
-            stripe::UpdateSubscription {
-                cancel_at_period_end: Some(false),
-                ..Default::default()
-            },
-        )
-        .await?;
-
-        app.db
-            .update_billing_subscription(
-                subscription.id,
-                &UpdateBillingSubscriptionParams {
-                    stripe_cancel_at: ActiveValue::set(
-                        updated_stripe_subscription
-                            .cancel_at
-                            .and_then(|cancel_at| DateTime::from_timestamp(cancel_at, 0))
-                            .map(|time| time.naive_utc()),
-                    ),
-                    ..Default::default()
-                },
-            )
-            .await?;
-
-        return Ok(Json(ManageBillingSubscriptionResponse {
-            billing_portal_session_url: None,
-        }));
-    }
-
-    let flow = match body.intent {
-        ManageSubscriptionIntent::ManageSubscription => None,
-        ManageSubscriptionIntent::UpgradeToPro => {
-            let zed_pro_price_id: stripe::PriceId =
-                stripe_billing.zed_pro_price_id().await?.try_into()?;
-            let zed_free_price_id: stripe::PriceId =
-                stripe_billing.zed_free_price_id().await?.try_into()?;
-
-            let stripe_subscription =
-                Subscription::retrieve(&stripe_client, &subscription_id, &[]).await?;
-
-            let is_on_zed_pro_trial = stripe_subscription.status == SubscriptionStatus::Trialing
-                && stripe_subscription.items.data.iter().any(|item| {
-                    item.price
-                        .as_ref()
-                        .map_or(false, |price| price.id == zed_pro_price_id)
-                });
-            if is_on_zed_pro_trial {
-                let payment_methods = PaymentMethod::list(
-                    &stripe_client,
-                    &stripe::ListPaymentMethods {
-                        customer: Some(stripe_subscription.customer.id()),
-                        ..Default::default()
-                    },
-                )
-                .await?;
-
-                let has_payment_method = !payment_methods.data.is_empty();
-                if !has_payment_method {
-                    return Err(Error::http(
-                        StatusCode::BAD_REQUEST,
-                        "missing payment method".into(),
-                    ));
-                }
-
-                // If the user is already on a Zed Pro trial and wants to upgrade to Pro, we just need to end their trial early.
-                Subscription::update(
-                    &stripe_client,
-                    &stripe_subscription.id,
-                    stripe::UpdateSubscription {
-                        trial_end: Some(stripe::Scheduled::now()),
-                        ..Default::default()
-                    },
-                )
-                .await?;
-
-                return Ok(Json(ManageBillingSubscriptionResponse {
-                    billing_portal_session_url: None,
-                }));
-            }
-
-            let subscription_item_to_update = stripe_subscription
-                .items
-                .data
-                .iter()
-                .find_map(|item| {
-                    let price = item.price.as_ref()?;
-
-                    if price.id == zed_free_price_id {
-                        Some(item.id.clone())
-                    } else {
-                        None
-                    }
-                })
-                .context("No subscription item to update")?;
-
-            Some(CreateBillingPortalSessionFlowData {
-                type_: CreateBillingPortalSessionFlowDataType::SubscriptionUpdateConfirm,
-                subscription_update_confirm: Some(
-                    CreateBillingPortalSessionFlowDataSubscriptionUpdateConfirm {
-                        subscription: subscription.stripe_subscription_id,
-                        items: vec![
-                            CreateBillingPortalSessionFlowDataSubscriptionUpdateConfirmItems {
-                                id: subscription_item_to_update.to_string(),
-                                price: Some(zed_pro_price_id.to_string()),
-                                quantity: Some(1),
-                            },
-                        ],
-                        discounts: None,
-                    },
-                ),
-                ..Default::default()
-            })
-        }
-        ManageSubscriptionIntent::UpdatePaymentMethod => Some(CreateBillingPortalSessionFlowData {
-            type_: CreateBillingPortalSessionFlowDataType::PaymentMethodUpdate,
-            after_completion: Some(CreateBillingPortalSessionFlowDataAfterCompletion {
-                type_: stripe::CreateBillingPortalSessionFlowDataAfterCompletionType::Redirect,
-                redirect: Some(CreateBillingPortalSessionFlowDataAfterCompletionRedirect {
-                    return_url: format!(
-                        "{}{path}",
-                        app.config.zed_dot_dev_url(),
-                        path = body.redirect_to.unwrap_or_else(|| "/account".to_string())
-                    ),
-                }),
-                ..Default::default()
-            }),
-            ..Default::default()
-        }),
-        ManageSubscriptionIntent::Cancel => {
-            if subscription.kind == Some(SubscriptionKind::ZedFree) {
-                return Err(Error::http(
-                    StatusCode::BAD_REQUEST,
-                    "free subscription cannot be canceled".into(),
-                ));
-            }
-
-            Some(CreateBillingPortalSessionFlowData {
-                type_: CreateBillingPortalSessionFlowDataType::SubscriptionCancel,
-                after_completion: Some(CreateBillingPortalSessionFlowDataAfterCompletion {
-                    type_: stripe::CreateBillingPortalSessionFlowDataAfterCompletionType::Redirect,
-                    redirect: Some(CreateBillingPortalSessionFlowDataAfterCompletionRedirect {
-                        return_url: format!("{}/account", app.config.zed_dot_dev_url()),
-                    }),
-                    ..Default::default()
-                }),
-                subscription_cancel: Some(
-                    stripe::CreateBillingPortalSessionFlowDataSubscriptionCancel {
-                        subscription: subscription.stripe_subscription_id,
-                        retention: None,
-                    },
-                ),
-                ..Default::default()
-            })
-        }
-        ManageSubscriptionIntent::StopCancellation => unreachable!(),
-    };
-
-    let mut params = CreateBillingPortalSession::new(customer_id);
-    params.flow_data = flow;
-    let return_url = format!("{}/account", app.config.zed_dot_dev_url());
-    params.return_url = Some(&return_url);
-
-    let session = BillingPortalSession::create(&stripe_client, params).await?;
-
-    Ok(Json(ManageBillingSubscriptionResponse {
-        billing_portal_session_url: Some(session.url),
-    }))
+    Router::new().route(
+        "/billing/subscriptions/sync",
+        post(sync_billing_subscription),
+    )
 }
 
 #[derive(Debug, Deserialize)]