← Todos los tutoriales

TUTORIAL 4 / 9

Crea un checkout. Verifica el pago.

Crea facturas desde tu backend y procesa notificaciones de pago verificadas.

Qué tendrás

Un flujo de pago del lado del servidor con protección para reintentos y duplicados.

1. Prepara los ID y el acceso

Empieza con una tienda habilitada y métodos de pago probados. En Ajustes → Acceso API, crea una credencial de lectura/escritura limitada al proyecto necesario. Mantén el token en tu backend, nunca en código del navegador ni en un repositorio público.

Copia el ID de API del proyecto y ID de API de la tienda del recuadro de la tienda Datos básicos → ID de API . Son UUID, no el identificador legible del proyecto ni tu número de pedido. Usa tu propio nombre de host de API.

En Tienda → IPN, crea un secreto de firma antes de proporcionar un ipn_url. Tu receptor HTTPS debe ser accesible desde el VPS del comercio.

2. Crea una factura

Sustituye los marcadores y envía esta solicitud desde tu backend. Los importes son cadenas decimales, no cálculos de coma flotante.

curl --fail-with-body --request POST \
  'https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices' \
  --header 'Authorization: Bearer YOUR_MERCHANT_API_TOKEN' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: order-1042-attempt-1' \
  --data '{
  "amount": "10.00",
  "currency": "EUR",
  "order_id": "order-1042",
  "description": "Example order",
  "ipn_url": "https://shop.example.com/payments/wholly-ipn",
  "metadata": {
    "cart_id": "cart-1042"
  }
}'

Guarda data.invoice_id con tu pedido y después redirige al cliente a links.checkout. Usa una clave de idempotencia única para un nuevo intento de pago. Si se agota el tiempo de espera, reintenta con la misma credencial, clave y bytes exactos del cuerpo.

Omitir payment_methods usa los métodos aceptados por la tienda. Puedes restringirlos por factura con slugs de cadenas y símbolos; esto nunca habilita un activo no aceptado. Todos los campos de solicitud y ejemplos de respuesta →

3. Elige IPN, webhooks o ambos

IPN sigue el ciclo de vida de la factura. Configura la URL IPN predeterminada de la tienda o sustitúyela con ipn_url para una factura. Los webhooks suscriben un endpoint a eventos seleccionados, por ejemplo invoice.settled.

Usan el mismo formato de firma, pero secretos distintos: IPN usa el secreto IPN de la tienda; cada endpoint de webhook tiene su propio secreto. Ninguno usa el token bearer de API para firmar.

Si ambos entregan a tu aplicación, espera notificaciones solapadas. No acredites un pedido dos veces.

4. Verifica y guarda la notificación

  1. Lee el cuerpo bruto exacto de la solicitud antes de analizar el JSON. Verifica Wholly-Signature con el secreto correspondiente y comprobaciones de marca de tiempo/repetición. Los SDK oficiales proporcionan verificadores.
  2. Valida la identidad de proyecto, tienda, factura y evento del cuerpo firmado. Los encabezados de entrega sin firmar no son una fuente de autenticación.
  3. Almacena el evento de forma persistente con un event_idúnico y después devuelve HTTP 2xx de inmediato. Procesa los pedidos en un proceso en segundo plano.
  4. Obtén la factura actual desde tu host de API configurado, no desde un host arbitrario proporcionado en una solicitud. Compara el proyecto, la tienda, el importe, la moneda y la referencia de pedido que guardaste.

PHP · Python · JavaScript / TypeScript · Especificación de firma y ejemplos de receptores

5. Atiende el pedido una vez, al liquidarse

Para un receptor basado en eventos , gestiona event_type = invoice.settled, y después verifica el valor actual de status = settled y tu política de excepciones. Atiende el pedido una sola vez dentro de una transacción de base de datos/restricción de pedido único.

El tipo de evento y el estado son distintos

Una cadena de finalización rápida puede enviar tanto payment.received y invoice.settled con status = settled. En otra cadena, payment.received puede seguir indicando processing. Ninguno de los dos flujos es un error.

Elimina eventos duplicados por event_id, no solo por secuencia: distintos tipos de eventos pueden compartir una secuencia. En cambio, los manejadores de SDK basados en estado agrupan las revisiones de factura y comprueban el estado independientemente del tipo de evento. No mezcles esa agrupación con un filtro por tipo de evento. Ambos enfoques siguen necesitando protección contra duplicados a nivel de pedido.

Inspecciona requires_review y las resoluciones manuales antes de atender el pedido. amount_status = paid por sí solo no demuestra confirmación. Los campos de activo pagado del nivel superior resumen la liquidación; payment_info contiene los detalles de pagos recibidos y los datos de cotización. Todos los estados, eventos y reglas de excepciones →

6. Prueba reintentos y recuperación

Prueba un pago pequeño, una entrega duplicada, una factura caducada y un receptor temporalmente no disponible. Reproducir un evento no debe generar una segunda acreditación del pedido. Gestiona los eventos fuera de orden sin sobrescribir un estado más reciente.

Inspecciona Tienda → IPN / Webhooks → Historial → Detalles, o las secciones de entrega en Detalles de la factura. Reenviar reutiliza el evento registrado, no una nueva liquidación.

El crédito de procesamiento bajo pausa IPN/webhooks mientras los pagos continúan. Concilia los pedidos pendientes mediante la API y gestiona las entregas retenidas tras la recuperación. Nunca atiendas un pedido basándote en una redirección del navegador ni en una captura del cliente.