fix(sat, payments, admin): multiple production fixes
- sat sweep-stale-jobs: increase initial/custom sync threshold 8h→24h to prevent watchdog killing long historical syncs - sat-client: fix formatDateForSat same-day rejection by auto-adjusting fechaFin - sat-sync job: check fiel_contribuyente in addition to fiel_credentials for cron eligibility - database: extend pool idle cleanup from 5min to 12h to prevent pool closure during long syncs - webhook controller: auto-extend currentPeriodEnd on recurring MercadoPago payments - invoicing service: auto-send FacturAPI invoice by email after creation - admin-clientes: fix no-renovaciones detection to include expired trials and deleted subscriptions
This commit is contained in:
@@ -10,6 +10,21 @@ import { despachoPlanTieneDualidadDb } from '../services/plan-catalogo.service.j
|
||||
import { emailService } from '../services/email/email.service.js';
|
||||
import { getTenantOwnerEmail } from '../utils/memberships.js';
|
||||
|
||||
/**
|
||||
* Calcula la siguiente fecha de fin de período según la frecuencia.
|
||||
* Usa el mismo algoritmo que Mercado Pago: mismo día del mes siguiente,
|
||||
* ajustando al último día si el mes destino tiene menos días.
|
||||
*/
|
||||
function computeNextPeriodEnd(date: Date, frequency: string): Date {
|
||||
const d = new Date(date);
|
||||
if (frequency === 'monthly') {
|
||||
d.setMonth(d.getMonth() + 1);
|
||||
} else if (frequency === 'annual' || frequency === 'yearly') {
|
||||
d.setFullYear(d.getFullYear() + 1);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
export async function handleMercadoPagoWebhook(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { type, data } = req.body;
|
||||
@@ -187,9 +202,20 @@ async function handlePaymentNotification(paymentId: string) {
|
||||
// precio de renewal. Se detecta comparando el monto cobrado contra lo que
|
||||
// `getPlanPrice(phase='firstYear')` devolvería para este plan.
|
||||
const esPrimerPago = subscription.status === 'pending';
|
||||
const updateData: { status: string; currentPeriodEnd?: Date } = { status: 'authorized' };
|
||||
|
||||
// Extender currentPeriodEnd para renovaciones recurrentes.
|
||||
// El primer pago ya tiene currentPeriodEnd establecido al crear la suscripción;
|
||||
// solo extendemos en pagos subsecuentes para reflejar el nuevo período cobrado.
|
||||
if (!esPrimerPago && subscription.currentPeriodEnd) {
|
||||
const nextPeriodEnd = computeNextPeriodEnd(subscription.currentPeriodEnd, subscription.frequency);
|
||||
updateData.currentPeriodEnd = nextPeriodEnd;
|
||||
console.log(`[WEBHOOK] Subscription ${subscription.id} extended to ${nextPeriodEnd.toISOString()} (${subscription.frequency})`);
|
||||
}
|
||||
|
||||
await prisma.subscription.update({
|
||||
where: { id: subscription.id },
|
||||
data: { status: 'authorized' },
|
||||
data: updateData,
|
||||
});
|
||||
subscriptionService.invalidateSubscriptionCache(tenantId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user