Gmagl
fix: correcciones cr铆ticas y refactorizaci贸n de componentes
3eebcd0
Raw
History Blame Contribute Delete
7.86 kB
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { stripe, parseWebhookEvent } from "@/lib/stripe";
// Webhook de Stripe mejorado con validaci贸n de firma
export async function POST(request: NextRequest) {
try {
const signature = request.headers.get("stripe-signature");
if (!signature) return NextResponse.json({ success: false, error: "Firma faltante" }, { status: 400 });
const body = await request.text();
// Parsear y validar webhook
let event;
try {
event = parseWebhookEvent(body, signature);
} catch (err) {
console.error("Firma de webhook inv谩lida:", err);
return NextResponse.json({ success: false, error: "Firma de webhook inv谩lida" }, { status: 403 });
}
// Manejar eventos de Stripe
switch (event.type) {
case "checkout.session.completed":
return await handleCheckoutCompleted(event.data.object);
case "customer.subscription.updated":
return await handleSubscriptionUpdated(event.data.object);
case "customer.subscription.deleted":
return await handleSubscriptionDeleted(event.data.object);
case "invoice.payment_succeeded":
return await handleInvoicePaymentSucceeded(event.data.object);
case "invoice.payment_failed":
return await handleInvoicePaymentFailed(event.data.object);
default:
console.log(`Evento no manejado: ${event.type}`);
return NextResponse.json({ success: true, received: true });
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Error desconocido";
console.error("Webhook error:", message);
return NextResponse.json({ success: false, error: message }, { status: 500 });
}
}
// Sesi贸n de checkout completada: crear suscripci贸n
async function handleCheckoutCompleted(session: any) {
try {
const userId = session.metadata?.userId;
const influencerId = session.metadata?.influencerId;
const tier = session.metadata?.tier;
const stripeCustomerId = session.customer;
const stripeSubscriptionId = session.subscription;
if (!userId || !influencerId || !tier) {
console.error("Metadata incompleta en checkout");
return NextResponse.json({ success: false, error: "Metadata incompleta" }, { status: 400 });
}
// Obtener detalles de la suscripci贸n en Stripe
const stripeSub = await stripe.subscriptions.retrieve(stripeSubscriptionId);
const firstItem = stripeSub.items.data[0];
const price = firstItem?.price?.unit_amount ? firstItem.price.unit_amount / 100 : 0;
const nextRenewalDate = firstItem ? new Date(firstItem.current_period_end * 1000) : new Date();
// Crear suscripci贸n en BD
const subscription = await db.influencerSubscription.create({
data: {
influencerId,
userId,
tier,
price,
currency: "USD",
stripeSubscriptionId,
stripeCustomerId,
autoRenew: true,
nextRenewalDate,
},
});
// TODO: Crear Earning cuando exista un registro real en MonetizationPlatform
// El platformId debe ser un cuid v谩lido de la tabla MonetizationPlatform
console.log(`Suscripci贸n creada: ${subscription.id}`);
return NextResponse.json({ success: true, subscription });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Error";
console.error("Error handleCheckoutCompleted:", message);
return NextResponse.json({ success: false, error: message }, { status: 500 });
}
}
// Renovaci贸n autom谩tica: invoice pagado
async function handleInvoicePaymentSucceeded(invoice: any) {
try {
const stripeSubscriptionId = invoice.subscription;
if (!stripeSubscriptionId) return NextResponse.json({ success: true });
const subscription = await db.influencerSubscription.findFirst({
where: { stripeSubscriptionId },
});
if (!subscription) {
console.error(`Suscripci贸n no encontrada: ${stripeSubscriptionId}`);
return NextResponse.json({ success: true });
}
// Obtener detalles de la suscripci贸n actualizada
const stripeSub = await stripe.subscriptions.retrieve(stripeSubscriptionId);
const firstItem = stripeSub.items.data[0];
const nextRenewalDate = firstItem ? new Date(firstItem.current_period_end * 1000) : new Date();
const amountPaid = invoice.amount_paid / 100;
// Actualizar pr贸xima renovaci贸n
await db.influencerSubscription.update({
where: { id: subscription.id },
data: { nextRenewalDate },
});
// TODO: Crear Earning cuando exista un registro real en MonetizationPlatform
// El platformId debe ser un cuid v谩lido de la tabla MonetizationPlatform
console.log(`Renovaci贸n procesada: suscripci贸n ${subscription.id}, pr贸xima: ${nextRenewalDate}`);
return NextResponse.json({ success: true });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Error";
console.error("Error handleInvoicePaymentSucceeded:", message);
return NextResponse.json({ success: false, error: message }, { status: 500 });
}
}
// Pago de renovaci贸n fallido: marcar suscripci贸n para aviso
async function handleInvoicePaymentFailed(invoice: any) {
try {
const stripeSubscriptionId = invoice.subscription;
if (!stripeSubscriptionId) return NextResponse.json({ success: true });
const subscription = await db.influencerSubscription.findFirst({
where: { stripeSubscriptionId },
});
if (!subscription) return NextResponse.json({ success: true });
// Registrar intento fallido
console.error(`Pago fallido para suscripci贸n: ${subscription.id}`);
// TODO: notificar al usuario
return NextResponse.json({ success: true });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Error";
console.error("Error handleInvoicePaymentFailed:", message);
return NextResponse.json({ success: false, error: message }, { status: 500 });
}
}
// Suscripci贸n actualizada: cambios de plan, estado, etc.
async function handleSubscriptionUpdated(stripeSub: any) {
try {
const subscription = await db.influencerSubscription.findFirst({
where: { stripeSubscriptionId: stripeSub.id },
});
if (!subscription) return NextResponse.json({ success: true });
const nextRenewalDate = stripeSub.cancel_at ? new Date(stripeSub.cancel_at * 1000) : new Date(stripeSub.current_period_end * 1000);
await db.influencerSubscription.update({
where: { id: subscription.id },
data: { nextRenewalDate },
});
console.log(`Suscripci贸n actualizada: ${subscription.id}`);
return NextResponse.json({ success: true });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Error";
console.error("Error handleSubscriptionUpdated:", message);
return NextResponse.json({ success: false, error: message }, { status: 500 });
}
}
// Suscripci贸n eliminada/cancelada
async function handleSubscriptionDeleted(stripeSub: any) {
try {
const subscription = await db.influencerSubscription.findFirst({
where: { stripeSubscriptionId: stripeSub.id },
});
if (!subscription) return NextResponse.json({ success: true });
// Marcar como cancelada
await db.influencerSubscription.update({
where: { id: subscription.id },
data: { status: "cancelled", autoRenew: false },
});
console.log(`Suscripci贸n cancelada: ${subscription.id}`);
return NextResponse.json({ success: true });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Error";
console.error("Error handleSubscriptionDeleted:", message);
return NextResponse.json({ success: false, error: message }, { status: 500 });
}
}