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 }); } }