Pagos · modules/billing
Adapter listo para producciónMercadoPago,
cableado como corresponde.
La mayoría de los starters de SaaS en Next.js son Stripe-first. Cuando necesitás cobrar en pesos o reales, pegar MercadoPago a Next.js a mano es donde viven los bugs clásicos — webhooks sin verificar, handlers no idempotentes y un sandbox que te pelea. UseDeploy trae el adapter para que heredes la plomería, no el dolor.
El dolor que ya nos comimos
Integrar MercadoPago tiene mala fama, y los foros de developers se la ganan: "de longe essa é a mais complexa e difícil de testar." El webhook asíncrono es donde muerde — la verdad sobre un pago llega después del redirect, en una request firmada aparte que tenés que verificar y reconciliar contra tu propia base de datos. El adapter de UseDeploy es una implementación real y funcional de ese flujo: preferencias de Checkout Pro para el redirect, un chequeo HMAC en tiempo constante en cada notificación entrante, y un fetch del estado del pago que mapea el resultado al mismo BillingEvent normalizado que emite cualquier otro proveedor. Vos cableás tu access token; el riel ya está construido.
Webhooks firmados, verificados antes de confiar en ellos
Cada notificación de MercadoPago se verifica contra el manifest x-signature / x-request-id con HMAC-SHA256 y una comparación en tiempo constante antes de traer el pago y mapearlo a un BillingEvent. Una llamada sin firma o manipulada se rechaza con un 400 — las clases de doble cobro y webhook falsificado quedan cerradas por defecto.
1 const manifest = `id:${dataId};request-id:${xRequestId};ts:${ts};`;
2 const expected = crypto
3 .createHmac('sha256', this.cfg.webhookSecret)
4 .update(manifest)
5 .digest('hex');
6
7 // constant-time comparison — never a naive === on the digest
8 return crypto.timingSafeEqual(hashBuf, expectedBuf);
Lo que el adapter hace hoy
Redirect de Checkout Pro
createCheckoutSession hace un POST a la API /checkout/preferences y devuelve la URL init_point. El modo de pago único está activo; currency_id cubre ARS, BRL, MXN, USD y EUR.
Webhooks asíncronos firmados
parseWebhook verifica la firma HMAC, trae /v1/payments/:id, y emite invoice.paid o invoice.payment_failed al event bus de billing.
Un solo contrato tipado
MercadoPago implementa el mismo port IPaymentProvider que Stripe y Polar — cambiar de proveedor es un cambio de un solo archivo en el composition root.
Event bus durable
Los eventos de billing fluyen por el outbox durable, así un webhook que llega dos veces o fuera de orden se reconcilia contra tu DB en vez de aplicarse dos veces.
Catálogo de planes desde Prisma
listPlans lee la tabla BillingPlan, así tu pricing vive en tu base de datos — no hardcodeado por proveedor.
Docs de producto trilingües
El sitio Fumadocs trae páginas reales EN / ES / PT (convención dot-parser, EN canónico), no solo ruteo de locale sobre copy en inglés.
Respuestas directas
¿Soporta suscripciones recurrentes (preapproval)?+
¿Cómo pruebo sin tocar producción?+
¿Puedo correr MercadoPago y Stripe al mismo tiempo?+
Referencia de configuración
Variables de entorno
PAYMENT_PROVIDERrequeridaSet to mercado-pago
MERCADO_PAGO_ACCESS_TOKENrequeridaMERCADO_PAGO_WEBHOOK_SECRETrequeridaHMAC secret for x-signature verification
Dónde vive
apps/server/src/modules/billing/infrastructure/providers/mercado-pago-payment-provider.tsapps/server/src/modules/billing/infrastructure/providers/index.ts
Dejá de pegar MercadoPago a Next.js a mano.
Arrancá sobre una base donde el checkout, los webhooks firmados y el contrato de billing ya están cableados — y localizados en EN, ES y PT.