From a28d89a8c2a8a25cc30a9d4ab42ec395b6e4ee10 Mon Sep 17 00:00:00 2001 From: m5r Date: Sun, 3 Oct 2021 20:56:31 +0200 Subject: [PATCH] let user know when his cancelled sub is going to expire --- .../api/queue/subscription-cancelled.ts | 4 ++- .../api/queue/subscription-created.ts | 3 +- app/settings/pages/settings/billing.tsx | 31 ++++++++++++------- app/settings/queries/get-subscription.ts | 2 +- .../migration.sql | 2 ++ db/schema.prisma | 21 +++++++------ integrations/paddle.ts | 2 +- 7 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 db/migrations/20211003184129_add_subscription_cancellation_effective_date/migration.sql diff --git a/app/settings/api/queue/subscription-cancelled.ts b/app/settings/api/queue/subscription-cancelled.ts index 7fea3fa..64bd10a 100644 --- a/app/settings/api/queue/subscription-cancelled.ts +++ b/app/settings/api/queue/subscription-cancelled.ts @@ -4,12 +4,13 @@ import type { PaddleSdkSubscriptionCancelledEvent } from "@devoxa/paddle-sdk"; import db from "db"; import appLogger from "integrations/logger"; +import type { Metadata } from "integrations/paddle"; import { translateSubscriptionStatus } from "integrations/paddle"; const logger = appLogger.child({ queue: "subscription-cancelled" }); type Payload = { - event: PaddleSdkSubscriptionCancelledEvent<{ organizationId: string }>; + event: PaddleSdkSubscriptionCancelledEvent; }; export const subscriptionCancelledQueue = Queue("api/queue/subscription-cancelled", async ({ event }) => { @@ -35,6 +36,7 @@ export const subscriptionCancelledQueue = Queue("api/queue/subscription lastEventTime, currency: event.currency, unitPrice: event.unitPrice, + cancellationEffectiveDate: event.cancelledFrom, }, }); }); diff --git a/app/settings/api/queue/subscription-created.ts b/app/settings/api/queue/subscription-created.ts index 112df96..535ae96 100644 --- a/app/settings/api/queue/subscription-created.ts +++ b/app/settings/api/queue/subscription-created.ts @@ -5,12 +5,13 @@ import type { PaddleSdkSubscriptionCreatedEvent } from "@devoxa/paddle-sdk"; import db, { MembershipRole } from "db"; import appLogger from "integrations/logger"; import { sendEmail } from "integrations/ses"; +import type { Metadata } from "integrations/paddle"; import { translateSubscriptionStatus } from "integrations/paddle"; const logger = appLogger.child({ queue: "subscription-created" }); type Payload = { - event: PaddleSdkSubscriptionCreatedEvent<{ organizationId: string }>; + event: PaddleSdkSubscriptionCreatedEvent; }; export const subscriptionCreatedQueue = Queue("api/queue/subscription-created", async ({ event }) => { diff --git a/app/settings/pages/settings/billing.tsx b/app/settings/pages/settings/billing.tsx index 88c6394..6ebe64b 100644 --- a/app/settings/pages/settings/billing.tsx +++ b/app/settings/pages/settings/billing.tsx @@ -24,7 +24,8 @@ const Billing: BlitzPage = (props) => { TODO: I want to be able to - upgrade to yearly - downgrade to monthly - - resubscribe (after pause/cancel for example) (message like "your subscription expired, would you like to renew ?") + - know how much time I have left until my cancelled subscription runs out --- DONE + - resubscribe (message like "your subscription expired, would you like to renew ?") */ useRequireOnboarding(); @@ -37,16 +38,24 @@ const Billing: BlitzPage = (props) => { <> {subscription ? ( -

Current plan: {subscription.paddlePlanId}

- - updatePaymentMethod({ updateUrl: subscription.updateUrl })} - text="Update payment method" - /> - cancelSubscription({ cancelUrl: subscription.cancelUrl })} - text="Cancel subscription" - /> + {subscription.status === SubscriptionStatus.deleted ? ( +

+ Your {subscription.paddlePlanId} subscription is cancelled and will expire on{" "} + {subscription.cancellationEffectiveDate!.toLocaleDateString()}. +

+ ) : ( + <> +

Current plan: {subscription.paddlePlanId}

+ updatePaymentMethod({ updateUrl: subscription.updateUrl })} + text="Update payment method" + /> + cancelSubscription({ cancelUrl: subscription.cancelUrl })} + text="Cancel subscription" + /> + + )}
) : null} diff --git a/app/settings/queries/get-subscription.ts b/app/settings/queries/get-subscription.ts index 0fc9f39..693520a 100644 --- a/app/settings/queries/get-subscription.ts +++ b/app/settings/queries/get-subscription.ts @@ -8,7 +8,7 @@ export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) return db.subscription.findFirst({ where: { organizationId: session.orgId, - status: { not: SubscriptionStatus.deleted }, + OR: [{ status: { not: SubscriptionStatus.deleted } }, { cancellationEffectiveDate: { gt: new Date() } }], }, }); }); diff --git a/db/migrations/20211003184129_add_subscription_cancellation_effective_date/migration.sql b/db/migrations/20211003184129_add_subscription_cancellation_effective_date/migration.sql new file mode 100644 index 0000000..66dabb0 --- /dev/null +++ b/db/migrations/20211003184129_add_subscription_cancellation_effective_date/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Subscription" ADD COLUMN "cancellationEffectiveDate" DATE; diff --git a/db/schema.prisma b/db/schema.prisma index 37e2683..f59c38e 100644 --- a/db/schema.prisma +++ b/db/schema.prisma @@ -39,16 +39,17 @@ model Subscription { createdAt DateTime @default(now()) @db.Timestamptz updatedAt DateTime @updatedAt @db.Timestamptz - paddleSubscriptionId Int @id @unique - paddlePlanId Int - paddleCheckoutId String - status SubscriptionStatus - updateUrl String - cancelUrl String - currency String - unitPrice Int - nextBillDate DateTime @db.Date - lastEventTime DateTime @db.Timestamp + paddleSubscriptionId Int @id @unique + paddlePlanId Int + paddleCheckoutId String + status SubscriptionStatus + updateUrl String + cancelUrl String + currency String + unitPrice Int + nextBillDate DateTime @db.Date + lastEventTime DateTime @db.Timestamp + cancellationEffectiveDate DateTime? @db.Date organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) organizationId String diff --git a/integrations/paddle.ts b/integrations/paddle.ts index 28b2915..f4ff5d6 100644 --- a/integrations/paddle.ts +++ b/integrations/paddle.ts @@ -24,7 +24,7 @@ export const paddleSdk = new PaddleSdk({ export type Metadata = { organizationId: string }; export function translateSubscriptionStatus( - status: PaddleSdkSubscriptionCreatedEvent["status"], + status: PaddleSdkSubscriptionCreatedEvent["status"], ): SubscriptionStatus { switch (status) { case PaddleSdkSubscriptionStatus.ACTIVE: