fix(pagos): permitir pagar plan actual trial_expired y soportar planes >$10k via Preference

- Expone subscription trial_expired en /despachos/me/plan e incluye planPrice.
- Para Business Control/Enterprise (>$10k) genera pago anual único con MP Preference
  en lugar de preapproval recurrente; el webhook activa 1 año de suscripción.
- Muestra CTA de pago en UI cuando la suscripción está trial/trial_expired.
- Agrega campo mp_preference_id a subscriptions y mejora mensajes de error MP.
This commit is contained in:
Horux Dev
2026-06-16 22:37:11 +00:00
parent 63908f9e9d
commit 3f3253d41b
8 changed files with 290 additions and 22 deletions

View File

@@ -174,6 +174,57 @@ async function handlePaymentNotification(paymentId: string) {
return;
}
// Detecta pagos únicos de suscripción anual (planes >$10k). external_reference = `subscription:${tenantId}:${subscriptionId}`
if (payment.externalReference.startsWith('subscription:')) {
const parts = payment.externalReference.split(':');
const tenantId = parts[1];
const subscriptionId = parts[2];
if (!tenantId || !subscriptionId) {
console.warn('[WEBHOOK] external_reference de subscription malformado:', payment.externalReference);
return;
}
const paymentRecord = await subscriptionService.recordPayment({
tenantId,
subscriptionId,
mpPaymentId: paymentId,
amount: payment.transactionAmount || 0,
status: payment.status || 'unknown',
paymentMethod: payment.paymentMethodId || 'unknown',
});
if (payment.status === 'approved') {
const subscription = await prisma.subscription.findUnique({ where: { id: subscriptionId } });
if (subscription) {
const now = new Date();
const periodEnd = computeNextPeriodEnd(now, 'annual');
await prisma.$transaction([
prisma.subscription.update({
where: { id: subscription.id },
data: {
status: 'authorized',
currentPeriodStart: now,
currentPeriodEnd: periodEnd,
},
}),
prisma.tenant.update({
where: { id: tenantId },
data: { plan: subscription.plan },
}),
]);
subscriptionService.invalidateSubscriptionCache(tenantId);
console.log(`[WEBHOOK] Suscripción ${subscriptionId} activada por pago único anual hasta ${periodEnd.toISOString()}`);
}
// Auto-emisión de factura (fail-soft)
await invoicingService.emitInvoiceIfApplicable(paymentRecord.id);
}
if (typeof process.send === 'function') {
process.send({ type: 'invalidate-tenant-cache', tenantId });
}
return;
}
// Flujo normal: pago recurrente del preapproval
const tenantId = payment.externalReference;
const subscription = await prisma.subscription.findFirst({