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
- Lee el cuerpo bruto exacto de la solicitud antes de analizar el JSON. Verifica
Wholly-Signaturecon el secreto correspondiente y comprobaciones de marca de tiempo/repetición. Los SDK oficiales proporcionan verificadores. - 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.
- 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. - 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.
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.