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,8 +38,14 @@ const Billing: BlitzPage<Props> = (props) => {
<> <>
{subscription ? ( {subscription ? (
<SettingsSection> <SettingsSection>
{subscription.status === SubscriptionStatus.deleted ? (
<p>
Your {subscription.paddlePlanId} subscription is cancelled and will expire on{" "}
{subscription.cancellationEffectiveDate!.toLocaleDateString()}.
</p>
) : (
<>
<p>Current plan: {subscription.paddlePlanId}</p> <p>Current plan: {subscription.paddlePlanId}</p>
<PaddleLink <PaddleLink
onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })} onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })}
text="Update payment method" text="Update payment method"
@ -47,6 +54,8 @@ const Billing: BlitzPage<Props> = (props) => {
onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })} onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })}
text="Cancel subscription" 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

@ -49,6 +49,7 @@ model Subscription {
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: