add subscription webhook

This commit is contained in:
daniel31x13 2024-10-06 01:59:31 -04:00
parent d99972a335
commit d042c82cb0
6 changed files with 244 additions and 1 deletions

View File

@ -17,5 +17,5 @@ export default async function getUsers() {
}, },
}); });
return { response: users, status: 200 }; return { response: users.sort((a: any, b: any) => a.id - b.id), status: 200 };
} }

View File

@ -0,0 +1,91 @@
import Stripe from "stripe";
import { prisma } from "./db";
type Data = {
id: string;
active: boolean;
quantity: number;
periodStart: number;
periodEnd: number;
};
export default async function handleSubscription({
id,
active,
quantity,
periodStart,
periodEnd,
}: Data) {
const subscription = await prisma.subscription.findUnique({
where: {
stripeSubscriptionId: id,
},
});
if (subscription) {
await prisma.subscription.update({
where: {
stripeSubscriptionId: id,
},
data: {
active,
quantity,
currentPeriodStart: new Date(periodStart * 1000),
currentPeriodEnd: new Date(periodEnd * 1000),
},
});
return;
} else {
if (!process.env.STRIPE_SECRET_KEY)
throw new Error("Missing Stripe secret key");
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2022-11-15",
});
const subscription = await stripe.subscriptions.retrieve(id);
const customerId = subscription.customer;
const customer = await stripe.customers.retrieve(customerId.toString());
const email = (customer as Stripe.Customer).email;
if (!email) throw new Error("Email not found");
const user = await prisma.user.findUnique({
where: {
email,
},
});
if (!user) throw new Error("User not found");
const userId = user.id;
await prisma.subscription
.upsert({
where: {
userId,
},
create: {
active,
stripeSubscriptionId: id,
quantity,
currentPeriodStart: new Date(periodStart * 1000),
currentPeriodEnd: new Date(periodEnd * 1000),
user: {
connect: {
id: userId,
},
},
},
update: {
active,
stripeSubscriptionId: id,
quantity,
currentPeriodStart: new Date(periodStart * 1000),
currentPeriodEnd: new Date(periodEnd * 1000),
},
})
.catch((err) => console.log(err));
}
}

View File

@ -18,12 +18,18 @@ export default async function paymentCheckout(
const NEXT_PUBLIC_TRIAL_PERIOD_DAYS = const NEXT_PUBLIC_TRIAL_PERIOD_DAYS =
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS; process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS;
const session = await stripe.checkout.sessions.create({ const session = await stripe.checkout.sessions.create({
customer: isExistingCustomer ? isExistingCustomer : undefined, customer: isExistingCustomer ? isExistingCustomer : undefined,
line_items: [ line_items: [
{ {
price: priceId, price: priceId,
quantity: 1, quantity: 1,
adjustable_quantity: {
enabled: true,
minimum: 1,
maximum: Number(process.env.STRIPE_MAX_QUANTITY || 100),
},
}, },
], ],
mode: "subscription", mode: "subscription",

View File

@ -0,0 +1,143 @@
import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
import handleSubscription from "@/lib/api/handleSubscription";
export const config = {
api: {
bodyParser: false,
},
};
const buffer = (req: NextApiRequest) => {
return new Promise<Buffer>((resolve, reject) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
req.on("end", () => {
resolve(Buffer.concat(chunks));
});
req.on("error", reject);
});
};
export default async function webhook(
req: NextApiRequest,
res: NextApiResponse
) {
if (process.env.NEXT_PUBLIC_DEMO === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
// see if stripe is already initialized
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET) {
return res.status(400).json({
response: "This action is disabled because Stripe is not initialized.",
});
}
let event = req.body;
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2022-11-15",
});
const signature = req.headers["stripe-signature"] as any;
try {
const body = await buffer(req);
event = stripe.webhooks.constructEvent(body, signature, endpointSecret);
} catch (err) {
console.error(err);
return res.status(400).send("Webhook signature verification failed.");
}
// switch (event.type) {
// case "invoice.payment_succeeded":
// console.log(
// `Payment for ${event.data.object.customer_email} was successful!`
// );
// await handleCreateSubscription(event.data.object);
// break;
// case "customer.subscription.updated":
// console.log(
// `Subscription for ${event.data.object.customer_email} was updated.`
// );
// await handleUpdateSubscription(event.data.object);
// break;
// case "customer.subscription.deleted":
// console.log(
// `Subscription for ${event.data.object.customer_email} was deleted.`
// );
// await handleDeleteSubscription(event.data.object);
// break;
// default:
// // Unexpected event type
// console.log(`Unhandled event type ${event.type}.`);
// }
// Handle the event based on its type
const eventType = event.type;
const data = event.data.object;
try {
switch (eventType) {
case "customer.subscription.created":
await handleSubscription({
id: data.id,
active: data.status === "active" || data.status === "trialing",
quantity: data?.quantity ?? 1,
periodStart: data.current_period_start,
periodEnd: data.current_period_end,
});
break;
case "customer.subscription.updated":
await handleSubscription({
id: data.id,
active: data.status === "active",
quantity: data?.quantity ?? 1,
periodStart: data.current_period_start,
periodEnd: data.current_period_end,
});
break;
case "customer.subscription.deleted":
await handleSubscription({
id: data.id,
active: false,
quantity: data?.lines?.data[0]?.quantity ?? 1,
periodStart: data.current_period_start,
periodEnd: data.current_period_end,
});
break;
case "customer.subscription.cancelled":
await handleSubscription({
id: data.id,
active: !(data.current_period_end * 1000 < Date.now()),
quantity: data?.lines?.data[0]?.quantity ?? 1,
periodStart: data.current_period_start,
periodEnd: data.current_period_end,
});
break;
default:
console.log(`Unhandled event type ${eventType}`);
}
} catch (error) {
console.error("Error handling webhook event:", error);
return res.status(500).send("Server Error");
}
return res.status(200).json({
response: "Done!",
});
}

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Subscription" ADD COLUMN "quantity" INTEGER NOT NULL DEFAULT 1;

View File

@ -170,6 +170,7 @@ model Subscription {
stripeSubscriptionId String @unique stripeSubscriptionId String @unique
currentPeriodStart DateTime currentPeriodStart DateTime
currentPeriodEnd DateTime currentPeriodEnd DateTime
quantity Int @default(1)
user User @relation(fields: [userId], references: [id]) user User @relation(fields: [userId], references: [id])
userId Int @unique userId Int @unique
createdAt DateTime @default(now()) createdAt DateTime @default(now())