From 3a3d526e77c9163176b7f7735d3f72607b0ae254 Mon Sep 17 00:00:00 2001 From: m5r Date: Sun, 3 Oct 2021 18:19:45 +0200 Subject: [PATCH] allow organizations to have multiple subscriptions. although only 1 can be active at a time --- app/settings/api/queue/delete-user-data.ts | 16 ++++-- .../api/queue/subscription-created.ts | 51 +++++++++---------- .../queue/subscription-payment-succeeded.ts | 1 + app/settings/components/billing/plans.tsx | 12 +++-- .../migration.sql | 14 +++++ db/schema.prisma | 8 +-- 6 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 db/migrations/20211002172141_org_can_have_multiple_subscriptions/migration.sql diff --git a/app/settings/api/queue/delete-user-data.ts b/app/settings/api/queue/delete-user-data.ts index 627d51e..d209e90 100644 --- a/app/settings/api/queue/delete-user-data.ts +++ b/app/settings/api/queue/delete-user-data.ts @@ -1,6 +1,6 @@ import { Queue } from "quirrel/blitz"; -import db, { MembershipRole } from "../../../../db"; +import db, { MembershipRole, SubscriptionStatus } from "../../../../db"; import appLogger from "../../../../integrations/logger"; import { cancelPaddleSubscription } from "../../../../integrations/paddle"; @@ -17,7 +17,11 @@ const deleteUserData = Queue("api/queue/delete-user-data", async ({ use memberships: { include: { organization: { - include: { subscription: true }, + include: { + subscriptions: { + where: { status: { not: SubscriptionStatus.deleted } }, + }, + }, }, }, }, @@ -33,8 +37,12 @@ const deleteUserData = Queue("api/queue/delete-user-data", async ({ use await db.organization.delete({ where: { id: organization.id } }); await db.user.delete({ where: { id: user.id } }); - if (organization.subscription) { - await cancelPaddleSubscription({ subscriptionId: organization.subscription.paddleSubscriptionId }); + if (organization.subscriptions.length > 0) { + await Promise.all( + organization.subscriptions.map((subscription) => + cancelPaddleSubscription({ subscriptionId: subscription.paddleSubscriptionId }), + ), + ); } break; diff --git a/app/settings/api/queue/subscription-created.ts b/app/settings/api/queue/subscription-created.ts index 9fd76a8..112df96 100644 --- a/app/settings/api/queue/subscription-created.ts +++ b/app/settings/api/queue/subscription-created.ts @@ -18,7 +18,7 @@ export const subscriptionCreatedQueue = Queue("api/queue/subscription-c const organization = await db.organization.findFirst({ where: { id: organizationId }, include: { - subscription: true, + subscriptions: true, memberships: { include: { user: true }, }, @@ -28,29 +28,26 @@ export const subscriptionCreatedQueue = Queue("api/queue/subscription-c throw new NotFoundError(); } + const isReturningSubscriber = organization.subscriptions.length > 0; const orgOwner = organization.memberships.find((membership) => membership.role === MembershipRole.OWNER); const email = orgOwner!.user!.email; - await db.organization.update({ - where: { id: organizationId }, + await db.subscription.create({ data: { - subscription: { - create: { - paddleSubscriptionId: event.subscriptionId, - paddlePlanId: event.productId, - paddleCheckoutId: event.checkoutId, - nextBillDate: event.nextPaymentDate, - status: translateSubscriptionStatus(event.status), - lastEventTime: event.eventTime, - updateUrl: event.updateUrl, - cancelUrl: event.cancelUrl, - currency: event.currency, - unitPrice: event.unitPrice, - }, - }, + organizationId, + paddleSubscriptionId: event.subscriptionId, + paddlePlanId: event.productId, + paddleCheckoutId: event.checkoutId, + nextBillDate: event.nextPaymentDate, + status: translateSubscriptionStatus(event.status), + lastEventTime: event.eventTime, + updateUrl: event.updateUrl, + cancelUrl: event.cancelUrl, + currency: event.currency, + unitPrice: event.unitPrice, }, }); - if (!!organization.subscription) { + if (isReturningSubscriber) { sendEmail({ subject: "Welcome back to Shellphone", body: "Welcome back to Shellphone", @@ -58,15 +55,17 @@ export const subscriptionCreatedQueue = Queue("api/queue/subscription-c }).catch((error) => { logger.error(error); }); - } else { - sendEmail({ - subject: "Welcome to Shellphone", - body: `Welcome to Shellphone`, - recipients: [email], - }).catch((error) => { - logger.error(error); - }); + + return; } + + sendEmail({ + subject: "Welcome to Shellphone", + body: `Welcome to Shellphone`, + recipients: [email], + }).catch((error) => { + logger.error(error); + }); }); export default subscriptionCreatedQueue; diff --git a/app/settings/api/queue/subscription-payment-succeeded.ts b/app/settings/api/queue/subscription-payment-succeeded.ts index f92b159..e35afc2 100644 --- a/app/settings/api/queue/subscription-payment-succeeded.ts +++ b/app/settings/api/queue/subscription-payment-succeeded.ts @@ -42,6 +42,7 @@ export const subscriptionPaymentSucceededQueue = Queue( }, }); }, + { retry: ["30s", "1m", "5m"] }, ); export default subscriptionPaymentSucceededQueue; diff --git a/app/settings/components/billing/plans.tsx b/app/settings/components/billing/plans.tsx index b3b42b0..aa3ed15 100644 --- a/app/settings/components/billing/plans.tsx +++ b/app/settings/components/billing/plans.tsx @@ -5,7 +5,8 @@ import clsx from "clsx"; import useSubscription from "../../hooks/use-subscription"; export default function Plans() { - const { subscription, subscribe } = useSubscription(); + const { subscription, subscribe, changePlan } = useSubscription(); + const hasSubscription = Boolean(subscription); return (
@@ -62,8 +63,13 @@ export default function Plans() {