Con la Direct API, el formulario de tarjeta forma parte de su sitio web y su servidor envía los datos de la tarjeta a Payniko. El navegador del pagador solo se redirige a Payniko para 3-D Secure y para los BIN hosted. Utilice la Direct API cuando el formulario de tarjeta deba estar en su propia página.
Pasos de integración
Compruebe el BIN
Cuando el pagador haya introducido seis dígitos, llame a GET /api/payments/bin/{bin6}. payment_mode indica si debe recopilar los datos completos de la tarjeta en su formulario o enviar al pagador a la página de pago.
Cree el pago
Llame a POST /api/payments/direct con los datos de la tarjeta, o solo con bin6 para un BIN hosted.
Complete la autenticación 3-D Secure
Si el estado es requires_action, redirija el navegador a next_action.url. A continuación, el navegador regresa a su success_url o fail_url con parámetros firmados.
Confirme el resultado
Espere el webhook payment.succeeded o payment.failed, o llame a GET /api/payments/{payment_id}. Entregue el pedido solo con el estado succeeded.
Flujo de pago
Your server Payniko Payer's browser
1 GET /api/payments/bin/{bin6} ───► payment_mode: direct | hosted
2 POST /api/payments/direct ─────► 200 requires_action + next_action.url
(or processing / succeeded / failed)
3 redirect to next_action.url ──────────────────────────────────► 3-D Secure page
───────────────────────────► success_url / fail_url
4 webhook ◄─────────────────────── payment.succeeded / payment.failed
GET /api/payments/{payment_id} ──► current status
Requisitos previos
La Direct API se habilita por cuenta. Sin este acceso, las solicitudes se rechazan con 4030055502.
Antes de crear el pago, la comprobación de BIN indica si la tarjeta puede pagarse en su formulario (direct) o si el pago debe completarse en la página de pago de Payniko (hosted). La comprobación no genera ningún cargo.
Parámetros
Campo
Tipo
Obligatorio
Descripción
Ejemplo
bin6
string · path
Obligatorio
Seis primeros dígitos de la tarjeta.
540061
amount
string · query
Obligatorio
Importe con dos decimales, por ejemplo 100.00. Se comprueba con respecto a los límites de su cuenta.
100.00
currency
string · query
Opcional
USD, EUR, AED o TRY. De forma predeterminada, la moneda principal del sistema. Utilice la misma moneda al crear el pago.
USD
Se rechaza cualquier otro parámetro de consulta. La cadena de consulta no forma parte de la firma.
./payniko.sh GET '/api/payments/bin/540061?amount=100.00¤cy=USD'
Devuelve { "data": ... } con los siguientes campos:
Campo
Tipo
Descripción
bin6
string
Los seis dígitos enviados.
bank
string | null
Banco emisor, si se conoce.
network
string | null
Red de tarjetas, si se conoce.
visamastercardamextroy
card_type
string | null
Tipo de tarjeta, si se conoce.
creditdebitprepaid
card_program
string | null
Programa de la tarjeta. Actualmente siempre null.
country
string | null
País emisor (ISO 3166-1 alfa-2), si se conoce.
domestic
boolean
true para una tarjeta nacional; false en caso contrario o si se desconoce.
payment_mode
string | null
direct: recopile los datos completos de la tarjeta. hosted: envíe al pagador a la página de pago. Cualquier otro valor, incluido null: no cree ningún pago.
directhosted
Tratamiento de la respuesta
direct: recopile los datos completos de la tarjeta y cree el pago con card.
hosted: cree el pago solo con bin6. El pagador introduce los datos de la tarjeta en la página de pago de Payniko.
Cualquier otro payment_mode o un error de tarjeta 409 (0055230, 0055231, 0055233): no cree ningún pago y solicite al pagador otra tarjeta con un mensaje neutro, ya que no se trata de un rechazo del banco. 4220055235 indica un problema con el importe. Ante 429 o 5xx, repita la misma comprobación más tarde.
Ejemplo
import {api} from './payniko.mjs';
let mode = null;
try {
const lookup = await api('GET', '/api/payments/bin/411111?amount=100.00¤cy=USD');
mode = lookup.data.payment_mode;
} catch (error) {
// No answer, 429 or 5xx: temporary. Try the same lookup again later.
if (error.status === undefined || error.status === 429 || error.status >= 500) throw error;
// Other 4xx: error.problem.code says why, for example 0055233.
}
if (mode !== 'direct' && mode !== 'hosted') {
// Also null: do not create a payment. Ask for another card with a neutral message.
}
Siempre devuelve 200 con el pago actual, también cuando se reenvía la misma solicitud. El rechazo de una tarjeta no es un error HTTP; la respuesta es 200 con status: failed.
Cuerpo de la solicitud
Campo
Tipo
Obligatorio
Descripción
Ejemplo
merchant_order_id
string
Obligatorio
Su referencia de pedido: de 1 a 64 caracteres de A-Z a-z 0-9 . _ -. Un pago por referencia y entorno; la unicidad no distingue entre mayúsculas y minúsculas.
ORDER-2026-002
amount
string
Obligatorio
Importe con dos decimales, por ejemplo "100.00", dentro de los límites de su cuenta.
"100.00"
currency
string
Opcional
De forma predeterminada, la moneda principal del sistema. Utilice la misma moneda que en la comprobación de BIN.
USDEURAEDTRY
USD
webhook_url
string · uri
Obligatorio
URL HTTPS pública en el puerto 443 que recibe los webhooks de este pago, 500 caracteres como máximo. No se aceptan direcciones locales, tampoco en sandbox; el nombre de host debe resolverse a una IP pública en el momento de la solicitud, de lo contrario 4229000008.
https://merchant.example/webhooks/payments
success_url
string · uri
Obligatorio
Destino de redirección del navegador cuando el pago se ha completado correctamente. HTTPS, 2048 caracteres como máximo, sin información de usuario ni fragmento. En sandbox también se admiten http://localhost, 127.0.0.1 y [::1].
https://merchant.example/payment/success
fail_url
string · uri
Obligatorio
Destino de redirección del navegador en todos los demás casos. Se aplican las mismas reglas que a success_url.
https://merchant.example/payment/failure
customer
object
Obligatorio
Datos de contacto del pagador; se usan para el resultado del pago, el recibo y los pasos de verificación del banco. Solo se aceptan email y phone; cualquier otro campo dentro de customer se rechaza con 4221000001.
{ … }
customer.email
string
Obligatorio
La dirección de correo electrónico real del pagador: RFC 5322, 254 caracteres como máximo, con dominio con punto. Se guarda en minúsculas.
El número de teléfono real del pagador en formato internacional (+ código de país, E.164), por ejemplo +37061234567. Regla específica para Turquía: +90 seguido del número nacional de 10 dígitos (5xx móvil, 2xx, 3xx, 4xx fijo, 8xx especial). Un número enviado sin código de país se trata como número turco (+90).
+37061234567
card
object
Condicional
Los datos de la tarjeta introducidos por el pagador (consulte la tabla siguiente). Envíe card o bin6, pero no ambos.
{ … }
bin6
string
Condicional
Solo para un BIN hosted: los seis primeros dígitos. El pagador introduce los datos de la tarjeta en la página de pago. Una solicitud para un BIN direct que solo incluye bin6 devuelve 4090055230.
540061
card.number
string
Obligatorio
13–19 dígitos sin espacios, con un dígito de control válido (15 dígitos para American Express).
4355090000044242
card.holder_name
string
Obligatorio
Nombre que figura en la tarjeta, de 2 a 120 caracteres.
JONAS KAZLAUSKAS
card.expiry_month
string
Obligatorio
Dos dígitos, de 01 a 12.
12
card.expiry_year
string
Obligatorio
Dos dígitos, por ejemplo 30. La tarjeta no debe estar caducada y el año puede estar como máximo 30 años en el futuro.
30
card.cvv
string
Obligatorio
Exactamente 3 dígitos, o 4 dígitos para American Express. Nunca lo almacene ni lo registre.
123
Todos los valores de la tarjeta son cadenas. Los campos desconocidos dentro de card se rechazan.
Con un BIN hosted, la página de pago no vuelve a pedir al pagador el correo electrónico ni el número de teléfono. Por privacidad, customer no se devuelve en el objeto de pago ni en los webhooks.
Campo
Motivo
Mensaje en errors
customer.email
Formato no válido o más de 254 caracteres.
customer.email must be a valid e-mail address (max 254 characters).
customer.email
Dirección de relleno, de ejemplo o desechable.
customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted.
customer.phone
No es un número válido en formato internacional.
customer.phone must be a valid phone number in international format (e.g. +37061234567).
customer.phone
Patrón de dígitos repetidos o consecutivos.
customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted.
customer.phone
Número turco que no tiene la forma +90 seguido de 10 dígitos que empiezan por 2, 3, 4, 5 u 8.
customer.phone for Türkiye must be +90 followed by a 10-digit number starting with 2, 3, 4, 5 or 8 (e.g. +905321234567).
HTTP 422 · 1000001
{
"type": "https://payniko.com/problems/1000001",
"title": "Validation failed",
"status": 422,
"detail": "The submitted fields are not valid.",
"code": "1000001",
"correlation_id": "4c7a1e9d-2b3f-4d85-9a6e-0f1b2c3d4e5f",
"errors": {
"customer.email": [
"customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted."
],
"customer.phone": [
"customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted."
]
}
}
Si una solicitud se rechaza por customer, muestre al pagador el motivo con claridad (por ejemplo: introduzca su propio correo electrónico o número de teléfono) y permítale corregir el campo antes de crear el pago de nuevo. La Direct API devuelve los mensajes de errors traducidos; la tabla muestra el texto en inglés.
Las páginas de enlace de pago de Payniko aplican las mismas reglas al correo electrónico y al número de teléfono que el pagador introduce allí, y le muestran el motivo de un rechazo.
Redirija el navegador a next_action.url (3-D Secure o, para un BIN hosted, la página de pago).
processing
Espere el webhook y no cree un pago nuevo.
succeeded
Entregue el pedido.
failed
Muestre failure.message y solicite al pagador otra tarjeta.
La llamada puede tardar varios segundos mientras Payniko se comunica con el banco. Use un tiempo de espera de al menos 60 segundos y, tras agotarse, reenvíe la solicitud con la misma Idempotency-Key y el mismo cuerpo. Mientras la primera solicitud siga en curso (hasta 3 minutos), el reintento devuelve 4090055503 con metadata.retry_class: retry_same_request: espere unos segundos y reenvíe la misma solicitud.
Para un BIN hosted, se ignora cualquier card enviado y el pagador continúa en la página de pago.
Si ya existe un pago para el merchant_order_id, o se envía el mismo Idempotency-Key con un cuerpo distinto, la solicitud se rechaza con 4090055503.
Los datos de tarjeta no válidos se rechazan con 4090055234. metadata.developer_detail identifica el campo: card_number_invalid, card_expiry_invalid o card_cvv_invalid. Solicite al pagador que vuelva a introducir los datos de la tarjeta.
Los datos de contacto ausentes o no válidos se rechazan con 4221000001; errors indica customer, customer.email o customer.phone.
3. 3-D Secure y el navegador
Cuando el estado es requires_action, el navegador del pagador debe abrir next_action.url, donde el banco realiza la autenticación 3-D Secure o el pagador continúa en la página de pago.
Redirija la página completa a next_action.url o ábrala en una pestaña nueva; ambos métodos se admiten siempre.
La página no vuelve a solicitar los datos de la tarjeta; solo recopila los datos del navegador que requiere el banco.
Utilice siempre la next_action.url más reciente antes de su next_action.expires_at (el final del paso de 3-D Secure). No vuelva a abrir nunca una URL anterior.
A continuación, el navegador se redirige a success_url (solo con succeeded) o a fail_url con parámetros firmados (consulte URL de retorno). Después, consulte el pago.
Carga en marcos
La página de 3-D Secure no puede mostrarse dentro de un marco; redirija la página completa o abra una pestaña nueva. Solo la página de pago de un BIN hosted puede cargarse en un marco, únicamente con claves de producción y solo en el origen exacto de la success_url del pago. La página de pago de la Hosted API no puede cargarse nunca en un marco.
La página de pago de un BIN hosted integra la propia pantalla segura de pago y de 3-D Secure del proveedor de pagos y puede mostrarse como iframe dentro de su propio formulario de pago.
Si carga en un marco la página de pago de un BIN hosted, los mensajes que esta envía a su ventana sirven únicamente como señal para comprobar el pago. Acéptelos solo desde el origen de next_action.url y desde su marco, ignore su contenido y haga que su servidor consulte el pago.
Navegador
// Only if you show the payment page of a hosted BIN in a frame on the exact origin of success_url.
// nextActionUrl: next_action.url from your server.
const pageOrigin = new URL(nextActionUrl).origin;
window.addEventListener('message', (event) => {
if (event.origin !== pageOrigin || event.source !== frame.contentWindow) return;
// A wake-up call only: ignore event.data. Ask your server to read the payment.
fetch('/checkout/ORDER-2026-002/refresh', {method: 'POST', credentials: 'same-origin'});
});
Objeto de pago
Las llamadas de creación y consulta devuelven este objeto dentro de data, y todos los webhooks de pago lo contienen. Su estructura es idéntica en la Hosted API y en la Direct API.
Campo
Tipo
Descripción
payment_id
string · LUVEI-ABCD-123456
ID de Payniko del pago. Guárdelo junto con su pedido.
merchant_order_id
string
Su referencia de pedido, exactamente como la envió.
flow
string
hosted para la Hosted API, direct para la Direct API.
hosteddirect
status
string
Estado actual del pago; consulte la tabla de estados más abajo.
Moneda del importe; la moneda principal del sistema si no se especificó ninguna.
environment
string
Entorno de la clave que creó el pago.
sandboxlive
next_action
object | null
La siguiente acción que debe realizar el pagador. Solo está presente mientras status es requires_action.
next_action.type
string
Siempre redirect.
next_action.url
string · uri
Abra esta URL en el navegador del pagador exactamente como se devuelve. No la construya ni la modifique nunca.
next_action.expires_at
string · date-time
Plazo para utilizar la URL. Con la Direct API, corresponde al final del paso de 3-D Secure y puede ser anterior al expires_at del propio pago; con la Hosted API, es el plazo del pago. Una vez vencido, el pago pasa a su siguiente estado y next_action pasa a ser null.
card
object | null
Datos enmascarados de la tarjeta, una vez conocidos.
Motivo del fallo. Solo está presente cuando status es failed.
failure.code
string
Código de motivo para utilizar en su lógica; consulte la lista más abajo.
failure.message
string
Mensaje breve apto para mostrarse al pagador como texto sin formato.
created_at
string · date-time
Fecha y hora de creación del pago.
expires_at
string · date-time
Plazo para completar el pago.
completed_at
string · date-time | null
Fecha y hora en que el pago alcanzó un estado final.
next_action puede ser null brevemente incluso en requires_action; en ese caso, vuelva a consultar el pago. Nunca se devuelven el número completo de la tarjeta, el CVV, la fecha de vencimiento ni el nombre del titular. card.network y card.bank pueden ser null cuando no se conocen.
Los datos de customer enviados al crear el pago no forman parte de este objeto y no aparecen en los webhooks.
El navegador debe abrir next_action.url para 3-D Secure o para la página de pago de un BIN hosted.
Redirija al pagador a next_action.url.
processing
La respuesta del banco está pendiente. Este estado también puede devolverse directamente en la respuesta de creación.
Espere el webhook y no cree un nuevo pago. Si el banco nunca responde, el pago puede permanecer en processing hasta la conciliación y después termina como failed o expired.
succeeded
Los fondos se han cobrado.
Entregue el pedido.
failed
El pago no se completó. failure contiene el motivo.
Muestre failure.message. Para reintentar, cree un pago nuevo con un merchant_order_id nuevo.
expired
El pagador no completó el pago antes de expires_at.
Si el pagador aún desea pagar, cree un pago nuevo.
cancelled
El pago se canceló.
No entregue el pedido.
Escenario
Estado
Evento
Aprobado tras 3-D Secure
requires_action → succeeded
payment.succeeded
Rechazado
failed
payment.failed
Respuesta del banco pendiente
processing
Ninguno hasta el resultado final
Sin respuesta del banco; se agotó el tiempo de espera del intento
processing → failed / expired
payment.failed
El pagador no inició el pago y venció el plazo (si el pagador abandona el pago a mitad del proceso, este puede finalizar como failed)
expired
payment.failed
Trate cualquier estado que no figure aquí como processing y vuelva a consultar el pago.
Códigos de fallo
Un pago en estado failed contiene failure.code y failure.message. Utilice el código en su lógica y muestre el mensaje al pagador como texto sin formato.
Código
Significado
payment_declined
El banco rechazó el pago.
do_not_honor
El banco rechazó el pago sin indicar el motivo.
insufficient_funds
La tarjeta no tiene fondos suficientes.
card_limit_reached
Se ha alcanzado un límite de la tarjeta.
card_details_invalid
Los datos de la tarjeta no son válidos.
card_expiry_invalid
La fecha de vencimiento no es válida o la tarjeta ha vencido.
card_security_code_invalid
El CVV no es válido.
card_not_active
La tarjeta está bloqueada o no está activa.
card_brand_not_supported
Esta red de tarjetas no está admitida.
three_ds_failed
La autenticación 3-D Secure falló o no se completó.
payment_not_permitted
La tarjeta no está habilitada para este tipo de pago.
amount_not_allowed
El importe no está permitido para esta tarjeta.
currency_not_supported
La moneda no está admitida para esta tarjeta.
payment_details_rejected
El banco rechazó los datos del pago.
duplicate_payment
El banco identificó el pago como duplicado.
payment_timed_out
El pago no se completó dentro del plazo.
bank_unavailable
No fue posible comunicarse con el banco.
bank_requested_retry
El banco solicitó que se reintente el pago más tarde.
service_unavailable
No fue posible procesar el pago en este momento.
Es posible que se añadan códigos nuevos; trate cualquier código desconocido como payment_declined. failure.message puede contener el texto del propio banco; de lo contrario, está en inglés en los webhooks y se ajusta a Accept-Language en GET.
URL de retorno
Cuando el pagador regresa, el navegador se redirige a success_url solo si el pago está en succeeded, y a fail_url en todos los demás casos. A la URL se añaden parámetros firmados. Verifíquelos para mostrar la página correcta y entregue el pedido solo después de que el webhook o un GET confirmen succeeded.
Direct API: si el resultado aún no es definitivo cuando el pagador vuelve de 3-D Secure, el navegador no se redirige a fail_url; el pagador permanece en una página de resultado neutral de Payniko. Confíe en el webhook o en un GET para conocer el resultado.
Campo
Descripción
payment_id
El ID del pago .
merchant_order_id
Su referencia de pedido. No está firmada: utilícela solo para localizar el pedido y realice la correspondencia mediante payment_id.
status
Estado del pago en el momento de la redirección, con los mismos valores que en GET. Siempre succeeded en success_url. En fail_url, normalmente failed, expired o cancelled; con la Hosted API puede ser requires_action o processing si el pagador se fue antes de que el pago alcanzara un estado final, por ejemplo mediante "Volver a la tienda".
failure_code
Solo está presente cuando status es failed; idéntico a failure.code.
ts
Hora Unix en la que se creó el enlace de retorno. El pagador puede abrir el enlace más tarde, por lo que un ts antiguo no indica por sí solo una falsificación.
key_version
Identifica la clave que firmó el enlace: 1 para la primera clave creada en ese entorno, 2 para la siguiente, y así sucesivamente. El valor nunca cambia para un mismo pago.
sig
HMAC-SHA256 en hexadecimal en minúsculas, de 64 caracteres.
Cadena que se firma
ts + "." + payment_id + "." + status + "." + failure_code // failure_code empty: keep the last dot
Seleccione la clave secreta correspondiente a la clave indicada en key_version. Es la clave que creó el pago, aunque las claves se hayan sustituido desde entonces.
Construya la cadena con los valores decodificados de la URL. Si failure_code no está presente, mantenga el punto al final de la cadena.
Calcule el HMAC-SHA256 y compárelo con sig en tiempo constante. Confirme que payment_id corresponde a uno de sus pagos.
Consulte el pago con GET /api/payments/{payment_id} (o espere el webhook) antes de entregar el pedido.
Sus propios parámetros de consulta se conservan. Si la URL ya contiene un parámetro con alguno de los nombres anteriores, se sustituye.
Si el enlace no pudo firmarse debido a un error temporal, la URL no contiene ninguno de estos parámetros. Considere el resultado como desconocido y consulte el pago.
// Node.js 20+: check the signed parameters on success_url / fail_url.
import {createHmac, timingSafeEqual} from 'node:crypto';
// query: URLSearchParams of the return URL.
// secretFor(keyVersion): the API secret of that key, or undefined if you do not know it.
export function verifyReturn(query, secretFor) {
const sig = query.get('sig') ?? '';
const secret = secretFor(query.get('key_version'));
if (!secret || !/^[0-9a-f]{64}$/.test(sig)) return false;
const signed = [query.get('ts') ?? '', query.get('payment_id') ?? '',
query.get('status') ?? '', query.get('failure_code') ?? ''].join('.');
const expected = createHmac('sha256', secret).update(signed, 'utf8').digest();
return timingSafeEqual(expected, Buffer.from(sig, 'hex'));
}
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
<?php
// PHP 8.1+: check the signed parameters on success_url / fail_url.
// $secretFor(string $keyVersion): the API secret of that key, or null if you do not know it.
function payniko_verify_return(array $query, callable $secretFor): bool
{
$sig = (string) ($query['sig'] ?? '');
$secret = $secretFor((string) ($query['key_version'] ?? ''));
if (!$secret || !preg_match('/^[0-9a-f]{64}$/', $sig)) return false;
$signed = implode('.', [(string) ($query['ts'] ?? ''), (string) ($query['payment_id'] ?? ''),
(string) ($query['status'] ?? ''), (string) ($query['failure_code'] ?? '')]);
return hash_equals(hash_hmac('sha256', $signed, $secret), $sig);
}
// $ok = payniko_verify_return($_GET, fn (string $v) => $secretsByKeyVersion[$v] ?? null);
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
# Python 3.9+: check the signed parameters on success_url / fail_url.
import hashlib, hmac, re
def verify_return(query, secret_for) -> bool:
# query: dict-like (for example Flask request.args); secret_for(key_version) -> secret or None
sig = query.get("sig", "")
secret = secret_for(query.get("key_version"))
if not secret or not re.fullmatch(r"[0-9a-f]{64}", sig):
return False
signed = ".".join([query.get("ts", ""), query.get("payment_id", ""),
query.get("status", ""), query.get("failure_code", "")])
expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
# Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
URL de retorno · Ejemplo
https://merchant.example/payment/success?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=succeeded&ts=1793001234&key_version=1&sig=200e8a5d4a7a03ead362a2c076408f9d8764f8f636f58bd4c17d78a50d59eb8a
string to sign: 1793001234.LUVEI-PAYN-654321.succeeded.
https://merchant.example/payment/failure?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=failed&failure_code=insufficient_funds&ts=1793001234&key_version=1&sig=1cd0a104ec4ec4f5b2933d48d14c0eb33d949dd7f27936a067ab8f68b413b70d
string to sign: 1793001234.LUVEI-PAYN-654321.failed.insufficient_funds
secret used in this example (fake): example_secret_not_real
GET/api/payments/{payment_id}
Consultar un pago
Devuelve { "data": Payment } con el estado actual. La consulta de un pago nunca lo modifica, por lo que puede llamar a este endpoint tantas veces como sea necesario: tras el retorno, cuando un webhook parezca incoherente o tras un tiempo de espera agotado.
Parámetros
Campo
Tipo
Obligatorio
Descripción
payment_id
string · path
Obligatorio
El payment_id devuelto al crear el pago. Cualquier clave del mismo entorno puede consultarlo.
./payniko.sh GET '/api/payments/3c2d7a4e-5b1f-4e8a-9d06-7f2b1c9e4a35'
Verifique cada webhook: el HMAC-SHA256 de X-Timestamp + "." + raw body, calculado con la clave secreta asociada a la clave de API que creó el pago, debe ser igual a X-Signature, y X-Timestamp debe estar dentro de un margen de 300 segundos. Almacene cada event_id una sola vez y responda con 2xx sin demora. Para ver ejemplos de código y el comportamiento de los reintentos, consulte Webhooks.
Errores
Código
HTTP
Significado
Acción recomendada
1000001
422
La solicitud no es válida: un campo desconocido, un formato no válido, una cadena de consulta en un POST, un parámetro de consulta no admitido o un cuerpo que no es un objeto JSON. errors enumera los campos afectados.
Corrija los campos indicados.
1000009
422
Falta el encabezado Idempotency-Key o no es válido.
Envíe con cada POST una clave de 8 a 128 caracteres compuesta por A-Z a-z 0-9 _ -.
0055502
401 / 403
Acceso denegado. 401: falta X-Api-Key o X-Merchant-Code, un encabezado está duplicado o tiene un formato no válido, la clave es desconocida o está revocada, el código de comercio no coincide, o se envió X-Environment y no coincide con la clave. 403: la cuenta o el acceso a la Direct API no están activos.
Verifique los encabezados y la clave de API. No reintente la solicitud de forma automática.
0055503
409
Conflicto en la Direct API: ya existe un pago para este merchant_order_id, el Idempotency-Key se utilizó con un cuerpo distinto o para un pago Hosted, o la solicitud original sigue en curso.
Consulte el pago con GET. Reenvíe únicamente una solicitud idéntica.
0055210
422
El BIN no tiene seis dígitos.
Envíe los seis primeros dígitos del número de tarjeta.
0055234
409
Los datos de la tarjeta no son válidos: número de tarjeta, fecha de caducidad o CVV. metadata.developer_detail indica el campo: card_number_invalid, card_expiry_invalid o card_cvv_invalid.
Solicite al pagador que vuelva a introducir los datos de la tarjeta.
0055233
409
El BIN es desconocido.
Solicite al pagador otra tarjeta. No se trata de un rechazo del banco.
0055230
409
Esta tarjeta no puede utilizarse para este pago.
Solicite al pagador otra tarjeta. No se trata de un rechazo del banco.
0055231
409
Esta tarjeta no se acepta para este pago.
Solicite al pagador otra tarjeta.
0055221
409
Se ha alcanzado un límite de esta tarjeta.
Utilice otra tarjeta o reintente más tarde.
0055235
422
El importe no se acepta (Direct API y comprobación de BIN). amount_limit contiene min y max cuando están disponibles.
Ajuste el importe.
0055001
409
Su cuenta no está lista para este pago. Con una clave sandbox, esto también significa que la simulación de sandbox no está disponible temporalmente (developer_detail: sandbox_unavailable), equivalente a 9000013.
Póngase en contacto con soporte.
1000002
404
No encontrado: la ruta es desconocida o este producto de API no está habilitado en la plataforma. Una API deshabilitada para su cuenta devuelve 403 en su lugar (9000040 en la Hosted API, 0055502 en la Direct API).
Verifique la ruta; contacte con soporte si la API debería estar disponible.
1000011
503
El servicio no está disponible temporalmente.
Reintente la misma solicitud. Consulte los recursos existentes antes de crear otros nuevos.
Los errores de la Direct API incluyen además metadata.retry_class (no_retry, retry_same_request, retry_after, read_order). Una ventana del navegador cerrada o un tiempo de espera agotado en su lado no significa que el pago haya fallado; consulte el pago. Para ver la lista completa, consulte Códigos de error.
Pruebas con tarjetas de sandbox
Con una clave de sandbox, la comprobación de BIN y el pago utilizan datos de prueba fijos: 435509 y 400000 son BIN direct, y 540061 es un BIN hosted. Utilice cualquier fecha de vencimiento futura y cualquier CVV de 3 dígitos. Para más información, consulte Sandbox y tarjetas de prueba.
Número de tarjeta
Resultado
4000000000000010
Aprobado sin 3-D Secure. Con la Direct API, la respuesta de creación ya devuelve succeeded.
4000000000000002 · 4355090000044242
Paso de 3-D Secure de prueba, seguido de succeeded.
4000000000000051 · 4355090000040000
Rechazada antes de 3-D Secure (do_not_honor). Hosted API: el pago puede permanecer unos 5 minutos en requires_action y enviar payment.updated antes de terminar como failed. Direct API: la respuesta de creación ya devuelve failed.
4000000000000093 · 4355090000005151
Paso de 3-D Secure de prueba, seguido de failed con insufficient_funds.
4000000000000044
Sin respuesta del banco ni 3-D Secure; el pago permanece en processing.
4355090000089999
Hosted API: processing sin paso 3-D Secure. Direct API: paso 3-D Secure de prueba y después processing.
Con la Direct API, una tarjeta de prueba rechazada devuelve failed directamente en la respuesta de creación; un requires_action intermedio solo se produce en la página de pago de un BIN hosted. Base sus decisiones en el webhook o en un GET.
Ejemplo completo
Un servidor Node.js mínimo que abarca la comprobación de BIN, la creación del pago, la redirección a 3-D Secure, la URL de retorno y el webhook. Utiliza la función auxiliar de solicitud y las funciones de verificación de Webhooks y URL de retorno.
server.mjs
// server.mjs: Direct API from start to end (Node.js 20+, no dependencies).
import http from 'node:http';
import {randomUUID} from 'node:crypto';
import {api} from './payniko.mjs'; // request helper (Authentication)
import {verifyWebhook} from './verify-webhook.mjs'; // webhook check (Webhooks)
import {verifyReturn} from './verify-return.mjs'; // return URL check (Return URL)
const site = 'https://merchant.example';
const orders = new Map(); // use your database
const seenEvents = new Set(); // use your database
const readBody = async (req) => { const parts = []; for await (const p of req) parts.push(p); return Buffer.concat(parts); };
const reply = (res, data) => {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data.status === 'requires_action' && data.next_action
? {redirect: data.next_action.url} : {status: data.status, failure: data.failure}));
};
http.createServer(async (req, res) => {
const url = new URL(req.url, site);
// 1. BIN check, 2. create the payment. Your own HTTPS form posts the card here.
if (req.method === 'POST' && url.pathname === '/pay') {
const form = JSON.parse((await readBody(req)).toString('utf8'));
const bin6 = form.number.slice(0, 6);
let mode = null;
try {
mode = (await api('GET', `/api/payments/bin/${bin6}?amount=100.00¤cy=USD`)).data.payment_mode;
} catch (error) {
if (error.status === undefined || error.status >= 500 || error.status === 429) throw error;
}
if (mode !== 'direct' && mode !== 'hosted') { res.writeHead(422).end('Please use another card.'); return; }
const orderId = 'ORDER-' + Date.now();
const key = randomUUID();
orders.set(orderId, {key, status: 'new'});
const body = {
merchant_order_id: orderId, amount: '100.00', currency: 'USD',
customer: {email: form.email, phone: form.phone},
webhook_url: `${site}/webhooks/payniko`,
success_url: `${site}/payment/success`, fail_url: `${site}/payment/failure`,
};
if (mode === 'direct') {
body.card = {number: form.number, holder_name: form.holder_name,
expiry_month: form.expiry_month, expiry_year: form.expiry_year, cvv: form.cvv};
} else {
body.bin6 = bin6; // payer enters the card on the payment page
}
const {data} = await api('POST', '/api/payments/direct', body, key);
Object.assign(orders.get(orderId), {paymentId: data.payment_id, status: data.status});
reply(res, data); // 3. browser goes to next_action.url
return;
}
// Return URL: show a page, but trust only the webhook or a GET.
if (req.method === 'GET' && (url.pathname === '/payment/success' || url.pathname === '/payment/failure')) {
const signed = verifyReturn(url.searchParams, () => process.env.API_SECRET);
let status = 'unknown';
if (signed) status = (await api('GET', `/api/payments/${url.searchParams.get('payment_id')}`)).data.status;
res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
res.end(status === 'succeeded' ? 'Thank you, payment received.' : 'Payment not completed.');
return;
}
// Webhook: verify, store event_id once, answer 2xx fast.
if (req.method === 'POST' && url.pathname === '/webhooks/payniko') {
const raw = await readBody(req);
if (!verifyWebhook(raw, req.headers, process.env.API_SECRET)) { res.writeHead(401).end(); return; }
const event = JSON.parse(raw.toString('utf8'));
if (!seenEvents.has(event.event_id)) { // use a unique index in your database
seenEvents.add(event.event_id);
const order = orders.get(event.data.merchant_order_id);
if (order && event.event_type.startsWith('payment.')) order.status = event.data.status;
}
res.writeHead(204).end();
return;
}
res.writeHead(404).end();
}).listen(8080);