# Checkout por API --- - [Checkout por API](#checkout) - [Overview](#overview) - [Un intento por cobro](#un-intento-por-cobro) - [Parámetros del pago](#payment-parameters) - [Parámetros del cliente](#customer-parameters) - [Dirección de envío](#delivery-address-parameters) - [Pago con tarjeta](#checkout-tarjetas) - [Parámetros de la tarjeta](#parametros-tarjeta) - [Información del navegador](#parametros-client) - [Ejemplo y respuesta](#ejemplo-para-tarjetas) - [3D Secure](#flujo-3ds) - [Flujo Visa (Credibanco)](#flujo-visa) - [Flujo Mastercard (Redeban)](#flujo-mastercard) - [Tarjetas de prueba de 3DS](#tarjetas-prueba-3ds) - [Abandonar la autenticación](#rechazar-3ds) - [Pago con PSE](#checkout-pse) - [Parámetros de PSE](#parametros-pse) - [Ejemplo para PSE](#ejemplo-para-pse) - [Pago con Bre-B](#checkout-breb) - [Parámetros de Bre-B](#parametros-breb) - [Ejemplo para Bre-B](#ejemplo-para-breb) - [Pago en efectivo](#checkout-efectivos) - [Parámetros de efectivo](#parametros-efectivos) - [Ejemplo para efectivo](#ejemplo-para-efectivos) ## Overview [#overview] 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](/generate-transaction) 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` | :::note `POST /api/v1/payment/transaction-checkout` (sin sufijo) es un alias de `/card` y 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`: `POST /api/v1/payment/transaction-checkout` :::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`](/generate-transaction), 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. :::caution **`customer_payer` no 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 piden `name` y `email`**; si envías el resto, se ignora. ::: ## Un intento por cobro [#un-intento-por-cobro] 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](/generate-transaction) 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`. Decide con `error.code`, no con el texto ni solo con el HTTP. Todos los errores traen el [objeto `error`](/error-codes#sobre): | HTTP | `error.code` | Cuándo | Qué hacer | | - | - | - | - | | `403` | `payment_already_used` | El cobro ya tiene una transacción (cualquier estado, también si ya se pagó) | Crear un cobro nuevo. No reuses el mismo `payment_id` | | `403` | `payment_already_paid` | El cobro ya fue pagado | No cobrar de nuevo | | `403` | `payment_in_progress` | Tiene transacciones en progreso que agotan su límite | Esperar el resultado | | `403` | `payment_expired` | Pasó la fecha límite del cobro | Crear un cobro nuevo | | `403` | `payment_inactive` | El cobro fue desactivado | Crear un cobro nuevo | | `403` | `invalid_payment_credentials` | El `payment.id` o el `payment.token` no son válidos. Mismo código en ambos casos, para no revelar qué ids existen | Revisar el par guardado | | `422` | `validation_failed` | Validación: el body está incompleto o inválido | Corregir el body y repetir **el mismo** cobro | | `429` | `payment_in_progress` | Hay otra petición en curso para este cobro | Esperar y repetir **el mismo** cobro | Ejemplo de respuesta cuando el cobro ya se usó: Código de respuesta: 403 ```json { "message": "Este cobro ya tiene una transacción y no permite reintentos", "error": { "type": "invalid_request_error", "code": "payment_already_used", "message": "Este cobro ya tiene una transacción y no permite reintentos", "param": "payment.id" } } ``` :::caution **El `429` cambió de forma.** Antes respondía `{"error": "texto"}`; ahora el texto viene en `message` y `error` es el objeto con `type: "rate_limit_error"` y `code: "payment_in_progress"`. Si leías `error` como string, cámbialo. Ver [Códigos de error](/error-codes#sobre). ::: :::caution 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. ::: :::note Consultar el [estado](/status-transaction) o recibir el [webhook](/webhook-transaction) no crea otra transacción. Siguen funcionando igual. ::: ## Parámetros del pago [#payment-parameters] | Nombre del campo | Descripción | Reglas | | - | - | - | | payment | Objeto con las credenciales del [pago generado en modalidad `api`](/generate-transaction) | `['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']` | ## Parámetros del cliente [#customer-parameters] | 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](/resources#lista-de-países) | `['required', 'string', 'in:COL,USA,MEX,...']` | | customer_payer.identification_type | Tipo de documento. Ver [enumeraciones](/resources#tipo-de-identificación) | `['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']` | :::note **Tu plantilla de checkout manda.** Si configuraste una [plantilla](/resources#checkout-templates) 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 `required` lo son. ::: ## Dirección de envío [#delivery-address-parameters] 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](/resources#lista-de-departamentos) | `['required_with:delivery_address', 'exists:departments,id']` | | delivery_address.city_id | Id de la [lista de ciudades](/resources#lista-de-ciudades) | `['required_with:delivery_address', 'exists:cities,id']` | | delivery_address.observations | Indicaciones para la entrega | `['nullable']` | ## Pago con tarjeta [#checkout-tarjetas] ### Parámetros de la tarjeta [#parametros-tarjeta] 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](/tokenized#create). 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:']` | | 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](/resources#tipo-de-identificación) | `['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']` | :::note Los campos marcados `required` dejan de serlo si tu [plantilla de checkout](/resources#checkout-templates) los oculta, y también si envías `payment_card.token`: en ese caso la tarjeta ya está guardada. ::: ### Información del navegador [#parametros-client] 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](#flujo-3ds) 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 y respuesta [#ejemplo-para-tarjetas] **Ejemplo de solicitud sin token:** `POST /api/v1/payment/transaction-checkout/card` Cuerpo de ejemplo: ```json { "payment": { "id": "generated_payment_id", "token": "generated_payment_token" }, "customer_payer": { "name": "Pepito perez", "email": "pepito@email.com" }, "payment_card": { "number": "5249314023340339", "name": "PEPITO PEREZ", "expiration_date": "2025-05", "cvv": "478", "identification_type": "CC", "id_number": "342343243", "installments": "1", "dialling_code": "57", "cellphone": "3123456789" } } ``` ```bash 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:** `POST /api/v1/payment/transaction-checkout/card` Cuerpo de ejemplo: ```json { "payment": { "id": "generated_payment_id", "token": "generated_payment_token" }, "customer_payer": { "name": "Efipay", "email": "email@email.com" }, "payment_card": { "token": "credit card token generated", "identification_type": "CC", "id_number": "342343243", "installments": "1", "dialling_code": "57", "cellphone": "3123456789" } } ``` ```bash 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" } }' ``` ### Respuestas :::tip Pago aprobado ::: Código de respuesta: 200 ```json { "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.efipay.co/Checkout/Transaction/9af329f1-e96a-40ab-b466-94a412f12c4a/Response", "approved_at": "2026-07-31T14:32:10.000000Z", "description": "Aprobada", "save": true, "payment_id": "9af329f1-e96a-40ab-b466-94a412f12c4a", "transaction": { "…": "mismos campos de la raíz" } } ``` :::danger Pago rechazado ::: Código de respuesta: 200 ```json { "transaction_id": 20482, "status": "Rechazada", "status_key": "rejected", "response_code": "51", "error": { "code": "51", "message": "Transacción declinada. Fondos insuficientes", "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", "save": true, "payment_id": "9af329f1-e96a-40ab-b466-94a412f12c4a", "transaction": { "…": "mismos campos de la raíz" } } ``` :::note Los campos de decisión están en la raíz y también en `transaction`; lee `status_key` en la raíz. `transaction` se mantiene para las integraciones que ya lo leen de ahí. Además de estos campos, la respuesta trae el bloque propio del medio cuando aplica: `3Ds`, `coupon` (efectivo), `redirect` (PSE) o `qr_breb` (Bre-B). ::: :::caution **Un rechazo llega con `200`.** La petición fue correcta; lo que no se aprobó es el pago. Decide con `status_key`, no con el código HTTP. Ver [códigos de error](/error-codes). ::: ### Cómo decidir sobre la respuesta | 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](/error-codes#red). ::: :::note **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 devuelve `commission`, `fee`, `gravamen`, `iva`, las retenciones y el `liquidated_amount`. Ver [Movimientos](/movements). ::: :::danger Validación ::: Código de respuesta: 422 ```json { "message": "El campo payment_card.cvv es obligatorio.", "errors": { "payment_card.cvv": ["El campo payment_card.cvv es obligatorio."] }, "error": { "type": "invalid_request_error", "code": "validation_failed", "message": "El campo payment_card.cvv es obligatorio.", "param": "payment_card.cvv" } } ``` :::danger El par `payment.id` + `payment.token` no coincide, o el cobro ya no acepta pagos ::: Código de respuesta: 403 ```json { "message": "El id o el token del cobro no son válidos.", "error": { "type": "authorization_error", "code": "invalid_payment_credentials", "message": "El id o el token del cobro no son válidos.", "param": "payment.token" } } ``` Si el par es válido pero el cobro ya no admite pagos, el `error.code` dice por qué (`payment_already_used`, `payment_expired`…). Ver [Un intento por cobro](#un-intento-por-cobro). Si activaste 3D Secure, la respuesta no es ninguna de estas: es la instrucción para continuar la autenticación. Sigue con [3D Secure](#flujo-3ds). ## 3D Secure [#flujo-3ds] 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`. :::caution 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](#parametros-client): 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 ```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** ```bash 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 }' ``` ### Flujo Visa (Credibanco) [#flujo-visa] #### 1. Inicio de la autenticación 3DS 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. ```json { "transaction_id": 1, "amount": 100000, "currency_type": "COP", "status": "Pendiente", "status_key": "pending", "response_code": null, "error": null, "description": "Transacción en proceso: Recolectando Data", "…": "resto de campos de la transacción, iguales a los de `transaction`", "save": true, "payment_id": "9e84d84b-e1e6-4a6a-a0eb-53becc71c359", "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.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" : "
...
", "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. ```javascript 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); } ``` #### 2. Consumir el enroll 3DS 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 `Pendiente` y tener 3DS ya inicializado (`setup_status: COMPLETED`). Si no, la API responde `403` sin detalle. ::: `POST /api/v1/payment/3ds/enroll/20481` Cuerpo de ejemplo: ```json { "payment_card": { "number": "4000000000001091", "name": "Efipay", "expiration_date": "2030-12", "cvv": "123" }, "browser_information": { "colorDepth": 24, "language": "es-CO", "screenHeight": 726, "screenWidth": 2133, "timeDifference": 300, "javaScriptEnabled": true } } ``` Cuerpo de ejemplo: ```json { "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** ```json { "transaction": { "transaction_id": 20481, "status": "Pendiente" }, "3Ds": { "implementation": "credibanco", "browser_response": "
...
" } } ``` `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** ```javascript const setup3dsChallenge = browserResponse => { const wrappedElement = document.getElementById("challenge3ds"); wrappedElement.innerHTML = iframe; var stepUpForm = document.querySelector('#step-up-form'); if (stepUpForm) { stepUpForm.submit(); } } ``` #### Tarjetas de prueba de 3DS [#tarjetas-prueba-3ds] | 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 | ### Flujo Mastercard (Redeban) [#flujo-mastercard] #### 1. Inicio de la autenticación 3DS 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. ```json { "transaction_id": 1, "amount": 100000, "currency_type": "COP", "status": "Pendiente", "status_key": "pending", "response_code": null, "error": null, "description": "Transacción en proceso: Recolectando Data", "…": "resto de campos de la transacción, iguales a los de `transaction`", "save": true, "payment_id": "9e84d84b-e1e6-4a6a-a0eb-53becc71c359", "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.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" : "
...
" } } ``` Recibida esta respuesta con la transacción pendiente y el objeto de 3Ds puedes tomar el siguiente ejemplo para implementar en tu navegador. ```javascript 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); } ``` #### 2. Consumir el auth continue 3DS 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:']` | | payment_card.cvv | Código de seguridad de 3 o 4 dígitos | `['required', 'numeric', 'digits_between:3,4']` | :::note A diferencia de `enroll`, aquí `browser_information` **no se valida**: la autenticación ya está en curso y basta con reenviar la tarjeta. ::: `POST /api/v1/payment/3ds/auth-continue/20481` Cuerpo de ejemplo: ```json { "payment_card": { "number": "4000000000001091", "name": "Efipay", "expiration_date": "2030-12", "cvv": "123" } } ``` Cuerpo de ejemplo: ```json { "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** ```json { "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** ```javascript 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 | ### Abandonar la autenticación [#rechazar-3ds] `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. `POST /api/v1/payment/3ds/reject/20481` ```bash curl -X POST \ '/api/v1/payment/3ds/reject/20481' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H "Content-type: application/json" ``` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json { "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 ::: Código de respuesta: 403 :::note Es el tercer endpoint del flujo 3DS, junto con `enroll` y `auth-continue`. Los tres son necesarios: dos para avanzar y este para cerrar el caso en que el cliente se va. ::: ## Pago con PSE [#checkout-pse] ### Parámetros de PSE [#parametros-pse] 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í](/resources#lista-bancos-pse). Consulta la lista de datos del formulario para pse [aquí](/resources#tipos-de-identificación-pse). 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](/resources#lista-bancos-pse). El código `0` no es un banco válido, es el placeholder «Selecciona tu banco» | `['required', 'not_in:0', 'in:']` | | pse.userType | Tipo de usuario: natural o jurídico. Ver [opciones disponibles](/resources#tipos-de-identificación-pse) | `['required', 'in:']` | | pse.identificationType | Tipo de documento. **Las opciones válidas dependen del `userType` que hayas enviado**. Ver [opciones disponibles](/resources#tipos-de-identificación-pse) | `['required', 'in:']` | | 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']` | :::caution 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-banks` con el mismo token con el que vas a cobrar. ::: ### Ejemplo para PSE [#ejemplo-para-pse] `POST /api/v1/payment/transaction-checkout/pse` Cuerpo de ejemplo: ```json { "payment": { "id": "generated_payment_id", "token": "generated_payment_token" }, "customer_payer": { "name": "Pepito Perez", "email": "email@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" } } ``` ```bash 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/" } }' ``` ## Pago con Bre-B [#checkout-breb] ### Parámetros de Bre-B [#parametros-breb] 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']` | ### Ejemplo para Bre-B [#ejemplo-para-breb] `POST /api/v1/payment/transaction-checkout/bre-b` Cuerpo de ejemplo: ```json { "payment": { "id": "generated_payment_id", "token": "generated_payment_token" }, "customer_payer": { "name": "Pepito Perez", "email": "email@email.com" }, "breb": { "cellphone_number": "3123456789" } } ``` ```bash 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 ```json { "transaction_id": 20481, "status": "Pendiente", "status_key": "pending", "…": "resto de campos de la transacción", "save": true, "payment_id": "9a6f8166-644e-4680-bc37-66535e591ea5", "transaction": { "…": "mismos campos de la raíz" }, "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 | ## Pago en efectivo [#checkout-efectivos] ### Parámetros de efectivo [#parametros-efectivos] 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í](/resources#lista-efectivos) 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](/resources#lista-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']` | ### Ejemplo para efectivo [#ejemplo-para-efectivos] `POST /api/v1/payment/transaction-checkout/cash` Cuerpo de ejemplo: ```json { "payment": { "id": "generated_payment_id", "token": "generated_payment_token" }, "customer_payer": { "name": "Pepito Perez", "email": "email@email.com" }, "cash": { "franchise": "Efecty", "cellphone_number": "3123456789", "identification_number": "123456789" } } ``` ```bash 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" } }' ```