Configuración de PayPal
Operador: app REST, variables, webhook, cron, DDL, sandbox/live y baja de Mercado Pago.
En esta página
Esta página es para el operador de la plataforma (quien despliega el backend compartido). Los pagos están apagados por defecto: sin las variables de abajo, todas las rutas de dinero responden 503 payments_not_configured. Los desarrolladores y compradores no necesitan nada de esto; ver Guía de desarrollador y Guía de facturación.
1. Crear la app REST de PayPal#
- En el panel de desarrolladores de PayPal crea una app REST en sandbox y otra en live (credenciales distintas).
- En la app habilita Subscriptions y Payouts, y activa Log in with PayPal (ver más abajo).
- Copia
client idysecretal entorno del backend. Los secretos van solo en variables de entorno de la plataforma, nunca en el repositorio.
2. Variables del backend#
Obligatorias para activar los pagos (fuente: bloomx-backend/.env.example y src/lib/payments/config.ts):
PAYPAL_ENV=sandbox
PAYPAL_CLIENT_ID=<client-id-de-la-app>
PAYPAL_CLIENT_SECRET=<secreto-de-la-app>
PAYPAL_WEBHOOK_ID=<id-del-webhook>
PAYPAL_OIDC_REDIRECT_URI=https://<backend>/api/payments/paypal/link/callback
PAYMENTS_CRON_SECRET=<secreto-aleatorio-de-24-o-mas-caracteres>PAYPAL_ENV acepta sandbox o live (production y prod son sinónimos de live); cualquier otro valor es inválido y nunca cae a live (los pagos quedan apagados).
| Opcional | Defecto | Qué controla |
|---|---|---|
PAYMENTS_DEVELOPER_SHARE_BPS | 7000 | Parte del desarrollador en puntos base (70 %). |
PAYMENTS_MIN_PRICE_CENTS | 200 | Precio mínimo por extensión (centavos USD). |
PAYMENTS_MAX_PRICE_CENTS | 100000 | Precio máximo por extensión. |
PAYMENTS_HOLD_DAYS | 14 | Retención antes de poder pagar al desarrollador. |
PAYMENTS_PAYOUT_MIN_CENTS | 1000 | Pago mínimo. |
PAYMENTS_PAYOUT_MAX_CENTS | 500000 | Pago máximo por ciclo. |
PAYMENTS_ACCOUNT_SWITCH_HOURS | 24 | Espera al cambiar de cuenta PayPal. |
PAYMENTS_GRACE_DAYS | 3 | Gracia tras un cobro de suscripción fallido. |
PAYMENTS_RENEWAL_BUFFER_HOURS | 24 | Holgura tras la fecha de renovación antes de pausar (webhook tardío). |
PAYMENTS_TERMS_VERSION | 2026-10 | Versión vigente de los términos del desarrollador. |
PLATFORM_RESEND_API_KEY | - | Clave de Resend de la plataforma para correos de revisión, cuenta y cobros. |
PLATFORM_MAIL_FROM | - | Remitente de esos correos, p. ej. BloomX <billing@tu-dominio>. |
Valores fuera de rango se ignoran y se usa el defecto. Estas variables no se exponen a las extensiones, ni siquiera con el fallback global de entorno.
3. Webhook#
Crea un webhook en la app con la URL https://<backend>/api/payments/webhook, suscrito a estos eventos, y copia su id a PAYPAL_WEBHOOK_ID:
CHECKOUT.ORDER.APPROVEDPAYMENT.CAPTURE.COMPLETED·PAYMENT.CAPTURE.DENIED·PAYMENT.CAPTURE.REFUNDED·PAYMENT.CAPTURE.REVERSEDBILLING.SUBSCRIPTION.CREATED·ACTIVATED·UPDATED·SUSPENDED·CANCELLED·EXPIREDBILLING.SUBSCRIPTION.PAYMENT.FAILEDPAYMENT.SALE.COMPLETED·PAYMENT.SALE.REFUNDED·PAYMENT.SALE.REVERSED·PAYMENT.SALE.DENIED
Cada evento se verifica con la API oficial verify-webhook-signature; sin verificación válida se responde 401 y no se toca nada.
4. Log in with PayPal#
- Activa Log in with PayPal en la app con los scopes
openidyemail. - Registra la
redirect_uriexactahttps://<backend>/api/payments/paypal/link/callbacky la misma enPAYPAL_OIDC_REDIRECT_URI. El backend nunca construye laredirect_uridesde la petición.
5. Cron de pagos#
La ruta /api/cron/payments concilia órdenes y suscripciones pendientes y ejecuta el ciclo de pagos a desarrolladores. Se autentica con Authorization: Bearer <PAYMENTS_CRON_SECRET> (mínimo 24 caracteres). El backend trae un vercel.json con un cron diario (0 6 * * *); en Vercel define CRON_SECRET con el mismo valor que PAYMENTS_CRON_SECRET para que Vercel envíe el Bearer correcto. Consulta Despliegue.
curl -X POST https://<backend>/api/cron/payments \
-H "Authorization: Bearer <PAYMENTS_CRON_SECRET>"6. Base de datos (DDL)#
Las tablas nuevas (src/lib/payments/ddl.ts y src/lib/marketplace/ddl.ts) no se crean solas en producción. Revisa el SQL y aplícalo:
# Imprime el SQL sin ejecutarlo
node scripts/apply-ddl.mjs
# Aplica el DDL (idempotente: CREATE ... IF NOT EXISTS)
DATABASE_URL=<url-de-la-base> node scripts/apply-ddl.mjs --applyMientras falten las tablas, las rutas de facturación responden 503 payments_unavailable (y las de dinero fallan cerradas: una extensión de pago no se regala). El libro mayor tiene un trigger que impide UPDATE y DELETE.
7. Comprobar antes de ir a live#
- Sin credenciales reales: las pruebas usan un servidor PayPal falso (
node --test tests/payments-unit.test.mjs,npm run test:pg). - Con cuentas sandbox reales:
PAYPAL_ENV=sandbox, una cuenta vendedora y otra compradora de sandbox; prueba compra única, suscripción con prueba, fallo de pago, reembolso, vinculación y un ciclo de payout. - Paso a live: cambia a las credenciales de la app live, un webhook nuevo (otro id) y la
redirect_urilive; dejaPAYPAL_ENV=liveal final. - Conciliación:
npm run payments:reconcilecompara tu base con PayPal; úsalo tras cualquier incidente.
# Pruebas con el servidor PayPal falso (sin credenciales reales)
node --test tests/payments-unit.test.mjs
npm run test:pg
# Conciliacion manual contra PayPal
npm run payments:reconcileRespuestas 503#
| Respuesta | Causa |
|---|---|
503 payments_not_configured | Falta alguna credencial (PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID) o PAYPAL_ENV es inválido. |
503 payments_unavailable | Las tablas de pagos aún no existen: aplica el DDL. |
Disponibilidad y límites del software#
Verifica con PayPal
PayPal Payouts y Subscriptions pueden no estar disponibles para ciertas cuentas o países (por ejemplo Perú): confirma con PayPal que tu cuenta de plataforma los tiene habilitados antes de ir a live. El software no puede comprobarlo por ti.
Lo que el software no resuelve
Los términos legales del marketplace y las obligaciones tributarias (por ejemplo ante SUNAT) no los resuelve el software: requieren asesoría legal y contable propias. Un recibo de BloomX no es un comprobante fiscal.
Mercado Pago: desactivado#
- Se eliminaron
create-preferencey su webhook, y el paquetemercadopago. MP_ACCESS_TOKEN,MP_WEBHOOK_SECRETyPAYMENT_REDIRECT_ALLOWED_ORIGINSya no tienen efecto; puedes quitarlas del entorno.Transaction.providerIdqueda como campo legado legible. El flujo nuevo usaPaymentOrder,Subscription,EntitlementyLedgerEntry.- No se migra ningún dato de producción porque no había transacciones.