billing_subscription.rs

  1use crate::db::{BillingCustomerId, BillingSubscriptionId};
  2use chrono::{Datelike as _, NaiveDate, Utc};
  3use sea_orm::entity::prelude::*;
  4use serde::Serialize;
  5
  6/// A billing subscription.
  7#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
  8#[sea_orm(table_name = "billing_subscriptions")]
  9pub struct Model {
 10    #[sea_orm(primary_key)]
 11    pub id: BillingSubscriptionId,
 12    pub billing_customer_id: BillingCustomerId,
 13    pub kind: Option<SubscriptionKind>,
 14    pub stripe_subscription_id: String,
 15    pub stripe_subscription_status: StripeSubscriptionStatus,
 16    pub stripe_cancel_at: Option<DateTime>,
 17    pub stripe_cancellation_reason: Option<StripeCancellationReason>,
 18    pub stripe_current_period_start: Option<i64>,
 19    pub stripe_current_period_end: Option<i64>,
 20    pub created_at: DateTime,
 21}
 22
 23impl Model {
 24    pub fn current_period_start_at(&self) -> Option<DateTimeUtc> {
 25        let period_start = self.stripe_current_period_start?;
 26        chrono::DateTime::from_timestamp(period_start, 0)
 27    }
 28
 29    pub fn current_period_end_at(&self) -> Option<DateTimeUtc> {
 30        let period_end = self.stripe_current_period_end?;
 31        chrono::DateTime::from_timestamp(period_end, 0)
 32    }
 33
 34    pub fn current_period(
 35        subscription: Option<Self>,
 36        is_staff: bool,
 37    ) -> Option<(DateTimeUtc, DateTimeUtc)> {
 38        if is_staff {
 39            let now = Utc::now();
 40            let year = now.year();
 41            let month = now.month();
 42
 43            let first_day_of_this_month =
 44                NaiveDate::from_ymd_opt(year, month, 1)?.and_hms_opt(0, 0, 0)?;
 45
 46            let next_month = if month == 12 { 1 } else { month + 1 };
 47            let next_month_year = if month == 12 { year + 1 } else { year };
 48            let first_day_of_next_month =
 49                NaiveDate::from_ymd_opt(next_month_year, next_month, 1)?.and_hms_opt(23, 59, 59)?;
 50
 51            let last_day_of_this_month = first_day_of_next_month - chrono::Days::new(1);
 52
 53            Some((
 54                first_day_of_this_month.and_utc(),
 55                last_day_of_this_month.and_utc(),
 56            ))
 57        } else {
 58            let subscription = subscription?;
 59            let period_start_at = subscription.current_period_start_at()?;
 60            let period_end_at = subscription.current_period_end_at()?;
 61
 62            Some((period_start_at, period_end_at))
 63        }
 64    }
 65}
 66
 67#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 68pub enum Relation {
 69    #[sea_orm(
 70        belongs_to = "super::billing_customer::Entity",
 71        from = "Column::BillingCustomerId",
 72        to = "super::billing_customer::Column::Id"
 73    )]
 74    BillingCustomer,
 75}
 76
 77impl Related<super::billing_customer::Entity> for Entity {
 78    fn to() -> RelationDef {
 79        Relation::BillingCustomer.def()
 80    }
 81}
 82
 83impl ActiveModelBehavior for ActiveModel {}
 84
 85#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Hash, Serialize)]
 86#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
 87#[serde(rename_all = "snake_case")]
 88pub enum SubscriptionKind {
 89    #[sea_orm(string_value = "zed_pro")]
 90    ZedPro,
 91    #[sea_orm(string_value = "zed_pro_trial")]
 92    ZedProTrial,
 93    #[sea_orm(string_value = "zed_free")]
 94    ZedFree,
 95}
 96
 97impl From<SubscriptionKind> for cloud_llm_client::Plan {
 98    fn from(value: SubscriptionKind) -> Self {
 99        match value {
100            SubscriptionKind::ZedPro => Self::ZedPro,
101            SubscriptionKind::ZedProTrial => Self::ZedProTrial,
102            SubscriptionKind::ZedFree => Self::ZedFree,
103        }
104    }
105}
106
107/// The status of a Stripe subscription.
108///
109/// [Stripe docs](https://docs.stripe.com/api/subscriptions/object#subscription_object-status)
110#[derive(
111    Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash, Serialize,
112)]
113#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
114#[serde(rename_all = "snake_case")]
115pub enum StripeSubscriptionStatus {
116    #[default]
117    #[sea_orm(string_value = "incomplete")]
118    Incomplete,
119    #[sea_orm(string_value = "incomplete_expired")]
120    IncompleteExpired,
121    #[sea_orm(string_value = "trialing")]
122    Trialing,
123    #[sea_orm(string_value = "active")]
124    Active,
125    #[sea_orm(string_value = "past_due")]
126    PastDue,
127    #[sea_orm(string_value = "canceled")]
128    Canceled,
129    #[sea_orm(string_value = "unpaid")]
130    Unpaid,
131    #[sea_orm(string_value = "paused")]
132    Paused,
133}
134
135impl StripeSubscriptionStatus {
136    pub fn is_cancelable(&self) -> bool {
137        match self {
138            Self::Trialing | Self::Active | Self::PastDue => true,
139            Self::Incomplete
140            | Self::IncompleteExpired
141            | Self::Canceled
142            | Self::Unpaid
143            | Self::Paused => false,
144        }
145    }
146}
147
148/// The cancellation reason for a Stripe subscription.
149///
150/// [Stripe docs](https://docs.stripe.com/api/subscriptions/object#subscription_object-cancellation_details-reason)
151#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Hash, Serialize)]
152#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
153#[serde(rename_all = "snake_case")]
154pub enum StripeCancellationReason {
155    #[sea_orm(string_value = "cancellation_requested")]
156    CancellationRequested,
157    #[sea_orm(string_value = "payment_disputed")]
158    PaymentDisputed,
159    #[sea_orm(string_value = "payment_failed")]
160    PaymentFailed,
161}