feat: add webhook

This commit is contained in:
Mauricio Siu
2024-10-20 23:08:26 -06:00
parent ffe7b04bea
commit 1907e7e59c
13 changed files with 4483 additions and 827 deletions

View File

@@ -1,20 +1,21 @@
import { db } from "@/server/db";
import { admins, github } from "@/server/db/schema";
import { eq } from "drizzle-orm";
import { buffer } from "node:stream/consumers";
import { db } from "@/server/db";
import { admins, server } from "@/server/db/schema";
import { findAdminById } from "@dokploy/server";
import { asc, eq } from "drizzle-orm";
import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
apiVersion: "2024-09-30.acacia",
maxNetworkRetries: 3,
});
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET || "";
export const config = {
api: {
bodyParser: false, // Deshabilitar el body parser de Next.js
bodyParser: false,
},
};
@@ -22,108 +23,334 @@ export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const buf = await buffer(req); // Leer el raw body como un Buffer
if (!endpointSecret) {
return res.status(400).send("Webhook Error: Missing Stripe Secret Key");
}
const buf = await buffer(req);
const sig = req.headers["stripe-signature"] as string;
let event: Stripe.Event;
try {
// Verificar el evento usando el raw body (buf)
event = stripe.webhooks.constructEvent(buf, sig, endpointSecret);
const newSubscription = event.data.object as Stripe.Subscription;
console.log(event.type);
switch (event.type) {
case "customer.subscription.created":
await db
.update(admins)
.set({
stripeSubscriptionId: newSubscription.id,
stripeSubscriptionStatus: newSubscription.status,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
)
.returning();
break;
case "customer.subscription.deleted":
await db
.update(admins)
.set({
stripeSubscriptionStatus: "canceled",
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
case "customer.subscription.updated":
console.log(newSubscription.status);
// Suscripción actualizada (upgrade, downgrade, cambios)
await db
.update(admins)
.set({
stripeSubscriptionStatus: newSubscription.status,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
case "invoice.payment_succeeded":
console.log(newSubscription.customer);
await db
.update(admins)
.set({
stripeSubscriptionStatus: "active",
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
case "invoice.payment_failed":
// Pago fallido
await db
.update(admins)
.set({
stripeSubscriptionStatus: "payment_failed",
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).json({ received: true });
} catch (err) {
console.error("Webhook signature verification failed.", err.message);
return res.status(400).send("Webhook Error: ");
}
const webhooksAllowed = [
"customer.subscription.created",
"customer.subscription.deleted",
"customer.subscription.updated",
"invoice.payment_succeeded",
"invoice.payment_failed",
"customer.deleted",
"checkout.session.completed",
];
if (!webhooksAllowed.includes(event.type)) {
return res.status(400).send("Webhook Error: Invalid Event Type");
}
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
const adminId = session?.metadata?.adminId as string;
const subscription = await stripe.subscriptions.retrieve(
session.subscription as string,
);
await db
.update(admins)
.set({
stripeCustomerId: session.customer as string,
stripeSubscriptionId: session.subscription as string,
serversQuantity: subscription?.items?.data?.[0]?.quantity ?? 0,
})
.where(eq(admins.adminId, adminId))
.returning();
const admin = await findAdminById(adminId);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
const newServersQuantity = admin.serversQuantity;
const servers = await findServersByAdminIdSorted(admin.adminId);
if (servers.length > newServersQuantity) {
for (const [index, server] of servers.entries()) {
if (index < newServersQuantity) {
await activateServer(server.serverId);
} else {
await deactivateServer(server.serverId);
}
}
} else {
for (const server of servers) {
await activateServer(server.serverId);
}
}
break;
}
case "customer.subscription.created": {
const newSubscription = event.data.object as Stripe.Subscription;
await db
.update(admins)
.set({
stripeSubscriptionId: newSubscription.id,
serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0,
stripeCustomerId: newSubscription.customer as string,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
)
.returning();
const admin = await findAdminByStripeCustomerId(
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
const newServersQuantity = admin.serversQuantity;
const servers = await findServersByAdminIdSorted(admin.adminId);
// 4 > 3
if (servers.length > newServersQuantity) {
for (const [index, server] of servers.entries()) {
// 0 < 3 = true
// 1 < 3 = true
// 2 < 3 = true
// 3 < 3 = false
if (index < newServersQuantity) {
await activateServer(server.serverId);
} else {
await deactivateServer(server.serverId);
}
}
} else {
for (const server of servers) {
await activateServer(server.serverId);
}
}
break;
}
case "customer.subscription.deleted": {
const newSubscription = event.data.object as Stripe.Subscription;
await db
.update(admins)
.set({
stripeSubscriptionId: null,
serversQuantity: 0,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
const admin = await findAdminByStripeCustomerId(
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
await disableServers(admin.adminId);
break;
}
case "customer.subscription.updated": {
const newSubscription = event.data.object as Stripe.Subscription;
await db
.update(admins)
.set({
serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0,
})
.where(
eq(
admins.stripeCustomerId,
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
),
);
const admin = await findAdminByStripeCustomerId(
typeof newSubscription.customer === "string"
? newSubscription.customer
: "",
);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
const newServersQuantity = admin.serversQuantity;
const servers = await findServersByAdminIdSorted(admin.adminId);
if (servers.length > newServersQuantity) {
for (const [index, server] of servers.entries()) {
if (index < newServersQuantity) {
await activateServer(server.serverId);
} else {
await deactivateServer(server.serverId);
}
}
} else {
for (const server of servers) {
await activateServer(server.serverId);
}
}
break;
}
case "invoice.payment_succeeded": {
const newInvoice = event.data.object as Stripe.Invoice;
const suscription = await stripe.subscriptions.retrieve(
newInvoice.subscription as string,
);
await db
.update(admins)
.set({
serversQuantity: suscription?.items?.data?.[0]?.quantity ?? 0,
})
.where(eq(admins.stripeCustomerId, suscription.customer as string));
const admin = await findAdminByStripeCustomerId(
suscription.customer as string,
);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
const newServersQuantity = admin.serversQuantity;
const servers = await findServersByAdminIdSorted(admin.adminId);
if (servers.length > newServersQuantity) {
for (const [index, server] of servers.entries()) {
if (index < newServersQuantity) {
await activateServer(server.serverId);
} else {
await deactivateServer(server.serverId);
}
}
} else {
for (const server of servers) {
await activateServer(server.serverId);
}
}
break;
}
case "invoice.payment_failed": {
const newInvoice = event.data.object as Stripe.Invoice;
await db
.update(admins)
.set({
serversQuantity: 0,
})
.where(
eq(
admins.stripeCustomerId,
typeof newInvoice.customer === "string" ? newInvoice.customer : "",
),
);
const admin = await findAdminByStripeCustomerId(
typeof newInvoice.customer === "string" ? newInvoice.customer : "",
);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
await disableServers(admin.adminId);
break;
}
case "customer.deleted": {
const customer = event.data.object as Stripe.Customer;
const admin = await findAdminByStripeCustomerId(customer.id);
if (!admin) {
return res.status(400).send("Webhook Error: Admin not found");
}
await disableServers(admin.adminId);
await db
.update(admins)
.set({
stripeCustomerId: null,
stripeSubscriptionId: null,
serversQuantity: 0,
})
.where(eq(admins.stripeCustomerId, customer.id));
break;
}
default:
console.log(`Unhandled event type: ${event.type}`);
}
return res.status(200).json({ received: true });
}
const disableServers = async (adminId: string) => {
await db
.update(server)
.set({
serverStatus: "inactive",
})
.where(eq(server.adminId, adminId));
};
const findAdminByStripeCustomerId = async (stripeCustomerId: string) => {
const admin = db.query.admins.findFirst({
where: eq(admins.stripeCustomerId, stripeCustomerId),
});
return admin;
};
const activateServer = async (serverId: string) => {
await db
.update(server)
.set({ serverStatus: "active" })
.where(eq(server.serverId, serverId));
};
const deactivateServer = async (serverId: string) => {
await db
.update(server)
.set({ serverStatus: "inactive" })
.where(eq(server.serverId, serverId));
};
export const findServersByAdminIdSorted = async (adminId: string) => {
const servers = await db.query.server.findMany({
where: eq(server.adminId, adminId),
orderBy: asc(server.createdAt),
});
return servers;
};