let user know when his cancelled sub is going to expire

This commit is contained in:
m5r 2021-10-03 20:56:31 +02:00
parent 5f3060c591
commit a28d89a8c2
7 changed files with 40 additions and 25 deletions

View File

@ -4,12 +4,13 @@ import type { PaddleSdkSubscriptionCancelledEvent } from "@devoxa/paddle-sdk";
import db from "db"; import db from "db";
import appLogger from "integrations/logger"; import appLogger from "integrations/logger";
import type { Metadata } from "integrations/paddle";
import { translateSubscriptionStatus } from "integrations/paddle"; import { translateSubscriptionStatus } from "integrations/paddle";
const logger = appLogger.child({ queue: "subscription-cancelled" }); const logger = appLogger.child({ queue: "subscription-cancelled" });
type Payload = { type Payload = {
event: PaddleSdkSubscriptionCancelledEvent<{ organizationId: string }>; event: PaddleSdkSubscriptionCancelledEvent<Metadata>;
}; };
export const subscriptionCancelledQueue = Queue<Payload>("api/queue/subscription-cancelled", async ({ event }) => { export const subscriptionCancelledQueue = Queue<Payload>("api/queue/subscription-cancelled", async ({ event }) => {
@ -35,6 +36,7 @@ export const subscriptionCancelledQueue = Queue<Payload>("api/queue/subscription
lastEventTime, lastEventTime,
currency: event.currency, currency: event.currency,
unitPrice: event.unitPrice, unitPrice: event.unitPrice,
cancellationEffectiveDate: event.cancelledFrom,
}, },
}); });
}); });

View File

@ -5,12 +5,13 @@ import type { PaddleSdkSubscriptionCreatedEvent } from "@devoxa/paddle-sdk";
import db, { MembershipRole } from "db"; import db, { MembershipRole } from "db";
import appLogger from "integrations/logger"; import appLogger from "integrations/logger";
import { sendEmail } from "integrations/ses"; import { sendEmail } from "integrations/ses";
import type { Metadata } from "integrations/paddle";
import { translateSubscriptionStatus } from "integrations/paddle"; import { translateSubscriptionStatus } from "integrations/paddle";
const logger = appLogger.child({ queue: "subscription-created" }); const logger = appLogger.child({ queue: "subscription-created" });
type Payload = { type Payload = {
event: PaddleSdkSubscriptionCreatedEvent<{ organizationId: string }>; event: PaddleSdkSubscriptionCreatedEvent<Metadata>;
}; };
export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-created", async ({ event }) => { export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-created", async ({ event }) => {

View File

@ -24,7 +24,8 @@ const Billing: BlitzPage<Props> = (props) => {
TODO: I want to be able to TODO: I want to be able to
- upgrade to yearly - upgrade to yearly
- downgrade to monthly - 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(); useRequireOnboarding();
@ -37,16 +38,24 @@ const Billing: BlitzPage<Props> = (props) => {
<> <>
{subscription ? ( {subscription ? (
<SettingsSection> <SettingsSection>
<p>Current plan: {subscription.paddlePlanId}</p> {subscription.status === SubscriptionStatus.deleted ? (
<p>
<PaddleLink Your {subscription.paddlePlanId} subscription is cancelled and will expire on{" "}
onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })} {subscription.cancellationEffectiveDate!.toLocaleDateString()}.
text="Update payment method" </p>
/> ) : (
<PaddleLink <>
onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })} <p>Current plan: {subscription.paddlePlanId}</p>
text="Cancel subscription" <PaddleLink
/> onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })}
text="Update payment method"
/>
<PaddleLink
onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })}
text="Cancel subscription"
/>
</>
)}
</SettingsSection> </SettingsSection>
) : null} ) : null}

View File

@ -8,7 +8,7 @@ export default resolver.pipe(resolver.authorize(), async (_ = null, { session })
return db.subscription.findFirst({ return db.subscription.findFirst({
where: { where: {
organizationId: session.orgId, organizationId: session.orgId,
status: { not: SubscriptionStatus.deleted }, OR: [{ status: { not: SubscriptionStatus.deleted } }, { cancellationEffectiveDate: { gt: new Date() } }],
}, },
}); });
}); });

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Subscription" ADD COLUMN "cancellationEffectiveDate" DATE;

View File

@ -39,16 +39,17 @@ model Subscription {
createdAt DateTime @default(now()) @db.Timestamptz createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz updatedAt DateTime @updatedAt @db.Timestamptz
paddleSubscriptionId Int @id @unique paddleSubscriptionId Int @id @unique
paddlePlanId Int paddlePlanId Int
paddleCheckoutId String paddleCheckoutId String
status SubscriptionStatus status SubscriptionStatus
updateUrl String updateUrl String
cancelUrl String cancelUrl String
currency String currency String
unitPrice Int unitPrice Int
nextBillDate DateTime @db.Date nextBillDate DateTime @db.Date
lastEventTime DateTime @db.Timestamp lastEventTime DateTime @db.Timestamp
cancellationEffectiveDate DateTime? @db.Date
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String organizationId String

View File

@ -24,7 +24,7 @@ export const paddleSdk = new PaddleSdk({
export type Metadata = { organizationId: string }; export type Metadata = { organizationId: string };
export function translateSubscriptionStatus( export function translateSubscriptionStatus(
status: PaddleSdkSubscriptionCreatedEvent<unknown>["status"], status: PaddleSdkSubscriptionCreatedEvent<Metadata>["status"],
): SubscriptionStatus { ): SubscriptionStatus {
switch (status) { switch (status) {
case PaddleSdkSubscriptionStatus.ACTIVE: case PaddleSdkSubscriptionStatus.ACTIVE: