allow organizations to have multiple subscriptions. although only 1 can be active at a time
This commit is contained in:
parent
22e2b21b14
commit
3a3d526e77
@ -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<Payload>("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<Payload>("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;
|
||||
|
@ -18,7 +18,7 @@ export const subscriptionCreatedQueue = Queue<Payload>("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<Payload>("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<Payload>("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;
|
||||
|
@ -42,6 +42,7 @@ export const subscriptionPaymentSucceededQueue = Queue<Payload>(
|
||||
},
|
||||
});
|
||||
},
|
||||
{ retry: ["30s", "1m", "5m"] },
|
||||
);
|
||||
|
||||
export default subscriptionPaymentSucceededQueue;
|
||||
|
@ -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 (
|
||||
<div className="mt-6 flex flex-row-reverse flex-wrap-reverse gap-x-4">
|
||||
@ -62,8 +63,13 @@ export default function Plans() {
|
||||
<button
|
||||
disabled={isCurrentTier}
|
||||
onClick={() => {
|
||||
subscribe({ planId: tier.planId });
|
||||
Panelbear.track(`Subscribe to ${tier.title}`);
|
||||
if (hasSubscription) {
|
||||
changePlan({ planId: tier.planId });
|
||||
Panelbear.track(`Subscribe to ${tier.title}`);
|
||||
} else {
|
||||
subscribe({ planId: tier.planId, coupon: "groot429" });
|
||||
Panelbear.track(`Subscribe to ${tier.title}`);
|
||||
}
|
||||
}}
|
||||
className={clsx(
|
||||
!isCurrentTier
|
||||
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `organizationId` on table `Subscription` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "Subscription_organizationId_unique";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Subscription" ALTER COLUMN "organizationId" SET NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ALTER COLUMN "fullName" DROP DEFAULT;
|
@ -30,7 +30,7 @@ model Organization {
|
||||
messages Message[]
|
||||
phoneCalls PhoneCall[]
|
||||
processingPhoneNumbers ProcessingPhoneNumber[]
|
||||
subscription Subscription?
|
||||
subscriptions Subscription[]
|
||||
|
||||
@@unique([id, twilioAccountSid])
|
||||
}
|
||||
@ -50,8 +50,8 @@ model Subscription {
|
||||
nextBillDate DateTime @db.Date
|
||||
lastEventTime DateTime @db.Timestamp
|
||||
|
||||
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
organizationId String?
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
organizationId String
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
@ -95,7 +95,7 @@ model User {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz
|
||||
fullName String @default("")
|
||||
fullName String
|
||||
email String @unique
|
||||
hashedPassword String?
|
||||
role GlobalRole @default(CUSTOMER)
|
||||
|
Loading…
Reference in New Issue
Block a user