add stripe / payment events
This commit is contained in:
parent
d778f61f33
commit
899a604d6d
3 changed files with 123 additions and 55 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue