From 0c176634651b1b62842f256b347ed4e8721a4c39 Mon Sep 17 00:00:00 2001 From: Jai A Date: Sun, 13 Oct 2024 18:24:32 -0700 Subject: [PATCH] Fix new charge date, pyro suspend reason --- ...fca57628eed279f720565fab55c8d10decd88.json | 58 ------------- ...5d5fc644d989293ccb42c1e06ec54dc2571f8.json | 82 +++++++++++++++++++ src/database/models/charge_item.rs | 16 ++++ src/database/models/user_subscription_item.rs | 24 ------ src/routes/internal/billing.rs | 60 ++++++++++---- 5 files changed, 144 insertions(+), 96 deletions(-) delete mode 100644 .sqlx/query-3cbc34bc326595fc9d070494613fca57628eed279f720565fab55c8d10decd88.json create mode 100644 .sqlx/query-a87c913916adf9177f8f38369975d5fc644d989293ccb42c1e06ec54dc2571f8.json diff --git a/.sqlx/query-3cbc34bc326595fc9d070494613fca57628eed279f720565fab55c8d10decd88.json b/.sqlx/query-3cbc34bc326595fc9d070494613fca57628eed279f720565fab55c8d10decd88.json deleted file mode 100644 index 14f57491..00000000 --- a/.sqlx/query-3cbc34bc326595fc9d070494613fca57628eed279f720565fab55c8d10decd88.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n us.id, us.user_id, us.price_id, us.interval, us.created, us.status, us.metadata\n FROM users_subscriptions us\n \n INNER JOIN charges c\n ON c.subscription_id = us.id\n AND (\n (c.status = 'cancelled' AND c.due < $1) OR\n (c.status = 'failed' AND c.last_attempt < $1 - INTERVAL '2 days')\n )\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "price_id", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "interval", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "created", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "status", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "metadata", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Timestamptz" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "3cbc34bc326595fc9d070494613fca57628eed279f720565fab55c8d10decd88" -} diff --git a/.sqlx/query-a87c913916adf9177f8f38369975d5fc644d989293ccb42c1e06ec54dc2571f8.json b/.sqlx/query-a87c913916adf9177f8f38369975d5fc644d989293ccb42c1e06ec54dc2571f8.json new file mode 100644 index 00000000..66986fd9 --- /dev/null +++ b/.sqlx/query-a87c913916adf9177f8f38369975d5fc644d989293ccb42c1e06ec54dc2571f8.json @@ -0,0 +1,82 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval\n FROM charges\n WHERE (status = 'cancelled' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "user_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "price_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "amount", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "currency_code", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "status", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "due", + "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "last_attempt", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "charge_type", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "subscription_id", + "type_info": "Int8" + }, + { + "ordinal": 10, + "name": "subscription_interval", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Timestamptz" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true, + false, + true, + true + ] + }, + "hash": "a87c913916adf9177f8f38369975d5fc644d989293ccb42c1e06ec54dc2571f8" +} diff --git a/src/database/models/charge_item.rs b/src/database/models/charge_item.rs index af0ed0bf..4ea19bf0 100644 --- a/src/database/models/charge_item.rs +++ b/src/database/models/charge_item.rs @@ -162,6 +162,22 @@ impl ChargeItem { .collect::, serde_json::Error>>()?) } + pub async fn get_unprovision( + exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, + ) -> Result, DatabaseError> { + let now = Utc::now(); + + let res = + select_charges_with_predicate!("WHERE (status = 'cancelled' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", now) + .fetch_all(exec) + .await?; + + Ok(res + .into_iter() + .map(|r| r.try_into()) + .collect::, serde_json::Error>>()?) + } + pub async fn remove( id: ChargeId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, diff --git a/src/database/models/user_subscription_item.rs b/src/database/models/user_subscription_item.rs index 8de0fd0e..d83546c8 100644 --- a/src/database/models/user_subscription_item.rs +++ b/src/database/models/user_subscription_item.rs @@ -95,30 +95,6 @@ impl UserSubscriptionItem { .collect::, serde_json::Error>>()?) } - pub async fn get_all_unprovision( - exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>, - ) -> Result, DatabaseError> { - let now = Utc::now(); - let results = select_user_subscriptions_with_predicate!( - " - INNER JOIN charges c - ON c.subscription_id = us.id - AND ( - (c.status = 'cancelled' AND c.due < $1) OR - (c.status = 'failed' AND c.last_attempt < $1 - INTERVAL '2 days') - ) - ", - now - ) - .fetch_all(exec) - .await?; - - Ok(results - .into_iter() - .map(|r| r.try_into()) - .collect::, serde_json::Error>>()?) - } - pub async fn upsert( &self, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, diff --git a/src/routes/internal/billing.rs b/src/routes/internal/billing.rs index 9abd8962..78de9aa5 100644 --- a/src/routes/internal/billing.rs +++ b/src/routes/internal/billing.rs @@ -1178,11 +1178,7 @@ pub async fn stripe_webhook( price_id, interval, created: Utc::now(), - status: if charge_status == ChargeStatus::Succeeded { - SubscriptionStatus::Provisioned - } else { - SubscriptionStatus::Unprovisioned - }, + status: SubscriptionStatus::Unprovisioned, metadata: None, }; @@ -1342,16 +1338,18 @@ pub async fn stripe_webhook( .json::() .await?; - if let Some(ref mut subscription) = metadata.user_subscription_item { - subscription.metadata = Some(SubscriptionMetadata::Pyro { id: res.uuid }); - subscription.upsert(&mut transaction).await?; + if let Some(ref mut subscription) = + metadata.user_subscription_item + { + subscription.metadata = + Some(SubscriptionMetadata::Pyro { id: res.uuid }); } } } } } - if let Some(subscription) = metadata.user_subscription_item { + if let Some(mut subscription) = metadata.user_subscription_item { let open_charge = ChargeItem::get_open_subscription(subscription.id, &mut *transaction) .await?; @@ -1382,7 +1380,11 @@ pub async fn stripe_webhook( amount: new_price as i64, currency_code: metadata.product_price_item.currency_code, status: ChargeStatus::Open, - due: Utc::now() + subscription.interval.duration(), + due: if subscription.status == SubscriptionStatus::Unprovisioned { + Utc::now() + subscription.interval.duration() + } else { + metadata.charge_item.due + subscription.interval.duration() + }, last_attempt: None, type_: ChargeType::Subscription, subscription_id: Some(subscription.id), @@ -1391,6 +1393,9 @@ pub async fn stripe_webhook( .upsert(&mut transaction) .await?; }; + + subscription.status = SubscriptionStatus::Provisioned; + subscription.upsert(&mut transaction).await?; } transaction.commit().await?; @@ -1538,8 +1543,18 @@ pub async fn subscription_task(pool: PgPool, redis: RedisPool) { let mut clear_cache_users = Vec::new(); // If an active subscription has a canceled charge OR a failed charge more than two days ago, it should be cancelled - let all_subscriptions = - user_subscription_item::UserSubscriptionItem::get_all_unprovision(&pool).await?; + let all_charges = ChargeItem::get_unprovision(&pool).await?; + + let mut all_subscriptions = user_subscription_item::UserSubscriptionItem::get_many( + &all_charges + .iter() + .filter_map(|x| x.subscription_id) + .collect::>() + .into_iter() + .collect::>(), + &pool, + ) + .await?; let subscription_prices = product_item::ProductPriceItem::get_many( &all_subscriptions .iter() @@ -1572,7 +1587,20 @@ pub async fn subscription_task(pool: PgPool, redis: RedisPool) { ) .await?; - for mut subscription in all_subscriptions { + for charge in all_charges { + let subscription = if let Some(subscription) = all_subscriptions + .iter_mut() + .find(|x| Some(x.id) == charge.subscription_id) + { + subscription + } else { + continue; + }; + + if subscription.status == SubscriptionStatus::Unprovisioned { + continue; + } + let product_price = if let Some(product_price) = subscription_prices .iter() .find(|x| x.id == subscription.price_id) @@ -1624,7 +1652,11 @@ pub async fn subscription_task(pool: PgPool, redis: RedisPool) { )) .header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?) .json(&serde_json::json!({ - "reason": "cancelled" + "reason": if charge.status == ChargeStatus::Cancelled { + "cancelled" + } else { + "paymentfailed" + } })) .send() .await;