PayPal
PayPal usa Orders v2 con captura: la librería crea una orden, tú rediriges al usuario a la página de aprobación de PayPal y, al volver, la librería captura el pago. La fuente de verdad es el webhook, aunque la vuelta del navegador también confirma la captura.
Configuración
import { PayPalGateway } from '@kaizen/payments-gateway'
PayPalGateway.register({
client_id: process.env.PAYPAL_CLIENT_ID!,
client_secret: process.env.PAYPAL_CLIENT_SECRET!,
webhook_id: process.env.PAYPAL_WEBHOOK_ID!,
environment: 'sandbox', // 'sandbox' | 'production'
public_base_url: process.env.PAYPAL_PUBLIC_BASE_URL!,
})| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
client_id | string | ✅ | Client ID de tu app REST de PayPal |
client_secret | string | ✅ | Client Secret de tu app REST de PayPal |
webhook_id | string | ✅ | ID del webhook, para verificar la firma de las notificaciones |
environment | 'sandbox' | 'production' | ✅ | Selecciona el endpoint de la API de PayPal |
public_base_url | string | ✅ | URL pública de tu API; PayPal devuelve al usuario a …/payments/paypal/return |
Variables de entorno
Los valores los aportas tú desde tu propio entorno y se los pasas a PayPalGateway.register():
PAYPAL_CLIENT_ID=AeA1QIZ...
PAYPAL_CLIENT_SECRET=EGnHDxD...
PAYPAL_WEBHOOK_ID=8SB63863JN...
PAYPAL_ENVIRONMENT=sandbox # 'sandbox' | 'production'
PAYPAL_PUBLIC_BASE_URL=https://tu-dominio.com| Variable | Campo | Requerido | Descripción |
|---|---|---|---|
PAYPAL_CLIENT_ID | client_id | ✅ | Client ID de la app REST |
PAYPAL_CLIENT_SECRET | client_secret | ✅ | Client Secret de la app REST |
PAYPAL_WEBHOOK_ID | webhook_id | ✅ | ID del webhook para verificar la firma |
PAYPAL_ENVIRONMENT | environment | ✅ | sandbox o production (elige el endpoint) |
PAYPAL_PUBLIC_BASE_URL | public_base_url | ✅ | URL pública de tu API para la vuelta del navegador |
Crear un cobro
curl -X POST http://localhost:3000/payments/charge \
-H 'content-type: application/json' \
-d '{
"gateway": "paypal",
"amount": 4900,
"currency": "EUR",
"reference": "pedido-001",
"redirect": {
"success_url": "http://localhost:3000/ok",
"cancel_url": "http://localhost:3000/ko"
}
}'redirect.success_urlyredirect.cancel_urlson obligatorios en PayPal. Sin ellos →400.amountva en la unidad mínima de la moneda (4900 = 49,00 €).- La respuesta trae un
redirect_urlcon la página de aprobación de PayPal: manda ahí al usuario.
La respuesta tiene el mismo formato que el resto de pasarelas:
{
"payment_id": "b9c1…",
"gateway": "paypal",
"status": "pending",
"redirect_url": "https://www.sandbox.paypal.com/checkoutnow?token=…"
}Cómo se paga
- Rediriges al usuario al
redirect_url(la página de aprobación de PayPal). - El usuario aprueba el pago en PayPal.
- PayPal lo devuelve a
…/payments/paypal/return(una ruta que monta la propia librería a partir de tupublic_base_url). Ahí la librería captura la orden y luego redirige el navegador a tusuccess_url(o acancel_urlsi no se capturó).
TIP
Tú no implementas la ruta de vuelta: la librería la expone y PayPal la usa como return_url. Tú solo aportas success_url / cancel_url en el cuerpo del cobro y public_base_url en la config.
Notificación y confirmación
Además de la captura en la vuelta del navegador, PayPal envía un webhook a POST /webhooks/paypal. La librería:
- Verifica la firma llamando a la API de PayPal con tu
webhook_id. Inválida →400. - Lee el
event_type:PAYMENT.CAPTURE.COMPLETED→ capturado → emitepayment.captured.PAYMENT.CAPTURE.DENIED→ fallido → emitepayment.failed.
El gateway_ref que casa la notificación con el Payment es el id de la orden de PayPal, generado por la librería al crear el cobro. La idempotencia por (gateway, event_id) evita que la vuelta del navegador y el webhook se procesen dos veces.