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