1use anyhow::Context as _;
2
3use crate::db::billing_subscription::{
4 StripeCancellationReason, StripeSubscriptionStatus, SubscriptionKind,
5};
6
7use super::*;
8
9#[derive(Debug)]
10pub struct CreateBillingSubscriptionParams {
11 pub billing_customer_id: BillingCustomerId,
12 pub kind: Option<SubscriptionKind>,
13 pub stripe_subscription_id: String,
14 pub stripe_subscription_status: StripeSubscriptionStatus,
15 pub stripe_cancellation_reason: Option<StripeCancellationReason>,
16 pub stripe_current_period_start: Option<i64>,
17 pub stripe_current_period_end: Option<i64>,
18}
19
20#[derive(Debug, Default)]
21pub struct UpdateBillingSubscriptionParams {
22 pub billing_customer_id: ActiveValue<BillingCustomerId>,
23 pub kind: ActiveValue<Option<SubscriptionKind>>,
24 pub stripe_subscription_id: ActiveValue<String>,
25 pub stripe_subscription_status: ActiveValue<StripeSubscriptionStatus>,
26 pub stripe_cancel_at: ActiveValue<Option<DateTime>>,
27 pub stripe_cancellation_reason: ActiveValue<Option<StripeCancellationReason>>,
28 pub stripe_current_period_start: ActiveValue<Option<i64>>,
29 pub stripe_current_period_end: ActiveValue<Option<i64>>,
30}
31
32impl Database {
33 /// Creates a new billing subscription.
34 pub async fn create_billing_subscription(
35 &self,
36 params: &CreateBillingSubscriptionParams,
37 ) -> Result<billing_subscription::Model> {
38 self.transaction(|tx| async move {
39 let id = billing_subscription::Entity::insert(billing_subscription::ActiveModel {
40 billing_customer_id: ActiveValue::set(params.billing_customer_id),
41 kind: ActiveValue::set(params.kind),
42 stripe_subscription_id: ActiveValue::set(params.stripe_subscription_id.clone()),
43 stripe_subscription_status: ActiveValue::set(params.stripe_subscription_status),
44 stripe_cancellation_reason: ActiveValue::set(params.stripe_cancellation_reason),
45 stripe_current_period_start: ActiveValue::set(params.stripe_current_period_start),
46 stripe_current_period_end: ActiveValue::set(params.stripe_current_period_end),
47 ..Default::default()
48 })
49 .exec(&*tx)
50 .await?
51 .last_insert_id;
52
53 Ok(billing_subscription::Entity::find_by_id(id)
54 .one(&*tx)
55 .await?
56 .context("failed to retrieve inserted billing subscription")?)
57 })
58 .await
59 }
60
61 /// Updates the specified billing subscription.
62 pub async fn update_billing_subscription(
63 &self,
64 id: BillingSubscriptionId,
65 params: &UpdateBillingSubscriptionParams,
66 ) -> Result<()> {
67 self.transaction(|tx| async move {
68 billing_subscription::Entity::update(billing_subscription::ActiveModel {
69 id: ActiveValue::set(id),
70 billing_customer_id: params.billing_customer_id.clone(),
71 kind: params.kind.clone(),
72 stripe_subscription_id: params.stripe_subscription_id.clone(),
73 stripe_subscription_status: params.stripe_subscription_status.clone(),
74 stripe_cancel_at: params.stripe_cancel_at.clone(),
75 stripe_cancellation_reason: params.stripe_cancellation_reason.clone(),
76 stripe_current_period_start: params.stripe_current_period_start.clone(),
77 stripe_current_period_end: params.stripe_current_period_end.clone(),
78 created_at: ActiveValue::not_set(),
79 })
80 .exec(&*tx)
81 .await?;
82
83 Ok(())
84 })
85 .await
86 }
87
88 /// Returns the billing subscription with the specified Stripe subscription ID.
89 pub async fn get_billing_subscription_by_stripe_subscription_id(
90 &self,
91 stripe_subscription_id: &str,
92 ) -> Result<Option<billing_subscription::Model>> {
93 self.transaction(|tx| async move {
94 Ok(billing_subscription::Entity::find()
95 .filter(
96 billing_subscription::Column::StripeSubscriptionId.eq(stripe_subscription_id),
97 )
98 .one(&*tx)
99 .await?)
100 })
101 .await
102 }
103
104 pub async fn get_active_billing_subscription(
105 &self,
106 user_id: UserId,
107 ) -> Result<Option<billing_subscription::Model>> {
108 self.transaction(|tx| async move {
109 Ok(billing_subscription::Entity::find()
110 .inner_join(billing_customer::Entity)
111 .filter(billing_customer::Column::UserId.eq(user_id))
112 .filter(
113 Condition::all()
114 .add(
115 Condition::any()
116 .add(
117 billing_subscription::Column::StripeSubscriptionStatus
118 .eq(StripeSubscriptionStatus::Active),
119 )
120 .add(
121 billing_subscription::Column::StripeSubscriptionStatus
122 .eq(StripeSubscriptionStatus::Trialing),
123 ),
124 )
125 .add(billing_subscription::Column::Kind.is_not_null()),
126 )
127 .one(&*tx)
128 .await?)
129 })
130 .await
131 }
132
133 /// Returns whether the user has an active billing subscription.
134 pub async fn has_active_billing_subscription(&self, user_id: UserId) -> Result<bool> {
135 Ok(self.count_active_billing_subscriptions(user_id).await? > 0)
136 }
137
138 /// Returns the count of the active billing subscriptions for the user with the specified ID.
139 pub async fn count_active_billing_subscriptions(&self, user_id: UserId) -> Result<usize> {
140 self.transaction(|tx| async move {
141 let count = billing_subscription::Entity::find()
142 .inner_join(billing_customer::Entity)
143 .filter(
144 billing_customer::Column::UserId.eq(user_id).and(
145 billing_subscription::Column::StripeSubscriptionStatus
146 .eq(StripeSubscriptionStatus::Active)
147 .or(billing_subscription::Column::StripeSubscriptionStatus
148 .eq(StripeSubscriptionStatus::Trialing)),
149 ),
150 )
151 .count(&*tx)
152 .await?;
153
154 Ok(count as usize)
155 })
156 .await
157 }
158}