Este es el segundo paso de la modalidad api: procesar el pago con los datos que
capturaste en tu propio checkout.
Antes tienes que haber
generado el pago con
checkout_type: "api", que te devuelve un payment_id y un token. Ese par autentica
esta llamada.
1. POST /payment/generate-payment → { payment_id, token }
2. POST /payment/transaction-checkout/{medio} ← estás aquí
Hay un endpoint por medio de pago:
| Medio | Endpoint |
|---|---|
| Tarjeta | POST /api/v1/payment/transaction-checkout/card |
| PSE | POST /api/v1/payment/transaction-checkout/pse |
| Bre-B | POST /api/v1/payment/transaction-checkout/bre-b |
| Efectivo | POST /api/v1/payment/transaction-checkout/cash |
{info}
POST /api/v1/payment/transaction-checkout(sin sufijo) es un alias de/cardy se mantiene por compatibilidad. En integraciones nuevas usa/card, que dice qué hace.
Si necesitas probar el alias tal cual, recibe exactamente lo mismo que /card:
{danger} Esta modalidad recibe el número de tarjeta y el CVV en tu servidor: te aplica PCI DSS. Si no estás certificado, usa
checkout_type: redirect, que hace lo mismo sin que el dato sensible pase por tu sistema.
Todos los endpoints comparten el bloque payment, un bloque customer_payer y, cuando
tu cobro lo pide, la dirección de envío. Después cada medio agrega el suyo.
{warning}
customer_payerno es igual en todos. Solo el pago con tarjeta exige los datos completos (dirección, ciudad, departamento, país, código postal y teléfono). PSE, Bre-B y efectivo solo pidennamey
Cada payment_id admite un solo intento de transacción. El body de la petición no
cambia; lo que cambia es que no puedes volver a cobrar el mismo payment_id después de
esa primera transacción.
El payment_id y el token que recibiste en
generate-payment sirven para una
transacción. Aplica a tarjeta, PSE, efectivo y Bre-B. El estado de esa transacción no
abre un segundo intento.
Si el pago es rechazado y quieres reintentar, genera un cobro nuevo con
/api/v1/payment/generate-payment y usa el nuevo payment_id + token.
| HTTP | Cuándo | Qué hacer |
|---|---|---|
403 |
El cobro ya tiene una transacción (cualquier estado) | Crear un cobro nuevo. No reuses el mismo payment_id |
422 |
Validación: el body está incompleto o inválido | Corregir el body y repetir el mismo cobro |
429 |
Hay otra petición en curso para este cobro | Esperar y repetir el mismo cobro |
Ejemplo de respuesta cuando el cobro ya se usó:
{
"message": "Este cobro ya tiene una transacción y no permite reintentos"
}
{warning} El flujo 3DS (
/api/v1/payment/3ds/enroll/{transaction_id}y/api/v1/payment/3ds/auth-continue/{transaction_id}) continúa la transacción ya creada. Eso no es un reintento y sigue permitido.
{info} Consultar el estado o recibir el webhook no crea otra transacción. Siguen funcionando igual.
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| payment | Objeto con las credenciales del pago generado en modalidad api |
['required'] |
| payment.id | El payment_id que devolvió generar el pago |
['required', 'string'] |
| payment.token | El token que devolvió generar el pago. Solo se muestra una vez |
['required', 'string'] |
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| customer_payer | Datos de quien paga | ['required'] |
| customer_payer.name | Nombre de quien paga. En efectivo el mínimo baja a 2 caracteres | ['required', 'string', 'min:5', 'max:255'] |
| customer_payer.email | Correo de quien paga. Solo se aceptan caracteres alfanuméricos | ['required', 'email'] |
Los siguientes solo aplican al pago con tarjeta:
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| customer_payer.address_1 | Dirección principal | ['required', 'string', 'min:5', 'max:100'] |
| customer_payer.address_2 | Dirección secundaria | ['required', 'string', 'min:1', 'max:100'] |
| customer_payer.city | Ciudad | ['required', 'string', 'min:1', 'max:100'] |
| customer_payer.state | Departamento o estado | ['required', 'string', 'min:1', 'max:100'] |
| customer_payer.zip_code | Código postal | ['required', 'numeric', 'digits_between:1,10'] |
| customer_payer.country | País en ISO3. Ver lista de países | ['required', 'string', 'in:COL,USA,MEX,...'] |
| customer_payer.identification_type | Tipo de documento. Ver enumeraciones | ['nullable', 'in:CC,CE,TI,PPT,DNI,NIT,Pasaporte,Otro'] |
| customer_payer.id_number | Número de documento. Con NIT debe llevar dígito de verificación (900123456-7). No puede ser un número de tarjeta |
['nullable', 'digits_between:5,15'] |
| customer_payer.dialling_code | Indicativo telefónico con +. Obligatorio salvo que envíes payment_card.cellphone |
['required', 'regex:/^\+\d{1,3}$/i'] |
| customer_payer.cellphone | Celular, solo dígitos. Obligatorio salvo que envíes payment_card.cellphone |
['required', 'numeric', 'digits_between:5,15'] |
{info} Tu plantilla de checkout manda. Si configuraste una plantilla que oculta la dirección, la ciudad, el departamento, el país, el indicativo o el celular, esos campos dejan de ser obligatorios. Sin plantilla, todos los marcados como
requiredlo son.
Si el pago generado se configuró con "advance_options" y existe request_address_delivery se requerirá la siguiente información, de lo contrario no necesita enviarse.
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| delivery_address | Objeto con la dirección de envío | ['required'] |
| delivery_address.address | Dirección de entrega | ['required_with:delivery_address', 'string', 'min:5', 'max:100'] |
| delivery_address.department_id | Id de la lista de departamentos | ['required_with:delivery_address', 'exists:departments,id'] |
| delivery_address.city_id | Id de la lista de ciudades | ['required_with:delivery_address', 'exists:cities,id'] |
| delivery_address.observations | Indicaciones para la entrega | ['nullable'] |
Adicional a los parámetros anteriores se deben agregar los siguientes:
Puedes pagar de dos formas, y son excluyentes: o envías los datos de la tarjeta, o
envías un token de una tarjeta ya guardada. Si mandas token junto con number,
name, expiration_date o cvv, la petición se rechaza.
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| payment_card | Objeto con los datos de la tarjeta | ['required'] |
| payment_card.token | Token de una tarjeta guardada con el tokenizador. Si lo envías, no envíes ningún otro dato de la tarjeta | ['sometimes', 'missing_with:payment_card.number,payment_card.name,payment_card.expiration_date,payment_card.cvv'] |
| payment_card.number | Número de la tarjeta, sin espacios. La franquicia debe estar habilitada en tu comercio | ['required', 'missing_with:payment_card.token', 'numeric', 'digits_between:14,16'] |
| payment_card.name | Nombre impreso en la tarjeta. Solo letras y espacios | ['required', 'missing_with:payment_card.token', 'string'] |
| payment_card.expiration_date | Vencimiento en formato YYYY-MM, con mes entre 01 y 12. No puede estar vencida |
['required', 'missing_with:payment_card.token', 'date_format:Y-m', 'after_or_equal:<mes actual>'] |
| payment_card.cvv | Código de seguridad de 3 o 4 dígitos | ['required', 'missing_with:payment_card.token', 'regex:/^\d{3,4}$/i'] |
| payment_card.installments | Número de cuotas | ['required', 'integer', 'between:1,60'] |
| payment_card.identification_type | Tipo de documento del tarjetahabiente. Ver enumeraciones | ['nullable', 'in:CC,CE,TI,PPT,DNI,NIT,Pasaporte,Otro'] |
| payment_card.id_number | Número de documento del tarjetahabiente. No puede ser un número de tarjeta | ['nullable', 'numeric', 'digits_between:5,15'] |
| payment_card.dialling_code | Indicativo telefónico con +. Obligatorio salvo que envíes customer_payer.cellphone |
['required', 'regex:/^\+\d{1,3}$/i'] |
| payment_card.cellphone | Celular, solo dígitos. Obligatorio salvo que envíes customer_payer.cellphone |
['required', 'numeric', 'digits_between:5,15'] |
| payment_card.redirect_url | A dónde vuelve el cliente tras el iframe de 3DS. Debe ser una URL que responda | ['nullable', 'url', 'active_url', 'max:500'] |
{info} Los campos marcados
requireddejan de serlo si tu plantilla de checkout los oculta, y también si envíaspayment_card.token: en ese caso la tarjeta ya está guardada.
Puedes enviar datos del navegador de tu cliente. Normalmente son opcionales y sirven
para el antifraude, pero si activas 3D Secure con enable_3ds: true siete de ellos
pasan a ser obligatorios, porque la red los exige para autenticar.
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| enable_3ds | Activa la autenticación 3D Secure para este pago | ['sometimes', 'nullable', 'boolean'] |
| browser_information | Objeto con la información del navegador del cliente | ['required_if:enable_3ds,true'] |
| browser_information.colorDepth | Profundidad de color de la pantalla, por ejemplo "24" |
['required_if:enable_3ds,true', 'string', 'max:5'] |
| browser_information.language | Idioma del navegador, por ejemplo "es-CO" |
['required_if:enable_3ds,true', 'string', 'max:10'] |
| browser_information.screenHeight | Alto de la pantalla en píxeles | ['required_if:enable_3ds,true', 'numeric'] |
| browser_information.screenWidth | Ancho de la pantalla en píxeles | ['required_if:enable_3ds,true', 'numeric'] |
| browser_information.timeDifference | Diferencia horaria con UTC en minutos, la que devuelve getTimezoneOffset() |
['required_if:enable_3ds,true', 'numeric'] |
| browser_information.javaScriptEnabled | Si el navegador tiene JavaScript activo | ['required_if:enable_3ds,true', 'boolean'] |
| browser_information.javaEnabled | Si el navegador tiene Java activo | ['required_if:enable_3ds,true', 'boolean'] |
| browser_information.acceptLanguage | Preferencias de idioma del navegador, por ejemplo "en-US" |
['nullable', 'string', 'max:10'] |
| browser_information.ipAddress | IP del cliente que hace la solicitud | ['nullable', 'ip'] |
| browser_information.sessionId | Identificador único de la sesión del usuario | ['nullable', 'string', 'max:255'] |
| browser_information.userAgent | Navegador y sistema operativo del usuario | ['nullable', 'string', 'max:255'] |
Ejemplo de solicitud sin token:
curl -X POST \
"/api/v1/payment/transaction-checkout" \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"payment": {
"id": "9a6f8166-644e-4680-bc37-66535e591ea5",
"token": "1rV9zc9DApoOw3a"
},
"customer_payer": {
"name": "Efipay",
"email": "efipay@gmail.com"
},
"payment_card": {
"number": "5249314023340339",
"name": "efipay",
"expiration_date": "2025-05",
"cvv": "478",
"identification_type": "CC",
"id_number": "342343243",
"installments": "1",
"dialling_code": "57",
"cellphone": "3004564884"
},
"browser_information": {
"acceptLanguage": "es-ES",
"ipAddress": "190.150.0.1",
"sessionId": "dfds54fds534sd534dsds",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
}'
Ejemplo de solicitud con token:
curl -X POST\
'/api/v1/payment/transaction-checkout/card'\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"payment": {
"id": "9abca90d-978d-4722-891f-b41b6ebbae1e",
"token": "quSOdCbSljI31wT"
},
"customer_payer": {
"name": "Efipay",
"email": "email@email.com"
},
"payment_card": {
"token": "eyJpdiI6IkdzaG44RFV5dE5GNlE2MWRSM2lBTGc9PSIsInZhbHVlIjoiZWxyRzkyYTE4THNtY2VseCs5VzlKbW5pa0NicCtiRWhGNWQ5ZTBwTGM1VXF0UXlEemtVWEJyY21ueVIwZ2U5cHNGS2FvdFg1SHVTcmtiQ0phQWNia1JsTUZtQjU3OFpCR0p3bVVlTy9OVVMwM3hJNnJOWGZTbitwL3dlVmtmRStRN2xBZ3paWHI4bDFS
N0lBVHhRa2hBPT0iLCJtYWMiOiJlNDY0NmM4YThhNWMzMDYyOGMxNDAxMjA5MmVlMjNjYjNiYWEyMjczNjA5ZGNkMzM0NmMxMzg0YjZhYjgyY2ZhIiwidGFnIjoiIn0=",
"identification_type": "CC",
"id_number": "342343243",
"installments": "1",
"dialling_code": "57",
"cellphone": "3004564884"
}
}'
{success} Pago aprobado code:
200 { "transaction_id": 20481, "status": "Aprobada", "status_key": "approved", "response_code": "00", "error": null, "amount": 50000, "currency_type": "COP", "value_cop": 50000, "tax": 7983, "payment_method": "credit", "payment_method_source": "Visa", "card": { "franchise": "Visa", "bin": "453210", "last_four": "7890" }, "authorization_code": "005077", "trazability_id": "320000303129", "url_response": "https://sag-qa.efipay.co/Checkout/Transaction/9af329f1-e96a-40ab-b466-94a412f12c4a/Response", "approved_at": "2026-07-31T14:32:10.000000Z", "description": "Aprobada" }
{danger} Pago rechazado code:
200 { "transaction_id": 20482, "status": "Rechazada", "status_key": "rejected", "response_code": "51", "error": { "code": "51", "message": "Fondos insuficientes en la cuenta del tarjetahabiente.", "retryable": false, "action": "contact_issuer" }, "amount": 50000, "payment_method": "credit", "payment_method_source": "Visa", "card": { "franchise": "Visa", "bin": "453210", "last_four": "7890" }, "authorization_code": null, "description": "Transacción declinada. Fondos insuficientes" }
{warning} Un rechazo llega con
200. La petición fue correcta; lo que no se aprobó es el pago. Decide constatus_key, no con el código HTTP. Ver códigos de error.
| Campo | Tipo | Para qué |
|---|---|---|
status |
string | Estado en español (Aprobada, Rechazada, Pendiente…). Para mostrar |
status_key |
string | Clave estable en inglés. Para decidir en tu código |
response_code |
string | null | Código crudo de la red (00, 05, 51, M12…). Es el que hay que citarnos en un soporte |
error |
objeto | null | null si se aprobó. Si no, {code, message, retryable, action} |
error.retryable |
bool | Si tiene sentido volver a intentar. false en fondos insuficientes, tarjeta vencida y fraude |
tax |
número | null | Impuesto de la transacción, para cuadrar tu factura |
card |
objeto | null | {franchise, bin, last_four}, ya separados |
payment_id |
uuid | El cobro al que pertenece. Sirve de correlación si falta transaction_id |
Valores de status_key: approved, rejected, failed, pending, started,
for_pay, cancelled, reversed, escalated_reversal, authorized.
{danger} Nunca reintentes automáticamente con
error.retryable: false. Reintentar un rechazo por fondos o por tarjeta bloqueada da el mismo resultado, y algunos emisores penalizan la insistencia. El catálogo completo de códigos está en Códigos de error.
{info} La comisión no viene aquí. En el momento del pago todavía no está liquidada. Consúltala después en
GET /api/v1/virtual-account/movements/{transaction_id}, que devuelvecommission,fee,gravamen,iva, las retenciones y elliquidated_amount. Ver Movimientos.
{danger} Validación code:
422 { "message": "El campo payment_card.cvv es obligatorio.", "errors": { "payment_card.cvv": ["El campo payment_card.cvv es obligatorio."] } }
{danger} El par
payment.id+payment.tokenno coincide, o el cobro ya fue pagado code:403
Si activaste 3D Secure, la respuesta no es ninguna de estas: es la instrucción para continuar la autenticación. Sigue con 3D Secure.
Para implementar el flujo de 3Ds mediante api se requiere que el comercio realice el desarrollo del flujo para estos casos. Como aclaración se debe tener en cuenta que se tienen dos implementaciones diferentes según la franquicia que de la tarjeta, a continuación se explicaran con detalle ambos casos.
Para habilitar 3Ds en transacciones con Visa y Mastercard adiciona el atributo enable_3ds, y junto a él la información del navegador del cliente en browser_information.
{warning} 3D Secure pasará a ser obligatorio. Cuando tengamos la fecha en firme la publicaremos aquí y la anunciaremos por correo; mientras tanto, habilitarlo ya te deja listo. Ver flujo 3Ds
Los campos exactos y sus reglas están en
Información del navegador: con enable_3ds: true, siete campos de
browser_information pasan a ser obligatorios.
Par obtener la información del navegador puedes usar esta función para javascript
function getBrowserInformation() {
// Get color depth
const colorDepth = window.screen.colorDepth;
// Check if JavaScript is enabled (if this runs, JavaScript is enabled)
const jsEnabled = true;
let javaEnabled = false;
try {
javaEnabled = navigator.javaEnabled();
} catch (error) {
const javaEnabled = false;
}
// Get browser language
const language = navigator.language || navigator.userLanguage;
// Get screen height and width
const screenHeight = window.innerHeight;
const screenWidth = window.innerWidth;
// Calculate time difference from UTC (in hours)
const timeDifference = new Date().getTimezoneOffset();
return {
colorDepth,
language,
screenHeight,
screenWidth,
timeDifference,
javaScriptEnabled: jsEnabled,
javaEnabled,
}
}
Ejemplo compra con 3Ds habilitado
curl -X POST\
'/api/v1/payment/transaction-checkout'\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"payment": {
"id": "9a6f8166-644e-4680-bc37-66535e591ea5",
"token": "1rV9zc9DApoOw3a"
},
"customer_payer": {
"name": "Efipay",
"email": "efipay@gmail.com"
},
"payment_card": {
"number": "5249314023340339",
"name": "efipay",
"expiration_date": "2025-05",
"cvv": "478",
"identification_type": "CC",
"id_number": "342343243",
"installments": "1",
"dialling_code": "57",
"cellphone": "3004564884"
},
"browser_information": {
"colorDepth": "24",
"language": "es-ES",
"screenHeight": 726,
"screenWidth": 2133,
"timeDifference": 300,
"javaScriptEnabled": true,
"javaEnabled": false
},
"enable_3ds": true
}'
Al iniciar la trx se responderá con un objeto con información para continuar con el flujo de 3ds, el cual tendrá el nombre de la implementación, un código html para agregar en el navegador del cliente y una url para validar el éxito de la operación, en caso de que el objeto 3Ds no sea devuelto se devolverá el objeto con la transacción con su información del estado de la misma.
Ejemplo respuesta 3Ds para continuar con la autenticación.
{
"save": true,
"transaction": {
"transaction_id": 1,
"amount": 100000,
"currency_type": "COP",
"value_cop": 100000,
"payment_method": "credit",
"payment_method_source": "Credibanco",
"trazability_id": null,
"authorization_code": null,
"transaction_details": {
"name": "Efipay",
"identification_type": "CC",
"identification_number": "123456789",
"email": "efipay@efipay.com",
"country": "+57",
"phone": "3001234567",
"number_card": "123456******1234",
"installments": "1",
"franchise": "Credibanco",
"status_message": "Transacción en proceso: Recolectando Data"
},
"status": "Pendiente",
"url_response": "https://sag-qa.efipay.co/Checkout/Transaction/9e84d84b-e1e6-4a6a-a0eb-53becc71c359/Response",
"approved_at": null,
"production": true,
"created_at": "2025-03-25 15:20:17",
"customer_payer": {
"name": "Efipay",
"email": "efipay@efipay.com",
"country": "COL",
"zip_code": "0000",
"state": "Bogota",
"city": "Bogota",
"address_2": "Cr 23",
"address_1": "Apto 1A",
"created_at": "2024-11-07 18:39:40",
"updated_at": "2024-11-07 18:39:40"
},
"currency_rate_conversion": {
"id": 1,
"usd_to_cop": 4288.58,
"eur_to_cop": 4640.617049,
"trm_for_cop": 1,
"active": 1,
"created_at": "2024-10-21T16:20:23.000000Z",
"updated_at": "2024-10-21T16:20:23.000000Z",
"deleted_at": null
},
"description": "Transacción en proceso: Recolectando Data"
},
"3Ds": {
"implementation" : "credibanco",
"browser_response" : "<div>...</div>",
"centinelapistag" : "https://centinelapistag..."
}
}
Recibida esta respuesta con la transacción pendiente y el objeto de 3Ds puedes tomar el siguiente ejemplo para implementar en tu navegador.
const setup3DsIframe = (browserResponse, centinelapistag) => {
const wrappedElement = document.getElementById("hidden3ds");
wrappedElement.innerHTML = iframe;
const ddcForm = document.querySelector('#ddc-form');
if (ddcForm) {
console.log('ddcForm', ddcForm);
ddcForm.submit();
}
let eventMessage3ds = false;
const threeDsTimeOut = setTimeout(() => {
if (!eventMessage3ds) {
//rechazar transacción
console.log('Error red demasiado tiempo esperando mensaje 3ds');
}
}, 50000);
window.addEventListener("message", (event) => {
eventMessage3ds = true;
clearTimeout(threeDsTimeOut)
if (event.origin === centinelapistag) {
let data = JSON.parse(event.data);
console.log('Merchant received a message:', data);
if (data !== undefined && data.Status) {
console.log('Songbird ran DF successfully');
enrollTransaction(); // Continuar con -> 2. Consumir el enroll 3Ds
} else {
//rechazar transacción
console.log('Error evento de mensaje front status 3ds');
}
}
}, false);
}
Cuando se obtenga la repuesta satisfactoria del evento se puede continuar con el consumo del enroll, aquí se pueden presentar dos casos, el primero en donde 3Ds determine la autenticación exitosa y se pueda procesar la transacción sin mas pasos, la segunda donde 3Ds determine hacer una validación adicional con un challenge para autenticar al tarjetahabiente
Para el primer caso se devolverá la transacción con su estado correspondiente a como es costumbre.
Para el caso donde se solicite el challenge se responderá con un nuevo objeto 3Ds así:
Recuerde que este flujo puede presentarse un challenge del banco que redirige al cliente a una nueva ventana para que el cliente pase el challenge por lo que si desea volver a ser redirigido automáticamente a una pagina de tu integración deberás configurar las opciones avanzadas con result_urls, adicional puede usar el webhook para obtener la respuesta en paralelo a su integración.
Request
POST /api/v1/payment/3ds/enroll/{transaction_id}
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| payment_card | Datos de la tarjeta a autenticar | ['required'] |
| payment_card.number | Número de la tarjeta, sin espacios | ['required', 'numeric', 'digits_between:12,20'] |
| payment_card.name | Nombre impreso en la tarjeta. Solo letras y espacios | ['required', 'string'] |
| payment_card.expiration_date | Vencimiento en formato YYYY-MM. No puede estar vencida |
['required', 'date'] |
| payment_card.cvv | Código de seguridad de 3 o 4 dígitos | ['required', 'numeric', 'digits_between:3,4'] |
| browser_information | Datos del navegador del cliente | ['required'] |
| browser_information.colorDepth | Profundidad de color de la pantalla | ['required', 'integer'] |
| browser_information.javaScriptEnabled | Si el navegador tiene JavaScript activo | ['required', 'boolean'] |
| browser_information.language | Idioma del navegador | ['required', 'string'] |
| browser_information.screenHeight | Alto de la pantalla en píxeles | ['required', 'numeric'] |
| browser_information.screenWidth | Ancho de la pantalla en píxeles | ['required', 'numeric'] |
| browser_information.timeDifference | Diferencia horaria con UTC en minutos | ['required', 'numeric'] |
| browser_information.ipAddress | IP del cliente | ['nullable', 'ip'] |
| browser_information.sessionId | Identificador de la sesión | ['nullable', 'string', 'max:255'] |
| browser_information.userAgent | Navegador y sistema operativo | ['nullable', 'string', 'max:255'] |
{danger} La transacción debe estar en estado
Pendientey tener 3DS ya inicializado (setup_status: COMPLETED). Si no, la API responde403sin detalle.
Cuerpo de ejemplo:
{
"payment_card": {
"number": "4000000000001091",
"name": "Efipay",
"expiration_date": "2025-12",
"cvv": "123"
},
"browser_information": {
"colorDepth": "24",
"language": "es-ES",
"screenHeight": 726,
"screenWidth": 2133,
"timeDifference": 300,
"javaScriptEnabled": true
}
}
Response
{
"transaction": { "transaction_id": 20481, "status": "Pendiente" },
"3Ds": {
"implementation": "credibanco",
"browser_response": "<div id=\"3ds-form\">...</div>"
}
}
transaction trae la transacción completa, igual que en un pago normal, pero en estado
Pendiente. Lo que tienes que usar es 3Ds.browser_response: es HTML que debes
insertar en tu página para que el banco muestre su reto al cliente.
Ejemplo cliente
const setup3dsChallenge = browserResponse => {
const wrappedElement = document.getElementById("challenge3ds");
wrappedElement.innerHTML = iframe;
var stepUpForm = document.querySelector('#step-up-form');
if (stepUpForm) {
stepUpForm.submit();
}
}
| Versión de 3DS Tarjetas | Tarjetas Visa |
|---|---|
| "AUTHENTICATION_SUCCESSFUL" | 400000 00 0000 2701 |
| "AUTHENTICATION_FAILED" | 400000 00 0000 2925 |
| "PENDING_AUTHENTICATION" | 400000 00 0000 2503 |
PENDING_AUTHENTICATION "AUTHENTICATION_FAILED" |
400000 00 0000 2370 |
Al iniciar la trx se responderá con un objeto con información para continuar con el flujo de 3ds, el cual tendrá el nombre de la implementación, un código html para agregar en el navegador del cliente, en caso de que el objeto 3Ds no sea devuelto se devolverá el objeto con la transacción con su información del estado de la misma.
Ejemplo respuesta 3Ds para continuar con la autenticación.
{
"save": true,
"transaction": {
"transaction_id": 1,
"amount": 100000,
"currency_type": "COP",
"value_cop": 100000,
"payment_method": "credit",
"payment_method_source": "Credibanco",
"trazability_id": null,
"authorization_code": null,
"transaction_details": {
"name": "Efipay",
"identification_type": "CC",
"identification_number": "123456789",
"email": "efipay@efipay.com",
"country": "+57",
"phone": "3001234567",
"number_card": "123456******1234",
"installments": "1",
"franchise": "Credibanco",
"status_message": "Transacción en proceso: Recolectando Data"
},
"status": "Pendiente",
"url_response": "https://sag-qa.efipay.co/Checkout/Transaction/9e84d84b-e1e6-4a6a-a0eb-53becc71c359/Response",
"approved_at": null,
"production": true,
"created_at": "2025-03-25 15:20:17",
"customer_payer": {
"name": "Efipay",
"email": "efipay@efipay.com",
"country": "COL",
"zip_code": "0000",
"state": "Bogota",
"city": "Bogota",
"address_2": "Cr 23",
"address_1": "Apto 1A",
"created_at": "2024-11-07 18:39:40",
"updated_at": "2024-11-07 18:39:40"
},
"currency_rate_conversion": {
"id": 1,
"usd_to_cop": 4288.58,
"eur_to_cop": 4640.617049,
"trm_for_cop": 1,
"active": 1,
"created_at": "2024-10-21T16:20:23.000000Z",
"updated_at": "2024-10-21T16:20:23.000000Z",
"deleted_at": null
},
"description": "Transacción en proceso: Recolectando Data"
},
"3Ds": {
"implementation" : "redeban",
"browser_response" : "<div>...</div>"
}
}
Recibida esta respuesta con la transacción pendiente y el objeto de 3Ds puedes tomar el siguiente ejemplo para implementar en tu navegador.
const setup3DsIframe = iframe => {
const wrappedElement = document.getElementById("hidden3ds");
wrappedElement.innerHTML = iframe;
Array.from(wrappedElement.querySelectorAll("script"))
.forEach( oldScriptEl => {
const newScriptEl = document.createElement("script");
Array.from(oldScriptEl.attributes).forEach( attr => {
newScriptEl.setAttribute(attr.name, attr.value)
});
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
newScriptEl.appendChild(scriptText);
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
});
setTimeout(() => {
console.log("run timeout 5sec");
authContinueTransaction();// Continuar con -> 2. Consumir el auth continue 3Ds
}, 5000);
}
Cuando se obtenga la repuesta satisfactoria del evento se puede continuar con el consumo del enroll, aquí se pueden presentar dos casos, el primero en donde 3Ds determine la autenticación exitosa y se pueda procesar la transacción sin mas pasos, la segunda donde 3Ds determine hacer una validación adicional con un challenge para autenticar al tarjetahabiente
Para el primer caso se devolverá la transacción con su estado correspondiente a como es costumbre.
Para el caso donde se solicite el challenge se responderá con un nuevo objeto 3Ds así:
Request
POST /api/v1/payment/3ds/auth-continue/{transaction_id}
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| payment_card | Datos de la tarjeta a autenticar | ['required'] |
| payment_card.number | Número de la tarjeta, sin espacios | ['required', 'numeric', 'digits_between:12,20'] |
| payment_card.name | Nombre impreso en la tarjeta. Solo letras y espacios | ['required', 'string'] |
| payment_card.expiration_date | Vencimiento en formato YYYY-MM, con mes entre 01 y 12. No puede estar vencida |
['required', 'date_format:Y-m', 'after_or_equal:<mes actual>'] |
| payment_card.cvv | Código de seguridad de 3 o 4 dígitos | ['required', 'numeric', 'digits_between:3,4'] |
{info} A diferencia de
enroll, aquíbrowser_informationno se valida: la autenticación ya está en curso y basta con reenviar la tarjeta.
Cuerpo de ejemplo:
{
"payment_card": {
"number": "4000000000001091",
"name": "Efipay",
"expiration_date": "2025-12",
"cvv": "123"
},
"browser_information": {
"colorDepth": "24",
"language": "es-ES",
"screenHeight": 726,
"screenWidth": 2133,
"timeDifference": 300,
"javaScriptEnabled": true
}
}
Response
{
"transaction": { "transaction_id": 20481, "status": "Pendiente" },
"3Ds": {
"implementation": "redeban",
"browser_response": { "challenge_request": "iframe" }
}
}
En Mastercard browser_response es un objeto y no HTML: te indica cómo montar el reto.
Ejemplo cliente
const setup3dsChallenge = browserResponse => {
const wrappedElement = document.getElementById("challenge3ds");
wrappedElement.innerHTML = iframe;
Array.from(wrappedElement.querySelectorAll("script"))
.forEach( oldScriptEl => {
const newScriptEl = document.createElement("script");
Array.from(oldScriptEl.attributes).forEach( attr => {
newScriptEl.setAttribute(attr.name, attr.value)
});
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
newScriptEl.appendChild(scriptText);
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
});
}
| tarjeta | descripción | monto |
|---|---|---|
| 2221008123677736 | 3DS Challenge | 151 |
POST /api/v1/payment/3ds/reject/{transaction}
Tu cliente puede cerrar el reto 3DS sin completarlo. Cuando eso pasa, la transacción se
queda en Pendiente y tu pedido queda colgado esperando algo que ya no va a llegar.
Llama a este endpoint para cerrarla como rechazada. El parámetro es el
transaction_id (el consecutivo) de la transacción que quedó pendiente.
curl -X POST \
'/api/v1/payment/3ds/reject/20481' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json"
{success} Respuesta satisfactoria code:
200 { "save": true, "transaction": { "transaction_id": 20481, "status": "Rechazada", "description": "Pago rechazado por el usuario en el proceso 3DS" } }
{danger} La transacción no está en
Pendiente: ya se resolvió, y no se puede rechazar code:403
{info} Es el tercer endpoint del flujo 3DS, junto con
enrollyauth-continue. Los tres son necesarios: dos para avanzar y este para cerrar el caso en que el cliente se va.
Cuando realizes la petición recibirás una url a la cual deberás redireccionar al usuario para que pueda realizar el pago, cuando el usuario realize el pago en su banco y regrese al comercio sera redireccionado a uno de las siguientes opciones; a la url si agregaste el custom_redirect_url en este checkout si no al checkout de respuesta de efipay.
La validación del pago lo podrás hacer a través de nuestro webhook o consultando el status al ser redireccionado a una de tus url personalizadas de redirección.
Consulta la lista de bancos disponibles aquí. Consulta la lista de datos del formulario para pse aquí.
Adicional a los parámetros anteriores se deben agregar los siguientes:
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| pse | Objeto con los datos del pago PSE | ['required'] |
| pse.financialInstitutionCode | Código del banco, de la lista de bancos. El código 0 no es un banco válido, es el placeholder «Selecciona tu banco» |
['required', 'not_in:0', 'in:<códigos de la lista de bancos>'] |
| pse.userType | Tipo de usuario: natural o jurídico. Ver opciones disponibles | ['required', 'in:<tipos de usuario PSE>'] |
| pse.identificationType | Tipo de documento. Las opciones válidas dependen del userType que hayas enviado. Ver opciones disponibles |
['required', 'in:<tipos de identificación del userType>'] |
| pse.identificationNumber | Número de documento. No puede ser un número de tarjeta | ['required', 'numeric', 'digits_between:5,15'] |
| pse.fullName | Nombre de quien paga | ['required', 'string', 'min:5', 'max:64'] |
| pse.cellphoneNumber | Celular, exactamente 10 dígitos | ['required', 'numeric', 'digits:10'] |
| pse.address | Dirección de quien paga | ['required', 'string', 'min:5', 'max:64'] |
| pse.email | Correo de quien paga. Solo caracteres alfanuméricos | ['required', 'email', 'max:110'] |
| pse.redirect | A dónde vuelve el cliente después de pagar en su banco. Debe ser una URL que responda | ['nullable', 'url', 'active_url', 'max:191'] |
{warning} El banco debe estar habilitado para tu comercio y pertenecer a la lista del ambiente de tu token: las listas de prueba y producción no son iguales. Consulta siempre
GET /api/v1/resources/checkout/pse-bankscon el mismo token con el que vas a cobrar.
curl -X POST\
"/api/v1/payment/transaction-checkout/pse"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"payment": {
"id": "generated_payment_id", //9a6f8166-644e-4680-bc37-66535e591ea5
"token": "token_payment" //1rV9zc9DApoOw3a
},
"customer_payer": {
"name": "Pepito perez",
"email": "pepito@email.com"
},
"pse": {
"financialInstitutionCode": "0000",
"userType": "person",
"identificationType": "CedulaDeCiudadania",
"identificationNumber": "123456789",
"fullName": "Pepito Perez",
"cellphoneNumber": "3123456789",
"address": "calle 93 # 32",
"email": "pepito@email.com",
"redirect": "https://efipay.co/"
}
}'
Iniciar una transacción con bre-b del pago generado con /generate-payment el cual creará un QR disponible por 30 minutos para que el usuario pueda concluir la trasacción en su aplicación bancaria
Es necesario implementar el webhook para recibir el resultado final de la transacción una vez se concluya, adicionalmete podra hacer uso del api de status para consultar el estado de la transacción.
Adicional a los parámetros anteriores se deben agregar los siguientes:
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| breb | Objeto con los datos del pago Bre-B | ['required'] |
| breb.cellphone_number | Celular de quien paga, exactamente 10 dígitos | ['required', 'numeric', 'digits:10'] |
curl -X POST\
"/api/v1/payment/transaction-checkout/bre-b"\
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H "Content-type: application/json" \
-d '{
"payment": {
"id": "generated_payment_id", //9a6f8166-644e-4680-bc37-66535e591ea5
"token": "token_payment" //1rV9zc9DApoOw3a
},
"customer_payer": {
"name": "Pepito perez",
"email": "pepito@email.com"
},
"breb": {
"cellphone_number": "3123456789",
}
}'
Respuesta
{
"save": true,
"transaction": { "transaction_id": 20481, "status": "Pendiente" },
"qr_breb": {
"qr_code_data": "00020101021226580014CO.COM.BREB...",
"qr_code_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"expiration_date": "2026-04-23 10:43:42"
}
}
| Campo | Qué es |
|---|---|
| qr_code_data | La cadena del QR. Genera tú la imagen, o imprímela |
| qr_code_image | La misma imagen ya generada, en base64, si prefieres mostrarla directo |
| expiration_date | El QR vence a los 30 minutos. Después hay que generar otro |
Ten presente que al usar este checkout para pagos en efectivos al realizar la petición todas las transacciones darán como respuesta el estado por pagar, y la información del cupón para que tu usuario realize el pago en la sucursal de efectivo correspondiente.
Para poder validar el pago de este tipo de transacciones te ofrecemos dos opciones; la primera y más sencilla usar nuestro webhook para notificarte la nueva información sobre las transacciones y la segunda es que realices una consulta del estado del pago después del tiempo de expiración que también te proporcionamos en la respuesta.
Podrás consultar la lista de efectivos disponibles para ti aquí
Adicional a los parámetros anteriores se deben agregar los siguientes:
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| cash.franchise | Nombre del punto de recaudo, de la lista de efectivos. Debe estar habilitado en tu comercio | ['required', 'string'] |
| cash.cellphone_number | Celular de quien paga, entre 6 y 10 dígitos | ['required', 'numeric', 'min_digits:6', 'max_digits:10'] |
| cash.identification_number | Número de documento de quien paga. No puede ser un número de tarjeta | ['required', 'numeric', 'digits_between:5,15'] |
curl -X POST\
"/api/v1/payment/transaction-checkout/cash" \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-type: application/json' \
-d '{
'payment': {
'id': 'generated_payment_id',
'token': 'token_payment'
},
'customer_payer': {
'name': 'Pepito peres',
'email': 'pepito@gmail.com'
},
"cash" : {
"franchise": "Efecty",
"cellphone_number": "3123456789",
"identification_number": "123456789"
}
}'