From 822ece71f66823560c8eab249bd8ba51a28617c8 Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Sun, 9 Feb 2025 17:27:24 +0700 Subject: [PATCH] Use stripe integration --- apps/web/src/app/api/stripe/checkout/route.ts | 1 + apps/web/src/app/api/stripe/webhook/route.ts | 27 ++++++++++++++----- packages/emails/src/send-email.tsx | 6 ++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/api/stripe/checkout/route.ts b/apps/web/src/app/api/stripe/checkout/route.ts index 7bebd928e7d..65f5799e8ac 100644 --- a/apps/web/src/app/api/stripe/checkout/route.ts +++ b/apps/web/src/app/api/stripe/checkout/route.ts @@ -103,6 +103,7 @@ export async function POST(request: NextRequest) { automatic_tax: { enabled: true, }, + expires_at: Math.floor(Date.now() / 1000) + 30 * 60, // 30 minutes after_expiration: { recovery: { enabled: true, diff --git a/apps/web/src/app/api/stripe/webhook/route.ts b/apps/web/src/app/api/stripe/webhook/route.ts index 593a164906a..5b742a393a2 100644 --- a/apps/web/src/app/api/stripe/webhook/route.ts +++ b/apps/web/src/app/api/stripe/webhook/route.ts @@ -210,6 +210,7 @@ export async function POST(request: NextRequest) { break; } case "checkout.session.expired": { + console.info("Checkout session expired"); const session = event.data.object as Stripe.Checkout.Session; // When a Checkout Session expires, the customer's email isn't returned in // the webhook payload unless they give consent for promotional content @@ -217,11 +218,13 @@ export async function POST(request: NextRequest) { const recoveryUrl = session.after_expiration?.recovery?.url; const userId = session.metadata?.userId; if (!userId) { + console.info("No user ID found in Checkout Session metadata"); Sentry.captureMessage("No user ID found in Checkout Session metadata"); break; } // Do nothing if the Checkout Session has no email or recovery URL if (!email || !recoveryUrl) { + console.info("No email or recovery URL found in Checkout Session"); Sentry.captureMessage( "No email or recovery URL found in Checkout Session", ); @@ -230,12 +233,14 @@ export async function POST(request: NextRequest) { const promoEmailKey = `promo_email_sent:${email}`; // Track that a promotional email opportunity has been shown to this user const hasReceivedPromo = await kv.get(promoEmailKey); + console.info("Has received promo", hasReceivedPromo); const user = await prisma.user.findUnique({ where: { id: userId, }, select: { + locale: true, subscription: { select: { active: true, @@ -247,16 +252,24 @@ export async function POST(request: NextRequest) { const isPro = !!user?.subscription?.active; // Avoid spamming people who abandon Checkout multiple times - if (!hasReceivedPromo && !isPro) { + if (user && !hasReceivedPromo && !isPro) { + console.info("Sending abandoned checkout email"); // Set the flag with a 30-day expiration (in seconds) await kv.set(promoEmailKey, 1, { ex: 30 * 24 * 60 * 60, nx: true }); - getEmailClient().sendTemplate("AbandonedCheckoutEmail", { - to: email, - props: { - name: session.customer_details?.name ?? undefined, - recoveryUrl, + getEmailClient(user.locale ?? undefined).sendTemplate( + "AbandonedCheckoutEmail", + { + to: email, + from: { + name: "Luke from Rallly", + address: "luke@rallly.co", + }, + props: { + name: session.customer_details?.name ?? undefined, + recoveryUrl, + }, }, - }); + ); } break; diff --git a/packages/emails/src/send-email.tsx b/packages/emails/src/send-email.tsx index 2e694e6cd61..cc71467708c 100644 --- a/packages/emails/src/send-email.tsx +++ b/packages/emails/src/send-email.tsx @@ -12,6 +12,10 @@ import type { TemplateComponent, TemplateName, TemplateProps } from "./types"; type SendEmailOptions = { to: string; + from?: { + name: string; + address: string; + }; props: TemplateProps; attachments?: Mail.Options["attachments"]; }; @@ -106,7 +110,7 @@ export class EmailClient { try { await this.sendEmail({ - from: this.config.mail.from, + from: options.from || this.config.mail.from, to: options.to, subject, html,