From 4e36793eaef154356abd7cc696a3bc528d9b0edf Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Fri, 23 Feb 2024 07:22:49 -0400 Subject: [PATCH 1/5] feat: update users tier when they cancel or sub --- src/constants.ts | 1 + src/services/StorageService.ts | 13 +++++++++++ .../handleCheckoutSessionCompleted.ts | 13 ++++++++++- src/webhooks/handleLifetimeRefunded.ts | 16 ++++++++++++-- src/webhooks/handleSubscriptionCanceled.ts | 17 ++++++++++++-- src/webhooks/handleSubscriptionUpdated.ts | 22 ++++++++++++++++--- src/webhooks/index.ts | 3 +++ 7 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index e36e976..421ac50 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,2 @@ export const FREE_PLAN_BYTES_SPACE = 2 * 1024 * 1024 * 1024; +export const FREE_INDIVIDUAL_TIER = 'free_individual_tier'; diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index cd00f64..cdbb6a8 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -43,3 +43,16 @@ export async function createOrUpdateUser(maxSpaceBytes: string, email: string, c }, ); } + +export async function updateUserTier(planId: string, uuid: string, config: AppConfig) { + return axios.put( + `${config.DRIVE_GATEWAY_URL}/api/gateway/user/update/tier`, + { planId, uuid }, + { + headers: { + 'Content-Type': 'application/json', + }, + auth: { username: config.DRIVE_GATEWAY_USER, password: config.DRIVE_GATEWAY_PASSWORD }, + }, + ); +} diff --git a/src/webhooks/handleCheckoutSessionCompleted.ts b/src/webhooks/handleCheckoutSessionCompleted.ts index 0ff57b2..62bf811 100644 --- a/src/webhooks/handleCheckoutSessionCompleted.ts +++ b/src/webhooks/handleCheckoutSessionCompleted.ts @@ -4,7 +4,7 @@ import Stripe from 'stripe'; import { type AppConfig } from '../config'; import CacheService from '../services/CacheService'; import { PaymentService, PriceMetadata } from '../services/PaymentService'; -import { createOrUpdateUser } from '../services/StorageService'; +import { createOrUpdateUser, updateUserTier } from '../services/StorageService'; import { UsersService } from '../services/UsersService'; export default async function handleCheckoutSessionCompleted( @@ -59,6 +59,17 @@ export default async function handleCheckoutSessionCompleted( throw err; } + try { + await updateUserTier(user.uuid, price.id, config); + } catch (err) { + log.error( + `Error while updating user tier: email: ${session.customer_email}, priceId: ${price.id} `, + ); + log.error(err); + + throw err; + } + try { const { customerId } = await usersService.findUserByUuid(user.uuid); if ((price.metadata as PriceMetadata).planType === 'one_time') { diff --git a/src/webhooks/handleLifetimeRefunded.ts b/src/webhooks/handleLifetimeRefunded.ts index f58f789..ba04fc3 100644 --- a/src/webhooks/handleLifetimeRefunded.ts +++ b/src/webhooks/handleLifetimeRefunded.ts @@ -1,8 +1,9 @@ import { FastifyLoggerInstance } from 'fastify'; -import { FREE_PLAN_BYTES_SPACE } from '../constants'; +import { FREE_INDIVIDUAL_TIER, FREE_PLAN_BYTES_SPACE } from '../constants'; import CacheService from '../services/CacheService'; -import { StorageService } from '../services/StorageService'; +import { StorageService, updateUserTier } from '../services/StorageService'; import { UsersService } from '../services/UsersService'; +import { AppConfig } from '../config'; export default async function handleLifetimeRefunded( storageService: StorageService, @@ -10,6 +11,7 @@ export default async function handleLifetimeRefunded( customerId: string, cacheService: CacheService, log: FastifyLoggerInstance, + config: AppConfig, ): Promise { const { uuid } = await usersService.findUserByCustomerID(customerId); @@ -20,5 +22,15 @@ export default async function handleLifetimeRefunded( } catch (err) { log.error(`Error in handleLifetimeRefunded after trying to clear ${customerId} subscription`); } + try { + await updateUserTier(uuid, FREE_INDIVIDUAL_TIER, config); + } catch (err) { + log.error( + `Error while updating user tier: uuid: ${uuid} `, + ); + log.error(err); + throw err; + } + return storageService.changeStorage(uuid, FREE_PLAN_BYTES_SPACE); } diff --git a/src/webhooks/handleSubscriptionCanceled.ts b/src/webhooks/handleSubscriptionCanceled.ts index bdc7e92..b56581f 100644 --- a/src/webhooks/handleSubscriptionCanceled.ts +++ b/src/webhooks/handleSubscriptionCanceled.ts @@ -1,8 +1,9 @@ import { FastifyLoggerInstance } from 'fastify'; -import { FREE_PLAN_BYTES_SPACE } from '../constants'; +import { FREE_INDIVIDUAL_TIER, FREE_PLAN_BYTES_SPACE } from '../constants'; import CacheService from '../services/CacheService'; -import { StorageService } from '../services/StorageService'; +import { StorageService, updateUserTier } from '../services/StorageService'; import { UsersService } from '../services/UsersService'; +import { AppConfig } from '../config'; export default async function handleSubscriptionCanceled( storageService: StorageService, @@ -10,6 +11,7 @@ export default async function handleSubscriptionCanceled( customerId: string, cacheService: CacheService, log: FastifyLoggerInstance, + config: AppConfig, ): Promise { const { uuid } = await usersService.findUserByCustomerID(customerId); try { @@ -17,5 +19,16 @@ export default async function handleSubscriptionCanceled( } catch (err) { log.error(`Error in handleSubscriptionCanceled after trying to clear ${customerId} subscription`); } + + try { + await updateUserTier(uuid, FREE_INDIVIDUAL_TIER, config); + } catch (err) { + log.error( + `Error while updating user tier: uuid: ${uuid} `, + ); + log.error(err); + throw err; + } + return storageService.changeStorage(uuid, FREE_PLAN_BYTES_SPACE); } diff --git a/src/webhooks/handleSubscriptionUpdated.ts b/src/webhooks/handleSubscriptionUpdated.ts index 7118a5f..71ceafd 100644 --- a/src/webhooks/handleSubscriptionUpdated.ts +++ b/src/webhooks/handleSubscriptionUpdated.ts @@ -1,10 +1,11 @@ import { FastifyLoggerInstance } from 'fastify'; import Stripe from 'stripe'; -import { FREE_PLAN_BYTES_SPACE } from '../constants'; +import { FREE_INDIVIDUAL_TIER, FREE_PLAN_BYTES_SPACE } from '../constants'; import CacheService from '../services/CacheService'; import { PriceMetadata } from '../services/PaymentService'; -import { StorageService } from '../services/StorageService'; +import { StorageService, updateUserTier } from '../services/StorageService'; import { UsersService } from '../services/UsersService'; +import { AppConfig } from '../config'; export default async function handleSubscriptionUpdated( storageService: StorageService, @@ -12,23 +13,38 @@ export default async function handleSubscriptionUpdated( subscription: Stripe.Subscription, cacheService: CacheService, log: FastifyLoggerInstance, + config: AppConfig, ): Promise { const customerId = subscription.customer as string; const { uuid, lifetime } = await usersService.findUserByCustomerID(customerId); if (lifetime) { return; } + const isSubscriptionCanceled = subscription.status === 'canceled'; const bytesSpace = - subscription.status === 'canceled' + isSubscriptionCanceled ? FREE_PLAN_BYTES_SPACE : parseInt((subscription.items.data[0].price.metadata as unknown as PriceMetadata).maxSpaceBytes); + const planTier = isSubscriptionCanceled + ? FREE_INDIVIDUAL_TIER : subscription.items.data[0].price.id; + try { await cacheService.clearSubscription(customerId); } catch (err) { log.error(`Error in handleSubscriptionUpdated after trying to clear ${customerId} subscription`); } + try { + await updateUserTier(uuid, planTier, config); + } catch (err) { + log.error( + `Error while updating user tier: uuid: ${uuid} `, + ); + log.error(err); + throw err; + } + return storageService.changeStorage(uuid, bytesSpace); } diff --git a/src/webhooks/index.ts b/src/webhooks/index.ts index cb006ca..a294447 100644 --- a/src/webhooks/index.ts +++ b/src/webhooks/index.ts @@ -48,6 +48,7 @@ export default function ( (event.data.object as Stripe.Subscription).customer as string, cacheService, fastify.log, + config, ); break; case 'customer.subscription.updated': @@ -57,6 +58,7 @@ export default function ( event.data.object as Stripe.Subscription, cacheService, fastify.log, + config, ); break; case 'payment_method.attached': @@ -94,6 +96,7 @@ export default function ( (event.data.object as Stripe.Charge).customer as string, cacheService, fastify.log, + config, ); break; default: From 2853d9cdd932b54af6a18c461aea208b93276eec Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Fri, 23 Feb 2024 10:51:03 -0400 Subject: [PATCH 2/5] fix: send productId instead of priceId --- src/services/StorageService.ts | 2 +- src/webhooks/handleCheckoutSessionCompleted.ts | 4 ++-- src/webhooks/handleSubscriptionUpdated.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index cdbb6a8..a934259 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -44,7 +44,7 @@ export async function createOrUpdateUser(maxSpaceBytes: string, email: string, c ); } -export async function updateUserTier(planId: string, uuid: string, config: AppConfig) { +export async function updateUserTier(uuid: string, planId: string, config: AppConfig) { return axios.put( `${config.DRIVE_GATEWAY_URL}/api/gateway/user/update/tier`, { planId, uuid }, diff --git a/src/webhooks/handleCheckoutSessionCompleted.ts b/src/webhooks/handleCheckoutSessionCompleted.ts index 62bf811..ba128a5 100644 --- a/src/webhooks/handleCheckoutSessionCompleted.ts +++ b/src/webhooks/handleCheckoutSessionCompleted.ts @@ -60,10 +60,10 @@ export default async function handleCheckoutSessionCompleted( } try { - await updateUserTier(user.uuid, price.id, config); + await updateUserTier(user.uuid, price.product, config); } catch (err) { log.error( - `Error while updating user tier: email: ${session.customer_email}, priceId: ${price.id} `, + `Error while updating user tier: email: ${session.customer_email}, planId: ${price.product} `, ); log.error(err); diff --git a/src/webhooks/handleSubscriptionUpdated.ts b/src/webhooks/handleSubscriptionUpdated.ts index 71ceafd..0e1285f 100644 --- a/src/webhooks/handleSubscriptionUpdated.ts +++ b/src/webhooks/handleSubscriptionUpdated.ts @@ -27,8 +27,8 @@ export default async function handleSubscriptionUpdated( ? FREE_PLAN_BYTES_SPACE : parseInt((subscription.items.data[0].price.metadata as unknown as PriceMetadata).maxSpaceBytes); - const planTier = isSubscriptionCanceled - ? FREE_INDIVIDUAL_TIER : subscription.items.data[0].price.id; + const planId = isSubscriptionCanceled + ? FREE_INDIVIDUAL_TIER : subscription.items.data[0].price.product; try { await cacheService.clearSubscription(customerId); @@ -37,7 +37,7 @@ export default async function handleSubscriptionUpdated( } try { - await updateUserTier(uuid, planTier, config); + await updateUserTier(uuid, planId, config); } catch (err) { log.error( `Error while updating user tier: uuid: ${uuid} `, From d362bdd227599cc7c7c1f20cb1153cbc2cc96a1c Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Fri, 23 Feb 2024 11:31:45 -0400 Subject: [PATCH 3/5] fix: use productId to update user tier --- src/core/users/User.ts | 1 + src/services/PaymentService.ts | 1 + src/webhooks/handleCheckoutSessionCompleted.ts | 2 +- src/webhooks/handleSubscriptionUpdated.ts | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/users/User.ts b/src/core/users/User.ts index 028f41d..8fb8be3 100644 --- a/src/core/users/User.ts +++ b/src/core/users/User.ts @@ -14,4 +14,5 @@ export type UserSubscription = interval: 'year' | 'month'; nextPayment: number; priceId: string; + productId: string; }; diff --git a/src/services/PaymentService.ts b/src/services/PaymentService.ts index 8c11239..bf2186b 100644 --- a/src/services/PaymentService.ts +++ b/src/services/PaymentService.ts @@ -343,6 +343,7 @@ export class PaymentService { nextPayment: subscription.current_period_end, amountAfterCoupon: upcomingInvoice.total, priceId: price.id, + productId: price.product as string, }; } diff --git a/src/webhooks/handleCheckoutSessionCompleted.ts b/src/webhooks/handleCheckoutSessionCompleted.ts index ba128a5..c62a23a 100644 --- a/src/webhooks/handleCheckoutSessionCompleted.ts +++ b/src/webhooks/handleCheckoutSessionCompleted.ts @@ -60,7 +60,7 @@ export default async function handleCheckoutSessionCompleted( } try { - await updateUserTier(user.uuid, price.product, config); + await updateUserTier(user.uuid, price.product as string, config); } catch (err) { log.error( `Error while updating user tier: email: ${session.customer_email}, planId: ${price.product} `, diff --git a/src/webhooks/handleSubscriptionUpdated.ts b/src/webhooks/handleSubscriptionUpdated.ts index 0e1285f..6ddc673 100644 --- a/src/webhooks/handleSubscriptionUpdated.ts +++ b/src/webhooks/handleSubscriptionUpdated.ts @@ -28,7 +28,7 @@ export default async function handleSubscriptionUpdated( : parseInt((subscription.items.data[0].price.metadata as unknown as PriceMetadata).maxSpaceBytes); const planId = isSubscriptionCanceled - ? FREE_INDIVIDUAL_TIER : subscription.items.data[0].price.product; + ? FREE_INDIVIDUAL_TIER : subscription.items.data[0].price.product as string; try { await cacheService.clearSubscription(customerId); From 5bfd3f1588f8214d9ef2fe1407a5125ff48d0145 Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Mon, 4 Mar 2024 23:30:22 -0400 Subject: [PATCH 4/5] chore: remove productId --- src/core/users/User.ts | 1 - src/services/PaymentService.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/core/users/User.ts b/src/core/users/User.ts index 8fb8be3..028f41d 100644 --- a/src/core/users/User.ts +++ b/src/core/users/User.ts @@ -14,5 +14,4 @@ export type UserSubscription = interval: 'year' | 'month'; nextPayment: number; priceId: string; - productId: string; }; diff --git a/src/services/PaymentService.ts b/src/services/PaymentService.ts index bf2186b..8c11239 100644 --- a/src/services/PaymentService.ts +++ b/src/services/PaymentService.ts @@ -343,7 +343,6 @@ export class PaymentService { nextPayment: subscription.current_period_end, amountAfterCoupon: upcomingInvoice.total, priceId: price.id, - productId: price.product as string, }; } From f5021f01b9531363383a48651f299f522d9ce436 Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Mon, 15 Apr 2024 08:32:49 -0400 Subject: [PATCH 5/5] chore: prevent fail when updating user tier --- src/webhooks/handleSubscriptionCanceled.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webhooks/handleSubscriptionCanceled.ts b/src/webhooks/handleSubscriptionCanceled.ts index b56581f..15f3ec7 100644 --- a/src/webhooks/handleSubscriptionCanceled.ts +++ b/src/webhooks/handleSubscriptionCanceled.ts @@ -24,10 +24,9 @@ export default async function handleSubscriptionCanceled( await updateUserTier(uuid, FREE_INDIVIDUAL_TIER, config); } catch (err) { log.error( - `Error while updating user tier: uuid: ${uuid} `, + `[TIER/SUB_CANCELED] Error while updating user tier: uuid: ${uuid} `, ); log.error(err); - throw err; } return storageService.changeStorage(uuid, FREE_PLAN_BYTES_SPACE);