collab: Allow starting a trial from Zed Free (#30970)

This PR makes it so a user can initiate a checkout session for a Zed Pro
trial while on the Zed Free plan.

Release Notes:

- N/A

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Marshall Bowers 2025-05-19 15:39:01 -04:00 committed by GitHub
parent 5c4f9e57d8
commit b440e1a467
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 29 deletions

View file

@ -280,7 +280,7 @@ async fn list_billing_subscriptions(
}))
}
#[derive(Debug, Clone, Copy, Deserialize)]
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ProductCode {
ZedPro,
@ -325,12 +325,17 @@ async fn create_billing_subscription(
))?
};
if app.db.has_active_billing_subscription(user.id).await? {
if let Some(existing_subscription) = app.db.get_active_billing_subscription(user.id).await? {
let is_checkout_allowed = body.product == ProductCode::ZedProTrial
&& existing_subscription.kind == Some(SubscriptionKind::ZedFree);
if !is_checkout_allowed {
return Err(Error::http(
StatusCode::CONFLICT,
"user already has an active subscription".into(),
));
}
}
let existing_billing_customer = app.db.get_billing_customer_by_user_id(user.id).await?;
if let Some(existing_billing_customer) = &existing_billing_customer {
@ -1135,6 +1140,29 @@ async fn sync_subscription(
},
)
.await?;
} else {
if let Some(existing_subscription) = app
.db
.get_active_billing_subscription(billing_customer.user_id)
.await?
{
if existing_subscription.kind == Some(SubscriptionKind::ZedFree)
&& subscription_kind == Some(SubscriptionKind::ZedProTrial)
{
let stripe_subscription_id = existing_subscription
.stripe_subscription_id
.parse::<stripe::SubscriptionId>()
.context("failed to parse Stripe subscription ID from database")?;
Subscription::cancel(
&stripe_client,
&stripe_subscription_id,
stripe::CancelSubscription {
invoice_now: None,
..Default::default()
},
)
.await?;
} else {
// If the user already has an active billing subscription, ignore the
// event and return an `Ok` to signal that it was processed
@ -1150,11 +1178,7 @@ async fn sync_subscription(
// 5. User ends up with no subscriptions
//
// In theory this situation shouldn't arise as we try to process the events in the order they occur.
if app
.db
.has_active_billing_subscription(billing_customer.user_id)
.await?
{
log::info!(
"user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}",
user_id = billing_customer.user_id,
@ -1162,6 +1186,7 @@ async fn sync_subscription(
);
return Ok(billing_customer);
}
}
app.db
.create_billing_subscription(&CreateBillingSubscriptionParams {

View file

@ -236,7 +236,9 @@ impl Database {
.filter(
billing_customer::Column::UserId.eq(user_id).and(
billing_subscription::Column::StripeSubscriptionStatus
.eq(StripeSubscriptionStatus::Active),
.eq(StripeSubscriptionStatus::Active)
.or(billing_subscription::Column::StripeSubscriptionStatus
.eq(StripeSubscriptionStatus::Trialing)),
),
)
.count(&*tx)