Payefy /Documentación Técnica
API v1 Checklist ↓
Elige una guía Pagos con tarjeta Consulta de Transacciones
Botón de PagoAPI E-commerce (checkout propio)

Integra cobros con tarjeta en tu sitio o app

Guía técnica para conectar la pasarela de Payefy. Cubre ambientes, credenciales, la firma TKA, el cuerpo de la petición, las respuestas reales y el catálogo completo de códigos.

1

Da de alta tu comercio

Recibes 3 credenciales: afiliación, nombre y token de activación.

2

Firma e integra

Calculas el TKA por petición y llamas al endpoint de cobro.

3

Prueba y pasa a producción

Validas en Sandbox y activamos tu comercio en Producción.

Dos formas de integrar

Botón de Pago Sin PCI

Rediriges al cliente a una página de cobro hospedada por Payefy. Tú no capturas la tarjeta. Ideal para salir rápido y sin alcance PCI. → Ver detalle

API E-commerce Checkout propio

Tu servidor envía la tarjeta al endpoint de cobro y recibes la respuesta síncrona. Control total del checkout; requiere manejar datos de tarjeta de forma segura. → Ver detalle

Ambientes

AmbienteBase URLUso
Sandboxhttps://sandbox.payefy.mePruebas de formato, headers y firma.
Producciónhttps://botpv.payefy.meCobros reales (comercio activado por Payefy).

Todos los endpoints cuelgan de la base del ambiente. No hay API keys ni sesión: cada petición transaccional se autentica con la firma TKA.

Credenciales y headers

Tus 3 credenciales

commerce-idNúmero de afiliación
commerce-nameNombre del comercio
token de activaciónTKR — base de la firma

Las entrega Payefy al dar de alta tu comercio (una tríada por ambiente).

Headers de la petición

Content-Typeapplication/json
commerce-idAfiliación
commerce-nameNombre del comercio
X-TimeEpoch en milisegundos
X-AuthTKA (ver Firma)

Firma (TKA)

El header X-Auth lleva el TKA, calculado por petición a partir de tu token (TKR), el X-Time y la afiliación:

algoritmo
cad1    = OR(TKR, X-Time)           // byte a byte, hasta el operando más corto
cad2    = XOR(cad1, afiliación)      // byte a byte; el resto de cad1 queda intacto
cad_b64 = base64(cad2)               // bytes crudos (latin1) → base64 ASCII
TKA     = bcrypt(cad_b64, cost = 11)  // va en el header X-Auth
Importante — causa el 99% de las firmas rechazadas: X-Time es epoch en milisegundos y debe ser exactamente el mismo valor que usaste para calcular el TKA. Calcúlalo una sola vez por petición. Segundos ≠ milisegundos.
Sobre bcrypt y los 72 bytes: se hace base64 antes del bcrypt, así que la entrada es ASCII pura (sin bytes NUL) y no hay truncado sorpresa. bcrypt corta a 72 bytes, pero cad_b64 solo supera 72 si tu TKR mide > 54 bytes (no ocurre con un token normal), y aun así ambos lados cortan igual. No pre-hashees ni recortes a mano. El hash $2y$11$… de PHP se valida sin problema (bcrypt acepta $2a/$2b/$2y).

Implementación de referencia

tka.js · npm i bcryptjs
const bcrypt = require('bcryptjs');

function orBytes(tkr, ts){
  const A = Buffer.from(tkr,'latin1'), B = Buffer.from(ts,'latin1'), out = Buffer.from(A);
  for (let i=0; i<Math.min(A.length,B.length); i++) out[i] = A[i] | B[i];
  return out;
}
function xorBytes(cad1, afil){
  const B = Buffer.from(afil,'latin1'), out = Buffer.from(cad1);
  for (let i=0; i<Math.min(cad1.length,B.length); i++) out[i] = cad1[i] ^ B[i];
  return out;
}
function makeTKA(tkr, xTime, afiliacion){
  const cadB64 = xorBytes(orBytes(tkr,xTime), afiliacion).toString('base64');
  return bcrypt.hashSync(cadB64, 11);           // X-Auth
}
const xTime = String(Date.now());              // X-Time (ms) — el MISMO valor
PayefyTka.php
function tka_or_bytes($tkr, $ts){
  $out = $tkr; $n = min(strlen($tkr), strlen($ts));
  for ($i=0; $i<$n; $i++) $out[$i] = chr(ord($tkr[$i]) | ord($ts[$i]));
  return $out;
}
function tka_xor_bytes($cad1, $afil){
  $out = $cad1; $n = min(strlen($cad1), strlen($afil));
  for ($i=0; $i<$n; $i++) $out[$i] = chr(ord($cad1[$i]) ^ ord($afil[$i]));
  return $out;
}
function make_tka($tkr, $ts, $afil){
  $cad_b64 = base64_encode(tka_xor_bytes(tka_or_bytes($tkr,$ts), $afil));
  return password_hash($cad_b64, PASSWORD_BCRYPT, ['cost'=>11]);  // X-Auth ($2y$11$…)
}
$xTime = (string)(int)round(microtime(true)*1000);        // X-Time (ms)
tka.py · pip install bcrypt
import base64, time, bcrypt

def _or(tkr, ts):
    a = bytearray(tkr.encode('latin1')); b = ts.encode('latin1')
    for i in range(min(len(a), len(b))): a[i] |= b[i]
    return bytes(a)

def _xor(cad1, afil):
    a = bytearray(cad1); b = afil.encode('latin1')
    for i in range(min(len(a), len(b))): a[i] ^= b[i]
    return bytes(a)

def make_tka(tkr, x_time, afiliacion):
    cad_b64 = base64.b64encode(_xor(_or(tkr, x_time), afiliacion))   # bytes ASCII
    return bcrypt.hashpw(cad_b64, bcrypt.gensalt(11)).decode()      # X-Auth

x_time = str(int(time.time() * 1000))                             # X-Time (ms)
Tka.cs · NuGet: BCrypt.Net-Next
using System; using System.Text;

static byte[] OrBytes(string tkr, string ts){
    var a = Encoding.Latin1.GetBytes(tkr); var b = Encoding.Latin1.GetBytes(ts);
    for (int i=0; i<Math.Min(a.Length,b.Length); i++) a[i] = (byte)(a[i] | b[i]);
    return a;
}
static byte[] XorBytes(byte[] cad1, string afil){
    var b = Encoding.Latin1.GetBytes(afil);
    for (int i=0; i<Math.Min(cad1.Length,b.Length); i++) cad1[i] = (byte)(cad1[i] ^ b[i]);
    return cad1;
}
static string MakeTKA(string tkr, string xTime, string afil){
    string cadB64 = Convert.ToBase64String(XorBytes(OrBytes(tkr,xTime), afil));
    return BCrypt.Net.BCrypt.HashPassword(cadB64, workFactor: 11);   // X-Auth
}
string xTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();  // X-Time (ms)

Vectores de prueba

Antes de tocar la API, verifica que tu cad_b64 (base64 antes del bcrypt) coincide con estos:

TKRX-Timeafiliacióncad_b64 esperado
1Mo6p53KiDeUVa#17000000000008262003CU1JBEAFAHt5dHV1dmEj
MiTokenDeRegistro_ABC12317171717171711234567890123TE1GS05BSE9MR0RFSnN0cm9fQUJDMTIz

API E-commerce

POST {base}/streampay/v1/ecommerce/charge — cobro de tarjeta no presente. Resuelve de forma síncrona: la respuesta trae el resultado (resp_code).

Cuerpo de la petición

CampoTipoOblig.Descripción / valor 2D
isoTypeStringSiempre "AGRS" al integrar vía Payefy (agregador).
amountDecimal (string)Monto, p.ej. "150.00".
currencyStringSolo "MXN" (ISO 4217 alfabético). USD no soportado en este rail.
referenceString (12)Numérico de 12 dígitos. Tolera ceros a la izquierda. Único por intento (idempotencia).
cardInformationObjetoVer abajo.
holder_nameStringSin acentos.
card_numberNumérico (16)16 dígitos.
last4 / binNumérico (4 / 6)Últimos 4 y primeros 6 dígitos.
expiration_yearNumérico (2)YY (2 dígitos).
expiration_monthNumérico (2)MM (2 dígitos, con cero a la izquierda: enero = "01").
cvvNumérico (3-4)Código de verificación.
entry_modeString"manual" para tarjeta no presente.
authenticationString"unknown" para 2D (sin 3DS). Otros: signature/pin/mail_tel/qps.
draftCaptureString (1)"1" = autorizar y capturar (liquidación inmediata). "0" = solo autorizar.
tpvBooleanfalse en e-commerce (true solo en TPV física).
periodicBooleanNofalse salvo cargo recurrente.
email, billingAddress, clientip3DS/AVSDatos del tarjetahabiente para AVS/antifraude. No disparan 3DS por sí solos (ver 3D Secure).
ejemplo · cobro 2D
{
  "isoType": "AGRS", "amount": "150.00", "currency": "MXN",
  "reference": "423456789485",
  "cardInformation": {
    "holder_name": "JUAN PEREZ",
    "card_number": "4110760000000008", "last4": "0008", "bin": "411076",
    "expiration_year": "27", "expiration_month": "09", "cvv": "123"
  },
  "entry_mode": "manual", "authentication": "unknown",
  "draftCapture": "1", "tpv": false, "periodic": false
}

3D Secure

El cobro resuelve síncrono y devuelve resp_code. Para 2D puro: envía authentication:"unknown" y no incluyas los campos 3DS_XID / 3DS_UCAF / 3DS_ECI; tu comercio se aprovisiona con un perfil e-commerce sin 3DS. Enviar email/billingAddress/clientip es AVS/antifraude y no gatilla un challenge. Si tu comercio usa 3DS interno, entonces el cobro puede devolver una urlRedirect para el challenge.

Respuestas

Aprobada · HTTP 200

{
  "request_status": true,
  "resp_code": "00",
  "description": "APROBADA",
  "authorization": "012773",
  "id": "160765279732",
  "trace_id": "140679",
  "binInformation": {
    "bin": "557910", "bank": "SANTANDER",
    "type": "DEBITO", "brand": "MASTERCARD"
  }
}

Declinada · HTTP 200

{
  "request_status": true,
  "resp_code": "05",          // ≠ "00"
  "description": "DECLINADA",
  "id": "…", "trace_id": "…"
  // sin campo "authorization"
}

Error técnico (firma/formato inválidos) → HTTP ≥ 400 con request_status:false, no un resp_code de negocio.

Regla de mapeo

Éxito: resp_code === "00" (y aprobatorios 08/11/76–81/M1).  ·  Declinada: HTTP 200 con cualquier otro resp_code.  ·  Error técnico: HTTP ≥ 400 o request_status:false.
CampoQué es
idIdentificador de la transacción (Numérico 12). Se usa para cancelar/reversar. Puede diferir de reference.
authorizationCódigo de autorización bancario (Numérico 6). Solo en aprobadas.
trace_idIdentificador del mensaje.
binInformationDatos del BIN: banco, tipo (crédito/débito), marca. No se devuelve ARN.
resp_code / descriptionResultado de la transacción (ver Códigos).

Códigos de respuesta

Catálogo completo (ISO 8583 → descripción del gateway). Aprobatorios: 00 y 08, 11, 76–81, M1, R0–R2; el resto es declinación o error.

CódigoDescripción ISODescripción Gateway

Botón de Pago

GET {base}/api/pay con parámetros en el query string. Rediriges al cliente a esa URL; Payefy hospeda la página de cobro y regresa a tu url_respuesta.

parámetros
{base}/api/pay
  ?commerce_id={afiliación}
  &commerce_name={nombre urlencoded}
  &concepto={texto urlencoded}
  &monto={12 dígitos en centavos}     // $50.00 → 000000005000
  &url_respuesta={url urlencoded}
  &checksum={SHA-256}
  &x_time={epoch ms}
  &x_auth={TKA}
checksum = SHA256("commerce_id=" + afiliación + "monto=" + monto + ".token=" + TKR).  x_auth = TKA (mismo algoritmo que E-commerce).
Nota: el checksum es exclusivo del Botón de Pago. La API E-commerce no lo usa — se autentica solo con X-Auth + X-Time en headers.

Ambiente de pruebas

Comportamiento del sandbox: por E-commerce el sandbox declina por defecto (resp_code 05) aunque tu firma, headers y formato estén perfectos. Es el comportamiento esperado y sirve para validar tu integración extremo a extremo. Para obtener aprobaciones de prueba, solicítanos la ruta habilitada. No interpretes el 05 como que tu integración falla.

Tarjetas de prueba

MarcaNúmeroExp (YY/MM)CVV
Mastercard557910015552100621 / 01111
Visa411076000000000823 / 12123

El set completo para forzar aprobada/rechazo/reembolso se entrega junto con tus credenciales de sandbox.

Operación y errores

  • Timeout: conexión 10 s, respuesta 30–40 s (la autorización puede tardar 15–25 s).
  • Sin reintentos automáticos por timeout (riesgo de doble cobro): ante un timeout, consulta el estado por reference antes de reintentar.
  • Idempotencia: usa un reference único por intento (derívalo de tu order-id). Un reenvío puede regresar 09/94 = TRANSACCIÓN DUPLICADA.
  • Whitelist de IP: no se requiere del lado del comercio.

Checklist a producción

  • El auto-test de vectores del TKA pasa en tu lenguaje.
  • X-Time en milisegundos y reutilizado en la firma.
  • Manejas los 3 desenlaces: aprobada / declinada (resp_code≠00) / error técnico (HTTP≥400).
  • Mapeas los resp_code a mensajes al usuario (catálogo completo).
  • reference único por intento; timeouts configurados; sin reintentos ciegos.
  • Validaste en Sandbox (recuerda: E-commerce declina por defecto).
  • Nos solicitaste las credenciales de Producción y la activación del comercio.

¿Dudas de integración? soporte@payefy.me. Payefy entrega bajo solicitud: la referencia del TKA en tu lenguaje, el set completo de tarjetas de prueba y ejemplos capturados de respuestas.

© Payefy · Documentación Técnica