billing_subscription.rs

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