From 3024399de7cfa542c41755131944607fa33fb89f Mon Sep 17 00:00:00 2001 From: d-ivashchuk Date: Mon, 1 Apr 2024 13:21:04 +0200 Subject: [PATCH] add notifications of subscription create & churn --- .../(user-scope)/billing/page.tsx | 2 +- src/app/api/lemon-squeezy/webhook/route.tsx | 34 +++++++++++-- src/jobs/jobs.ts | 48 +++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/app/(authenticated-routes)/(user-scope)/billing/page.tsx b/src/app/(authenticated-routes)/(user-scope)/billing/page.tsx index 10fe2e6..e6155a9 100644 --- a/src/app/(authenticated-routes)/(user-scope)/billing/page.tsx +++ b/src/app/(authenticated-routes)/(user-scope)/billing/page.tsx @@ -27,7 +27,7 @@ const Billing = () => { {userSubscriptionQuery.isLoading && ( )} - {userSubscriptionQuery.data ? ( + {subscription ? (

Subscription

diff --git a/src/app/api/lemon-squeezy/webhook/route.tsx b/src/app/api/lemon-squeezy/webhook/route.tsx index d80dfda..937fd7e 100644 --- a/src/app/api/lemon-squeezy/webhook/route.tsx +++ b/src/app/api/lemon-squeezy/webhook/route.tsx @@ -7,6 +7,8 @@ import { type LemonsqueezySubscriptionAttributes, type LemonsqueezyWebhookPayload, } from "~/types/lemonsqueezy"; +import { isTriggerEnabled } from "~/lib/trigger"; +import { slackNewChurnNotification, slackNewPaymentNotification } from "~/jobs"; export async function POST(request: Request) { if (!env.LEMON_SQUEEZY_WEBHOOK_SECRET) { @@ -51,6 +53,31 @@ export async function POST(request: Request) { where: { lemonSqueezyId: lemonSqueezySubscriptionId }, }); + if (isTriggerEnabled) { + if (event === "subscription_created") { + await slackNewPaymentNotification.invoke({ + user: { + email: subscriptionData.user_email, + id: userIdInDatabase, + }, + productName: subscriptionData.product_name, + }); + } + if ( + event === "subscription_cancelled" || + (event === "subscription_updated" && + subscriptionData.status === "cancelled") + ) { + await slackNewChurnNotification.invoke({ + user: { + email: subscriptionData.user_email, + id: userIdInDatabase, + }, + productName: subscriptionData.product_name, + }); + } + } + switch (event) { case "subscription_created": case "subscription_updated": @@ -61,9 +88,10 @@ export async function POST(request: Request) { renewsAt: subscriptionData.renews_at ? new Date(subscriptionData.renews_at) : null, - endsAt: subscriptionData.ends_at - ? new Date(subscriptionData.ends_at) - : null, + endsAt: + subscriptionData.status === "cancelled" + ? new Date(existingSubscription?.renewsAt ?? new Date()) + : subscriptionData.ends_at ?? null, trialEndsAt: subscriptionData.trial_ends_at ? new Date(subscriptionData.trial_ends_at) : null, diff --git a/src/jobs/jobs.ts b/src/jobs/jobs.ts index 9ad1a63..9b1cff0 100644 --- a/src/jobs/jobs.ts +++ b/src/jobs/jobs.ts @@ -34,3 +34,51 @@ export const slackNewUserNotification = triggerClient.defineJob({ }); }, }); +export const slackNewPaymentNotification = triggerClient.defineJob({ + id: "cascade-new-payment", + name: "Cascade new payment", + version: "0.0.1", + trigger: eventTrigger({ + name: "cascade.new.payment", + schema: z.object({ + user: z.object({ + email: z.string().email(), + id: z.string(), + }), + productName: z.string(), + }), + }), + integrations: { + slack, + }, + run: async (payload, io) => { + await io.slack.postMessage("post message", { + channel: "C06RZ0QNP6W", + text: `🔥 *New payment*\n\nEmail: ${payload.user.email}\nID:${payload.user.id}`, + }); + }, +}); +export const slackNewChurnNotification = triggerClient.defineJob({ + id: "cascade-new-churn", + name: "Cascade new churn", + version: "0.0.1", + trigger: eventTrigger({ + name: "cascade.new.churn", + schema: z.object({ + user: z.object({ + email: z.string().email(), + id: z.string(), + }), + productName: z.string(), + }), + }), + integrations: { + slack, + }, + run: async (payload, io) => { + await io.slack.postMessage("post message", { + channel: "C06RZ0QNP6W", + text: `👋🏼 *User churned*\n\nEmail: ${payload.user.email}`, + }); + }, +});