mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-20 21:35:23 +02:00
feat: add webhook
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user