collab: Add separate billing_customers
table (#15457)
This PR adds a new `billing_customers` table to hold the billing customers. Previously we were storing both the `stripe_customer_id` and `stripe_subscription_id` in the `billable_subscriptions` table. However, this creates problems when we need to correlate subscription events back to the subscription record, as we don't know the user that the Stripe event corresponds to. By moving the `stripe_customer_id` to a separate table we can create the Stripe customer earlier in the flow—before we create the Stripe Checkout session—and associate that customer with a user. This way when we receive events down the line we can use the Stripe customer ID to correlate it back to the user. We're doing some destructive actions to the `billing_subscriptions` table, but this is fine, as we haven't started using them yet. Release Notes: - N/A
This commit is contained in:
parent
66121fa0e8
commit
28c14cdee4
13 changed files with 183 additions and 52 deletions
|
@ -420,12 +420,20 @@ CREATE TABLE dev_server_projects (
|
||||||
CREATE TABLE IF NOT EXISTS billing_subscriptions (
|
CREATE TABLE IF NOT EXISTS billing_subscriptions (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
billing_customer_id INTEGER NOT NULL REFERENCES billing_customers(id),
|
||||||
stripe_customer_id TEXT NOT NULL,
|
|
||||||
stripe_subscription_id TEXT NOT NULL,
|
stripe_subscription_id TEXT NOT NULL,
|
||||||
stripe_subscription_status TEXT NOT NULL
|
stripe_subscription_status TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX "ix_billing_subscriptions_on_user_id" ON billing_subscriptions (user_id);
|
CREATE INDEX "ix_billing_subscriptions_on_billing_customer_id" ON billing_subscriptions (billing_customer_id);
|
||||||
CREATE INDEX "ix_billing_subscriptions_on_stripe_customer_id" ON billing_subscriptions (stripe_customer_id);
|
|
||||||
CREATE UNIQUE INDEX "uix_billing_subscriptions_on_stripe_subscription_id" ON billing_subscriptions (stripe_subscription_id);
|
CREATE UNIQUE INDEX "uix_billing_subscriptions_on_stripe_subscription_id" ON billing_subscriptions (stripe_subscription_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS billing_customers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||||
|
stripe_customer_id TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "uix_billing_customers_on_user_id" ON billing_customers (user_id);
|
||||||
|
CREATE UNIQUE INDEX "uix_billing_customers_on_stripe_customer_id" ON billing_customers (stripe_customer_id);
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS billing_customers (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
stripe_customer_id TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "uix_billing_customers_on_user_id" ON billing_customers (user_id);
|
||||||
|
CREATE UNIQUE INDEX "uix_billing_customers_on_stripe_customer_id" ON billing_customers (stripe_customer_id);
|
||||||
|
|
||||||
|
-- Make `billing_subscriptions` reference `billing_customers` instead of having its
|
||||||
|
-- own `user_id` and `stripe_customer_id`.
|
||||||
|
DROP INDEX IF EXISTS "ix_billing_subscriptions_on_user_id";
|
||||||
|
DROP INDEX IF EXISTS "ix_billing_subscriptions_on_stripe_customer_id";
|
||||||
|
ALTER TABLE billing_subscriptions DROP COLUMN user_id;
|
||||||
|
ALTER TABLE billing_subscriptions DROP COLUMN stripe_customer_id;
|
||||||
|
ALTER TABLE billing_subscriptions ADD COLUMN billing_customer_id INTEGER NOT NULL REFERENCES billing_customers (id) ON DELETE CASCADE;
|
||||||
|
CREATE INDEX "ix_billing_subscriptions_on_billing_customer_id" ON billing_subscriptions (billing_customer_id);
|
|
@ -3,7 +3,6 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use axum::{extract, routing::post, Extension, Json, Router};
|
use axum::{extract, routing::post, Extension, Json, Router};
|
||||||
use collections::HashSet;
|
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stripe::{
|
use stripe::{
|
||||||
|
@ -11,7 +10,7 @@ use stripe::{
|
||||||
CreateBillingPortalSessionFlowData, CreateBillingPortalSessionFlowDataAfterCompletion,
|
CreateBillingPortalSessionFlowData, CreateBillingPortalSessionFlowDataAfterCompletion,
|
||||||
CreateBillingPortalSessionFlowDataAfterCompletionRedirect,
|
CreateBillingPortalSessionFlowDataAfterCompletionRedirect,
|
||||||
CreateBillingPortalSessionFlowDataType, CreateCheckoutSession, CreateCheckoutSessionLineItems,
|
CreateBillingPortalSessionFlowDataType, CreateCheckoutSession, CreateCheckoutSessionLineItems,
|
||||||
CustomerId,
|
CreateCustomer, Customer, CustomerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::db::BillingSubscriptionId;
|
use crate::db::BillingSubscriptionId;
|
||||||
|
@ -59,28 +58,27 @@ async fn create_billing_subscription(
|
||||||
))?
|
))?
|
||||||
};
|
};
|
||||||
|
|
||||||
let existing_customer_id = {
|
let customer_id =
|
||||||
let existing_subscriptions = app.db.get_billing_subscriptions(user.id).await?;
|
if let Some(existing_customer) = app.db.get_billing_customer_by_user_id(user.id).await? {
|
||||||
let distinct_customer_ids = existing_subscriptions
|
CustomerId::from_str(&existing_customer.stripe_customer_id)
|
||||||
.iter()
|
.context("failed to parse customer ID")?
|
||||||
.map(|subscription| subscription.stripe_customer_id.as_str())
|
} else {
|
||||||
.collect::<HashSet<_>>();
|
let customer = Customer::create(
|
||||||
// Sanity: Make sure we can determine a single Stripe customer ID for the user.
|
&stripe_client,
|
||||||
if distinct_customer_ids.len() > 1 {
|
CreateCustomer {
|
||||||
Err(anyhow!("user has multiple existing customer IDs"))?;
|
email: user.email_address.as_deref(),
|
||||||
}
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
distinct_customer_ids
|
customer.id
|
||||||
.into_iter()
|
};
|
||||||
.next()
|
|
||||||
.map(|id| CustomerId::from_str(id).context("failed to parse customer ID"))
|
|
||||||
.transpose()
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let checkout_session = {
|
let checkout_session = {
|
||||||
let mut params = CreateCheckoutSession::new();
|
let mut params = CreateCheckoutSession::new();
|
||||||
params.mode = Some(stripe::CheckoutSessionMode::Subscription);
|
params.mode = Some(stripe::CheckoutSessionMode::Subscription);
|
||||||
params.customer = existing_customer_id;
|
params.customer = Some(customer_id);
|
||||||
params.client_reference_id = Some(user.github_login.as_str());
|
params.client_reference_id = Some(user.github_login.as_str());
|
||||||
params.line_items = Some(vec![CreateCheckoutSessionLineItems {
|
params.line_items = Some(vec![CreateCheckoutSessionLineItems {
|
||||||
price: Some(stripe_price_id.to_string()),
|
price: Some(stripe_price_id.to_string()),
|
||||||
|
@ -140,6 +138,14 @@ async fn manage_billing_subscription(
|
||||||
))?
|
))?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let customer = app
|
||||||
|
.db
|
||||||
|
.get_billing_customer_by_user_id(user.id)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| anyhow!("billing customer not found"))?;
|
||||||
|
let customer_id = CustomerId::from_str(&customer.stripe_customer_id)
|
||||||
|
.context("failed to parse customer ID")?;
|
||||||
|
|
||||||
let subscription = if let Some(subscription_id) = body.subscription_id {
|
let subscription = if let Some(subscription_id) = body.subscription_id {
|
||||||
app.db
|
app.db
|
||||||
.get_billing_subscription_by_id(subscription_id)
|
.get_billing_subscription_by_id(subscription_id)
|
||||||
|
@ -158,9 +164,6 @@ async fn manage_billing_subscription(
|
||||||
.ok_or_else(|| anyhow!("user has no active subscriptions"))?
|
.ok_or_else(|| anyhow!("user has no active subscriptions"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
let customer_id = CustomerId::from_str(&subscription.stripe_customer_id)
|
|
||||||
.context("failed to parse customer ID")?;
|
|
||||||
|
|
||||||
let flow = match body.intent {
|
let flow = match body.intent {
|
||||||
ManageSubscriptionIntent::Cancel => CreateBillingPortalSessionFlowData {
|
ManageSubscriptionIntent::Cancel => CreateBillingPortalSessionFlowData {
|
||||||
type_: CreateBillingPortalSessionFlowDataType::SubscriptionCancel,
|
type_: CreateBillingPortalSessionFlowDataType::SubscriptionCancel,
|
||||||
|
|
|
@ -45,6 +45,7 @@ use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||||
pub use tests::TestDb;
|
pub use tests::TestDb;
|
||||||
|
|
||||||
pub use ids::*;
|
pub use ids::*;
|
||||||
|
pub use queries::billing_customers::CreateBillingCustomerParams;
|
||||||
pub use queries::billing_subscriptions::CreateBillingSubscriptionParams;
|
pub use queries::billing_subscriptions::CreateBillingSubscriptionParams;
|
||||||
pub use queries::contributors::ContributorSelector;
|
pub use queries::contributors::ContributorSelector;
|
||||||
pub use sea_orm::ConnectOptions;
|
pub use sea_orm::ConnectOptions;
|
||||||
|
|
|
@ -68,6 +68,7 @@ macro_rules! id_type {
|
||||||
}
|
}
|
||||||
|
|
||||||
id_type!(AccessTokenId);
|
id_type!(AccessTokenId);
|
||||||
|
id_type!(BillingCustomerId);
|
||||||
id_type!(BillingSubscriptionId);
|
id_type!(BillingSubscriptionId);
|
||||||
id_type!(BufferId);
|
id_type!(BufferId);
|
||||||
id_type!(ChannelBufferCollaboratorId);
|
id_type!(ChannelBufferCollaboratorId);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub mod access_tokens;
|
pub mod access_tokens;
|
||||||
|
pub mod billing_customers;
|
||||||
pub mod billing_subscriptions;
|
pub mod billing_subscriptions;
|
||||||
pub mod buffers;
|
pub mod buffers;
|
||||||
pub mod channels;
|
pub mod channels;
|
||||||
|
|
42
crates/collab/src/db/queries/billing_customers.rs
Normal file
42
crates/collab/src/db/queries/billing_customers.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CreateBillingCustomerParams {
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub stripe_customer_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
/// Creates a new billing customer.
|
||||||
|
pub async fn create_billing_customer(
|
||||||
|
&self,
|
||||||
|
params: &CreateBillingCustomerParams,
|
||||||
|
) -> Result<billing_customer::Model> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
let customer = billing_customer::Entity::insert(billing_customer::ActiveModel {
|
||||||
|
user_id: ActiveValue::set(params.user_id),
|
||||||
|
stripe_customer_id: ActiveValue::set(params.stripe_customer_id.clone()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.exec_with_returning(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(customer)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the billing customer for the user with the specified ID.
|
||||||
|
pub async fn get_billing_customer_by_user_id(
|
||||||
|
&self,
|
||||||
|
user_id: UserId,
|
||||||
|
) -> Result<Option<billing_customer::Model>> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
Ok(billing_customer::Entity::find()
|
||||||
|
.filter(billing_customer::Column::UserId.eq(user_id))
|
||||||
|
.one(&*tx)
|
||||||
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,7 @@ use super::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CreateBillingSubscriptionParams {
|
pub struct CreateBillingSubscriptionParams {
|
||||||
pub user_id: UserId,
|
pub billing_customer_id: BillingCustomerId,
|
||||||
pub stripe_customer_id: String,
|
|
||||||
pub stripe_subscription_id: String,
|
pub stripe_subscription_id: String,
|
||||||
pub stripe_subscription_status: StripeSubscriptionStatus,
|
pub stripe_subscription_status: StripeSubscriptionStatus,
|
||||||
}
|
}
|
||||||
|
@ -18,8 +17,7 @@ impl Database {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
billing_subscription::Entity::insert(billing_subscription::ActiveModel {
|
billing_subscription::Entity::insert(billing_subscription::ActiveModel {
|
||||||
user_id: ActiveValue::set(params.user_id),
|
billing_customer_id: ActiveValue::set(params.billing_customer_id),
|
||||||
stripe_customer_id: ActiveValue::set(params.stripe_customer_id.clone()),
|
|
||||||
stripe_subscription_id: ActiveValue::set(params.stripe_subscription_id.clone()),
|
stripe_subscription_id: ActiveValue::set(params.stripe_subscription_id.clone()),
|
||||||
stripe_subscription_status: ActiveValue::set(params.stripe_subscription_status),
|
stripe_subscription_status: ActiveValue::set(params.stripe_subscription_status),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -56,7 +54,8 @@ impl Database {
|
||||||
) -> Result<Vec<billing_subscription::Model>> {
|
) -> Result<Vec<billing_subscription::Model>> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let subscriptions = billing_subscription::Entity::find()
|
let subscriptions = billing_subscription::Entity::find()
|
||||||
.filter(billing_subscription::Column::UserId.eq(user_id))
|
.inner_join(billing_customer::Entity)
|
||||||
|
.filter(billing_customer::Column::UserId.eq(user_id))
|
||||||
.order_by_asc(billing_subscription::Column::Id)
|
.order_by_asc(billing_subscription::Column::Id)
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -73,8 +72,9 @@ impl Database {
|
||||||
) -> Result<Vec<billing_subscription::Model>> {
|
) -> Result<Vec<billing_subscription::Model>> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let subscriptions = billing_subscription::Entity::find()
|
let subscriptions = billing_subscription::Entity::find()
|
||||||
|
.inner_join(billing_customer::Entity)
|
||||||
.filter(
|
.filter(
|
||||||
billing_subscription::Column::UserId.eq(user_id).and(
|
billing_customer::Column::UserId.eq(user_id).and(
|
||||||
billing_subscription::Column::StripeSubscriptionStatus
|
billing_subscription::Column::StripeSubscriptionStatus
|
||||||
.eq(StripeSubscriptionStatus::Active),
|
.eq(StripeSubscriptionStatus::Active),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod access_token;
|
pub mod access_token;
|
||||||
|
pub mod billing_customer;
|
||||||
pub mod billing_subscription;
|
pub mod billing_subscription;
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod buffer_operation;
|
pub mod buffer_operation;
|
||||||
|
|
39
crates/collab/src/db/tables/billing_customer.rs
Normal file
39
crates/collab/src/db/tables/billing_customer.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::db::{BillingCustomerId, UserId};
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
/// A billing customer.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "billing_customers")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: BillingCustomerId,
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub stripe_customer_id: String,
|
||||||
|
pub created_at: DateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
#[sea_orm(has_many = "super::billing_subscription::Entity")]
|
||||||
|
BillingSubscription,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::billing_subscription::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::BillingSubscription.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::db::{BillingSubscriptionId, UserId};
|
use crate::db::{BillingCustomerId, BillingSubscriptionId};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
/// A billing subscription.
|
/// A billing subscription.
|
||||||
|
@ -7,8 +7,7 @@ use sea_orm::entity::prelude::*;
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: BillingSubscriptionId,
|
pub id: BillingSubscriptionId,
|
||||||
pub user_id: UserId,
|
pub billing_customer_id: BillingCustomerId,
|
||||||
pub stripe_customer_id: String,
|
|
||||||
pub stripe_subscription_id: String,
|
pub stripe_subscription_id: String,
|
||||||
pub stripe_subscription_status: StripeSubscriptionStatus,
|
pub stripe_subscription_status: StripeSubscriptionStatus,
|
||||||
pub created_at: DateTime,
|
pub created_at: DateTime,
|
||||||
|
@ -17,16 +16,16 @@ pub struct Model {
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
#[sea_orm(
|
#[sea_orm(
|
||||||
belongs_to = "super::user::Entity",
|
belongs_to = "super::billing_customer::Entity",
|
||||||
from = "Column::UserId",
|
from = "Column::BillingCustomerId",
|
||||||
to = "super::user::Column::Id"
|
to = "super::billing_customer::Column::Id"
|
||||||
)]
|
)]
|
||||||
User,
|
BillingCustomer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::user::Entity> for Entity {
|
impl Related<super::billing_customer::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::User.def()
|
Relation::BillingCustomer.def()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ pub struct Model {
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
#[sea_orm(has_many = "super::access_token::Entity")]
|
#[sea_orm(has_many = "super::access_token::Entity")]
|
||||||
AccessToken,
|
AccessToken,
|
||||||
#[sea_orm(has_many = "super::billing_subscription::Entity")]
|
#[sea_orm(has_one = "super::billing_customer::Entity")]
|
||||||
BillingSubscription,
|
BillingCustomer,
|
||||||
#[sea_orm(has_one = "super::room_participant::Entity")]
|
#[sea_orm(has_one = "super::room_participant::Entity")]
|
||||||
RoomParticipant,
|
RoomParticipant,
|
||||||
#[sea_orm(has_many = "super::project::Entity")]
|
#[sea_orm(has_many = "super::project::Entity")]
|
||||||
|
@ -44,6 +44,12 @@ impl Related<super::access_token::Entity> for Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::billing_customer::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::BillingCustomer.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Related<super::room_participant::Entity> for Entity {
|
impl Related<super::room_participant::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::RoomParticipant.def()
|
Relation::RoomParticipant.def()
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::db::billing_subscription::StripeSubscriptionStatus;
|
use crate::db::billing_subscription::StripeSubscriptionStatus;
|
||||||
use crate::db::tests::new_test_user;
|
use crate::db::tests::new_test_user;
|
||||||
use crate::db::CreateBillingSubscriptionParams;
|
use crate::db::{CreateBillingCustomerParams, CreateBillingSubscriptionParams};
|
||||||
use crate::test_both_dbs;
|
use crate::test_both_dbs;
|
||||||
|
|
||||||
use super::Database;
|
use super::Database;
|
||||||
|
@ -25,9 +25,17 @@ async fn test_get_active_billing_subscriptions(db: &Arc<Database>) {
|
||||||
// A user with an active subscription has one active billing subscription.
|
// A user with an active subscription has one active billing subscription.
|
||||||
{
|
{
|
||||||
let user_id = new_test_user(db, "active-user@example.com").await;
|
let user_id = new_test_user(db, "active-user@example.com").await;
|
||||||
|
let customer = db
|
||||||
|
.create_billing_customer(&CreateBillingCustomerParams {
|
||||||
|
user_id,
|
||||||
|
stripe_customer_id: "cus_active_user".into(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(customer.stripe_customer_id, "cus_active_user".to_string());
|
||||||
|
|
||||||
db.create_billing_subscription(&CreateBillingSubscriptionParams {
|
db.create_billing_subscription(&CreateBillingSubscriptionParams {
|
||||||
user_id,
|
billing_customer_id: customer.id,
|
||||||
stripe_customer_id: "cus_active_user".into(),
|
|
||||||
stripe_subscription_id: "sub_active_user".into(),
|
stripe_subscription_id: "sub_active_user".into(),
|
||||||
stripe_subscription_status: StripeSubscriptionStatus::Active,
|
stripe_subscription_status: StripeSubscriptionStatus::Active,
|
||||||
})
|
})
|
||||||
|
@ -38,10 +46,6 @@ async fn test_get_active_billing_subscriptions(db: &Arc<Database>) {
|
||||||
assert_eq!(subscriptions.len(), 1);
|
assert_eq!(subscriptions.len(), 1);
|
||||||
|
|
||||||
let subscription = &subscriptions[0];
|
let subscription = &subscriptions[0];
|
||||||
assert_eq!(
|
|
||||||
subscription.stripe_customer_id,
|
|
||||||
"cus_active_user".to_string()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
subscription.stripe_subscription_id,
|
subscription.stripe_subscription_id,
|
||||||
"sub_active_user".to_string()
|
"sub_active_user".to_string()
|
||||||
|
@ -55,9 +59,17 @@ async fn test_get_active_billing_subscriptions(db: &Arc<Database>) {
|
||||||
// A user with a past-due subscription has no active billing subscriptions.
|
// A user with a past-due subscription has no active billing subscriptions.
|
||||||
{
|
{
|
||||||
let user_id = new_test_user(db, "past-due-user@example.com").await;
|
let user_id = new_test_user(db, "past-due-user@example.com").await;
|
||||||
|
let customer = db
|
||||||
|
.create_billing_customer(&CreateBillingCustomerParams {
|
||||||
|
user_id,
|
||||||
|
stripe_customer_id: "cus_past_due_user".into(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(customer.stripe_customer_id, "cus_past_due_user".to_string());
|
||||||
|
|
||||||
db.create_billing_subscription(&CreateBillingSubscriptionParams {
|
db.create_billing_subscription(&CreateBillingSubscriptionParams {
|
||||||
user_id,
|
billing_customer_id: customer.id,
|
||||||
stripe_customer_id: "cus_past_due_user".into(),
|
|
||||||
stripe_subscription_id: "sub_past_due_user".into(),
|
stripe_subscription_id: "sub_past_due_user".into(),
|
||||||
stripe_subscription_status: StripeSubscriptionStatus::PastDue,
|
stripe_subscription_status: StripeSubscriptionStatus::PastDue,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue