add stripe / payment events

This commit is contained in:
MarconLP 2023-04-23 19:52:14 +02:00
parent d778f61f33
commit 899a604d6d
No known key found for this signature in database
GPG key ID: A08A9C8B623F5EA5
3 changed files with 123 additions and 55 deletions

View file

@ -9,6 +9,7 @@ import {
handleSubscriptionCreatedOrUpdated,
} from "~/server/stripe-webhook-handlers";
import { stripe } from "~/server/stripe";
import { posthog } from "~/server/posthog";
// Stripe requires the raw body to construct the event.
export const config = {
@ -41,6 +42,7 @@ export default async function handler(
event,
stripe,
prisma,
posthog,
});
break;
case "customer.subscription.created":
@ -48,6 +50,7 @@ export default async function handler(
await handleSubscriptionCreatedOrUpdated({
event,
prisma,
posthog,
});
break;
case "customer.subscription.updated":
@ -55,6 +58,7 @@ export default async function handler(
await handleSubscriptionCreatedOrUpdated({
event,
prisma,
posthog,
});
break;
case "invoice.payment_failed":
@ -63,6 +67,17 @@ export default async function handler(
// Use this webhook to notify your user that their payment has
// failed and to retrieve new card details.
// Can also have Stripe send an email to the customer notifying them of the failure. See settings: https://dashboard.stripe.com/settings/billing/automatic
const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata.userId;
if (userId) {
posthog.capture({
distinctId: userId,
event: "stripe invoice.payment_failed",
});
void posthog.shutdownAsync();
}
break;
case "customer.subscription.deleted":
// handle subscription cancelled automatically based
@ -70,6 +85,7 @@ export default async function handler(
await handleSubscriptionCanceled({
event,
prisma,
posthog,
});
break;
default:

View file

@ -6,9 +6,64 @@ import { z } from "zod";
export const stripeRouter = createTRPCRouter({
createCheckoutSession: protectedProcedure
.input(z.object({ billedAnnually: z.boolean() }))
.mutation(async ({ ctx, input }) => {
const { stripe, session, prisma, req } = ctx;
.mutation(
async ({ ctx: { prisma, stripe, session, req, posthog }, input }) => {
const customerId = await getOrCreateStripeCustomerIdForUser({
prisma,
stripe,
userId: session.user?.id,
});
if (!customerId) {
throw new Error("Could not create customer");
}
const baseUrl =
env.NODE_ENV === "development"
? `http://${req.headers.host ?? "localhost:3000"}`
: `https://${req.headers.host ?? env.NEXTAUTH_URL}`;
const checkoutSession = await stripe.checkout.sessions.create({
customer: customerId,
client_reference_id: session.user?.id,
payment_method_types: ["card"],
allow_promotion_codes: true,
mode: "subscription",
line_items: [
{
price: input.billedAnnually
? env.STRIPE_ANNUAL_PRICE_ID
: env.STRIPE_MONTHLY_PRICE_ID,
quantity: 1,
},
],
success_url: `${baseUrl}/videos?checkoutSuccess=true`,
cancel_url: `${baseUrl}/videos?checkoutCanceled=true`,
subscription_data: {
metadata: {
userId: session.user?.id,
},
},
});
if (!checkoutSession) {
throw new Error("Could not create checkout session");
}
posthog.capture({
distinctId: session.user.id,
event: "visiting checkout page",
properties: {
billingCycle: input.billedAnnually ? "annual" : "monthly",
},
});
void posthog.shutdownAsync();
return { checkoutUrl: checkoutSession.url };
}
),
createBillingPortalSession: protectedProcedure.mutation(
async ({ ctx: { stripe, session, prisma, req, posthog } }) => {
const customerId = await getOrCreateStripeCustomerIdForUser({
prisma,
stripe,
@ -24,63 +79,26 @@ export const stripeRouter = createTRPCRouter({
? `http://${req.headers.host ?? "localhost:3000"}`
: `https://${req.headers.host ?? env.NEXTAUTH_URL}`;
const checkoutSession = await stripe.checkout.sessions.create({
customer: customerId,
client_reference_id: session.user?.id,
payment_method_types: ["card"],
allow_promotion_codes: true,
mode: "subscription",
line_items: [
{
price: input.billedAnnually
? env.STRIPE_ANNUAL_PRICE_ID
: env.STRIPE_MONTHLY_PRICE_ID,
quantity: 1,
},
],
success_url: `${baseUrl}/videos?checkoutSuccess=true`,
cancel_url: `${baseUrl}/videos?checkoutCanceled=true`,
subscription_data: {
metadata: {
userId: session.user?.id,
},
},
});
const stripeBillingPortalSession =
await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${baseUrl}/videos`,
});
if (!checkoutSession) {
throw new Error("Could not create checkout session");
if (!stripeBillingPortalSession) {
throw new Error("Could not create billing portal session");
}
return { checkoutUrl: checkoutSession.url };
}),
createBillingPortalSession: protectedProcedure.mutation(async ({ ctx }) => {
const { stripe, session, prisma, req } = ctx;
const customerId = await getOrCreateStripeCustomerIdForUser({
prisma,
stripe,
userId: session.user?.id,
});
if (!customerId) {
throw new Error("Could not create customer");
}
const baseUrl =
env.NODE_ENV === "development"
? `http://${req.headers.host ?? "localhost:3000"}`
: `https://${req.headers.host ?? env.NEXTAUTH_URL}`;
const stripeBillingPortalSession =
await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${baseUrl}/videos`,
posthog.capture({
distinctId: session.user.id,
event: "visiting billing portal page",
properties: {
stripeSubscriptionStatus: session.user.stripeSubscriptionStatus,
},
});
void posthog.shutdownAsync();
if (!stripeBillingPortalSession) {
throw new Error("Could not create billing portal session");
return { billingPortalUrl: stripeBillingPortalSession.url };
}
return { billingPortalUrl: stripeBillingPortalSession.url };
}),
),
});

View file

@ -1,5 +1,6 @@
import type { PrismaClient } from "@prisma/client";
import type Stripe from "stripe";
import { type PostHog } from "posthog-node";
// retrieves a Stripe customer id for a given user if it exists or creates a new one
export const getOrCreateStripeCustomerIdForUser = async ({
@ -54,10 +55,12 @@ export const handleInvoicePaid = async ({
event,
stripe,
prisma,
posthog,
}: {
event: Stripe.Event;
stripe: Stripe;
prisma: PrismaClient;
posthog: PostHog;
}) => {
const invoice = event.data.object as Stripe.Invoice;
const subscriptionId = invoice.subscription;
@ -76,14 +79,27 @@ export const handleInvoicePaid = async ({
stripeSubscriptionStatus: subscription.status,
},
});
if (userId && subscription.status) {
posthog.capture({
distinctId: userId,
event: "stripe invoice.paid",
properties: {
stripeSubscriptionStatus: subscription.status,
},
});
void posthog.shutdownAsync();
}
};
export const handleSubscriptionCreatedOrUpdated = async ({
event,
prisma,
posthog,
}: {
event: Stripe.Event;
prisma: PrismaClient;
posthog: PostHog;
}) => {
const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata.userId;
@ -98,14 +114,24 @@ export const handleSubscriptionCreatedOrUpdated = async ({
stripeSubscriptionStatus: subscription.status,
},
});
if (userId && subscription.status) {
posthog.capture({
distinctId: userId,
event: "stripe subscription created or updated",
});
void posthog.shutdownAsync();
}
};
export const handleSubscriptionCanceled = async ({
event,
prisma,
posthog,
}: {
event: Stripe.Event;
prisma: PrismaClient;
posthog: PostHog;
}) => {
const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata.userId;
@ -120,4 +146,12 @@ export const handleSubscriptionCanceled = async ({
stripeSubscriptionStatus: null,
},
});
if (userId && subscription.status) {
posthog.capture({
distinctId: userId,
event: "stripe subscription cancelled",
});
void posthog.shutdownAsync();
}
};