mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-03 16:38:34 +02:00
✨ Send recovery url to users with expired checkout sessions (#1555)
This commit is contained in:
parent
5437b91c10
commit
9fdd5f3ea3
13 changed files with 341 additions and 23 deletions
|
@ -103,6 +103,13 @@ 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,
|
||||
allow_promotion_codes: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (session.url) {
|
||||
|
|
|
@ -4,10 +4,13 @@ import { prisma } from "@rallly/database";
|
|||
import { posthog } from "@rallly/posthog/server";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { kv } from "@vercel/kv";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getEmailClient } from "@/utils/emails";
|
||||
|
||||
const checkoutMetadataSchema = z.object({
|
||||
userId: z.string(),
|
||||
});
|
||||
|
@ -206,6 +209,71 @@ 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
|
||||
const email = session.customer_details?.email;
|
||||
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",
|
||||
);
|
||||
break;
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isPro = !!user?.subscription?.active;
|
||||
|
||||
// Avoid spamming people who abandon Checkout multiple times
|
||||
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(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;
|
||||
}
|
||||
default:
|
||||
Sentry.captureException(new Error(`Unhandled event type: ${event.type}`));
|
||||
// Unexpected event type
|
||||
|
|
|
@ -20,7 +20,7 @@ export const getEmailClient = (locale?: string) => {
|
|||
config: {
|
||||
logoUrl: isSelfHosted
|
||||
? absoluteUrl("/images/rallly-logo-mark.png")
|
||||
: "https://rallly-public.s3.amazonaws.com/images/rallly-logo-mark.png",
|
||||
: "https://d39ixtfgglw55o.cloudfront.net/images/rallly-logo-mark.png",
|
||||
baseUrl: absoluteUrl(),
|
||||
domain: absoluteUrl().replace(/(^\w+:|^)\/\//, ""),
|
||||
supportEmail: env.SUPPORT_EMAIL,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue