mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-09-23 18:43:18 +08:00
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
import { PrismaClient, Prisma, SubscriptionPlan } from "@prisma/client";
|
|
import { buffer } from "micro";
|
|
import Cors from "micro-cors";
|
|
import { NextApiRequest, NextApiResponse } from "next";
|
|
import Stripe from "stripe";
|
|
import { getSubscriptionByEmail } from "../utils/subscription";
|
|
|
|
const stripe = new Stripe(process.env.STRIPE_API_KEY, {
|
|
// https://github.com/stripe/stripe-node#configuration
|
|
apiVersion: "2022-11-15",
|
|
});
|
|
|
|
const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
const prisma = new PrismaClient();
|
|
|
|
// Stripe requires the raw body to construct the event.
|
|
export const config = {
|
|
api: {
|
|
bodyParser: false,
|
|
},
|
|
};
|
|
|
|
const cors = Cors({
|
|
allowMethods: ["POST", "HEAD"],
|
|
});
|
|
|
|
const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
if (req.method === "POST") {
|
|
const buf = await buffer(req);
|
|
const sig = req.headers["stripe-signature"]!;
|
|
|
|
let event: Stripe.Event;
|
|
|
|
try {
|
|
event = stripe.webhooks.constructEvent(buf.toString(), sig, webhookSecret);
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
// On error, log and return the error message.
|
|
if (err! instanceof Error) console.log(err);
|
|
console.log(`❌ Error message: ${errorMessage}`);
|
|
res.status(400).send(`Webhook Error: ${errorMessage}`);
|
|
return;
|
|
}
|
|
|
|
// Cast event data to Stripe object.
|
|
if (event.type === "payment_intent.succeeded") {
|
|
const paymentIntent = event.data.object as Stripe.PaymentIntent;
|
|
const charge = await stripe.charges.retrieve(paymentIntent.latest_charge as string);
|
|
|
|
const customerId = paymentIntent.customer as string;
|
|
if (customerId) {
|
|
// Save the stripe customer id so that we can relate this customer to future payments.
|
|
await prisma.user.update({
|
|
where: {
|
|
email: paymentIntent.metadata.email,
|
|
},
|
|
data: {
|
|
stripeId: customerId,
|
|
},
|
|
});
|
|
}
|
|
|
|
const user = await prisma.user.findUniqueOrThrow({
|
|
where: { email: paymentIntent.metadata.email },
|
|
});
|
|
|
|
const payment: Prisma.PaymentUncheckedCreateInput = {
|
|
userId: user.id,
|
|
email: paymentIntent.metadata.email,
|
|
createdAt: new Date(paymentIntent.created * 1000),
|
|
paymentId: paymentIntent.id,
|
|
customerId: customerId || "",
|
|
description: paymentIntent.metadata.description,
|
|
amount: paymentIntent.amount,
|
|
currency: paymentIntent.currency,
|
|
receipt: charge.receipt_url as string,
|
|
};
|
|
await prisma.payment.create({ data: payment });
|
|
|
|
const currentSubscription = await getSubscriptionByEmail(paymentIntent.metadata.email);
|
|
// Create a new subscription if there is no active paid subscription.
|
|
if (currentSubscription.plan === "FREE" || currentSubscription.canceledAt || currentSubscription.expireAt < new Date().getTime()) {
|
|
const today = new Date(new Date().setHours(0, 0, 0, 0));
|
|
// Subtract 1 second from the year from now to make it 23:59:59
|
|
const yearFromNow = new Date(new Date(new Date().setHours(0, 0, 0, 0)).setFullYear(today.getFullYear() + 1) - 1000);
|
|
const subscription: Prisma.SubscriptionUncheckedCreateInput = {
|
|
userId: user.id,
|
|
email: paymentIntent.metadata.email,
|
|
createdAt: new Date(paymentIntent.created * 1000),
|
|
startAt: today,
|
|
expireAt: yearFromNow,
|
|
plan: paymentIntent.metadata.plan as SubscriptionPlan,
|
|
};
|
|
await prisma.subscription.create({ data: subscription });
|
|
} else {
|
|
// Extend the current subscription if there is an active paid subscription.
|
|
const expireAt = new Date(Math.max(currentSubscription.expireAt, new Date().getTime()));
|
|
expireAt.setFullYear(expireAt.getFullYear() + 1);
|
|
await prisma.subscription.update({
|
|
where: { id: currentSubscription.id },
|
|
data: {
|
|
expireAt,
|
|
},
|
|
});
|
|
}
|
|
} else if (event.type === "payment_intent.payment_failed") {
|
|
const paymentIntent = event.data.object as Stripe.PaymentIntent;
|
|
console.log(`❌ Payment failed: ${paymentIntent.last_payment_error?.message}`);
|
|
}
|
|
|
|
// Return a response to acknowledge receipt of the event.
|
|
res.json({ received: true });
|
|
} else {
|
|
res.setHeader("Allow", "POST");
|
|
res.status(405).end("Method Not Allowed");
|
|
}
|
|
};
|
|
|
|
export default cors(webhookHandler as any);
|