Spaces:
Build error
Build error
File size: 7,859 Bytes
3eebcd0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 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 });
}
}
|