Generar un pago
Overview
Sección titulada «Overview»Este es el primer paso de cualquier cobro por API: le dices a Efipay qué vas a cobrar y cuánto, y te devolvemos con qué seguir.
Lo que recibes depende de payment.checkout_type:
redirect→ unurlal que rediriges a tu cliente. Nosotros capturamos el pago.api→ untokencon el que procesas el pago desde tu propio checkout.
Opcionalmente puedes activar idempotencia con el header X-Idempotency-Enabled y
advanced_options.references para evitar pagos duplicados ante reintentos. Ver
Idempotencia.
POST /api/v1/payment/generate-payment
Un intento por cobro
Sección titulada «Un intento por cobro»Cada payment_id que devuelve este endpoint admite una sola transacción, tanto si
el cobro es redirect como si es api. El estado de esa transacción no importa:
aprobada, rechazada, pendiente o expirada, el cobro ya no acepta otro intento.
Un cobro (payment_id) y una transacción no son lo mismo. Este endpoint crea el
cobro. La transacción nace después, cuando el pagador usa la URL de checkout o cuando
llamas a Checkout.
A partir de ese primer intento el payment_id queda consumido. Si el pago falla y
quieres volver a intentar (otra tarjeta, otro medio, el mismo pagador), genera un
cobro nuevo: vuelve a llamar a /api/v1/payment/generate-payment y usa el nuevo
payment_id (y el nuevo token o url).
Sí consume el intento
- Cualquier transacción creada: tarjeta, PSE, efectivo, Bre-B, 3DS iniciado o abandono del checkout.
- Da igual el estado final: Aprobada, Rechazada, Pendiente o expirada.
No consume el intento
- Un
422de validación (el body está mal y aún no se creó transacción).
El cuerpo de generate-payment y de transaction-checkout no cambia. El flujo 3DS
(/api/v1/payment/3ds/...) sigue sobre la transacción ya creada: eso no es un segundo
cobro.
Elige tu modalidad
Sección titulada «Elige tu modalidad»redirect |
api |
|
|---|---|---|
| Quién captura el medio de pago | Nosotros | Tú |
| Qué recibes | url |
token |
| Peticiones para cobrar | 1 | 2 |
| Requiere certificación PCI DSS | No | Sí |
| Diseño del checkout | El nuestro, con tu logo | El tuyo |
| Medios de pago | Todos los que tengas habilitados | Los que implementes |
Parámetros del pago
Sección titulada «Parámetros del pago»| Nombre del campo | Descripción | Reglas |
|---|---|---|
| payment | Objeto con los datos del cobro | ['required'] |
| payment.description | Qué estás cobrando. Tu cliente lo ve en el checkout y en el comprobante | ['required', 'string', 'min:4', 'max:191'] |
| payment.amount | Valor a cobrar, en la unidad de la moneda (no en centavos), con hasta 2 decimales. El máximo depende de la moneda: 999999999999.99 en COP y 200000000.99 en USD/EUR |
['required', 'numeric', 'min:1', 'max:999999999999.99', 'decimal:0,2'] |
| payment.currency_type | Moneda del cobro. Ver monedas | ['required', 'string', 'in:COP,USD,EUR'] |
| payment.checkout_type | redirect (te damos un link) o api (cobras tú). Ver tipos de checkout |
['required', 'string', 'in:redirect,api'] |
| payment.selected_taxes | Ids de tus impuestos a aplicar. No se puede usar junto con tax_amount |
['nullable', 'array', 'missing_with:payment.tax_amount'] |
| payment.selected_taxes.* | Cada id debe corresponder a un impuesto activo | ['required', 'exists:taxes,id'] |
| payment.tax_amount | Valor del impuesto ya calculado por ti. No se puede usar junto con selected_taxes |
['nullable', 'numeric', 'decimal:0,2', 'min:0', 'max:<payment.amount>', 'missing_with:payment.selected_taxes'] |
| payment.metadata | Tus propios pares clave-valor. Ver Metadata | ['sometimes', 'nullable', 'metadata'] |
| payment.checkout_template_id | Plantilla de checkout que decide qué campos se le piden al cliente | ['nullable', 'exists:checkout_templates,id'] |
| office | Sucursal a la que pertenece el cobro. Debe ser una de tus sucursales | ['required', 'exists:offices,id'] |
Opciones avanzadas
Sección titulada «Opciones avanzadas»Todo lo de advanced_options es opcional y sirve para personalizar el cobro: hasta
cuándo se puede pagar, a dónde vuelve el cliente, qué medios de pago ofreces, si hay
descuento o envío.
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| advanced_options | Objeto con todo lo que sigue | ['nullable'] |
| advanced_options.picture | URL de una imagen a mostrar en el checkout. En este endpoint es una URL, no un archivo | ['nullable', 'string', 'url', 'ends_with:.jpg,.png,.jpeg'] |
| advanced_options.limit_date | Hasta cuándo se puede pagar. Acepta Y-m-d o Y-m-d H:i:s |
['nullable', 'date', 'after_or_equal:today'] |
| advanced_options.limit_payments | Cuántas transacciones aprobadas admite el cobro | ['nullable', 'integer'] |
| advanced_options.references | Tus referencias para identificar el cobro. Hasta 3. Si activas idempotencia, son la llave para no crear un pago duplicado | ['nullable', 'array', 'max:3']. Con X-Idempotency-Enabled: true: ['required', 'array', 'min:1', 'max:3'] |
| advanced_options.references.* | Cada referencia. El conjunto completo es la llave de idempotencia | ['required', 'string', 'max:50'] |
| advanced_options.result_urls | A dónde vuelve el cliente y a dónde te notificamos | ['nullable', 'array'] |
| advanced_options.result_urls.approved | Retorno de una transacción aprobada. También se acepta la clave Aprobada |
['nullable', 'url'] |
| advanced_options.result_urls.rejected | Retorno de una transacción rechazada. También se acepta la clave Rechazada |
['nullable', 'url'] |
| advanced_options.result_urls.pending | Retorno de una transacción pendiente. También se acepta la clave Pendiente |
['nullable', 'url'] |
| advanced_options.result_urls.webhook | A dónde te notificamos cada cambio de estado. Debe aceptar POST. Ver más |
['nullable', 'url'] |
| advanced_options.delivery_service | Servicio de envío | ['nullable'] |
| advanced_options.delivery_service.type | Gratis o Con Valor. Ver enumeraciones |
['required_with:advanced_options.delivery_service', 'in:Gratis,Con Valor'] |
| advanced_options.delivery_service.value | Valor del envío, que se suma al monto. Obligatorio cuando el tipo es Con Valor |
['required_with:advanced_options.delivery_service', 'required_if:advanced_options.delivery_service.type,Con Valor', 'numeric', 'decimal:0,2'] |
| advanced_options.request_address_delivery | Pedirle la dirección de envío al cliente en el checkout | ['nullable', 'boolean'] |
| advanced_options.has_all_payment_methods | true para ofrecer todos los medios habilitados en tu comercio, sin listarlos |
['nullable', 'boolean'] |
| advanced_options.payment_methods | Qué medios de pago ofreces. Debe traer al menos uno, y todos deben estar habilitados en tu comercio. Ver métodos de pago | ['sometimes', 'bail'] |
| advanced_options.payment_methods.credit | Franquicias de tarjeta a aceptar | ['sometimes', 'nullable', 'array'] |
| advanced_options.payment_methods.debit | Medios débito a aceptar | ['sometimes', 'nullable', 'array'] |
| advanced_options.payment_methods.pse | Bancos PSE a aceptar | ['sometimes', 'nullable', 'array'] |
| advanced_options.payment_methods.cash | Puntos de recaudo en efectivo a aceptar | ['sometimes', 'nullable', 'array'] |
| advanced_options.discount | Descuento sobre el monto | ['nullable'] |
| advanced_options.discount.before_on | Hasta qué fecha aplica el descuento | ['required_with:advanced_options.discount', 'date', 'after_or_equal:today'] |
| advanced_options.discount.type | value o percentage. Ver enumeraciones |
['required_with:advanced_options.discount', 'in:value,percentage'] |
| advanced_options.discount.value | Valor del descuento, según el tipo. Un porcentaje no puede pasar de 100, ni un valor superar el monto | ['required_with:advanced_options.discount', 'numeric', 'decimal:0,2'] |
| advanced_options.cash_expired_period | Cuánto dura el cupón de pago en efectivo, junto con cash_expired_interval |
['nullable', 'required_with:advanced_options.cash_expired_interval', 'integer', 'between:1,60'] |
| advanced_options.cash_expired_interval | Unidad de esa duración. Ver frecuencias | ['nullable', 'required_with:advanced_options.cash_expired_period', 'in:minute,hour,day,week,month,year'] |
| advanced_options.has_comments | Pedirle un comentario al cliente en el checkout | ['nullable', 'boolean'] |
| advanced_options.comments_label | Qué texto acompaña ese campo de comentario | ['nullable', 'string', 'max:100'] |
| advanced_options.credibanco_gateway | Tus propios códigos de terminal Credibanco, si no operas por agregador | ['nullable', 'array'] |
| advanced_options.credibanco_gateway.code | Código único de comercio en Credibanco | ['required_with:advanced_options.credibanco_gateway', 'string', 'min:3', 'max:15'] |
| advanced_options.credibanco_gateway.terminal | Código de terminal en Credibanco | ['required_with:advanced_options.credibanco_gateway', 'string', 'min:3', 'max:15'] |
| advanced_options.redeban_gateway | Tus propios códigos de terminal Redeban | ['nullable', 'array'] |
| advanced_options.redeban_gateway.code | Código único de comercio en Redeban | ['required_with:advanced_options.redeban_gateway', 'string', 'min:3', 'max:15'] |
| advanced_options.redeban_gateway.terminal | Código de terminal en Redeban | ['required_with:advanced_options.redeban_gateway', 'string', 'min:3', 'max:15'] |
| advanced_options.pse_gateway | Tus propios códigos PSE | ['nullable', 'array'] |
| advanced_options.pse_gateway.code | Código único de comercio en PSE | ['required_with:advanced_options.pse_gateway', 'string', 'min:3', 'max:15'] |
| advanced_options.pse_gateway.nit | NIT de tu comercio | ['required_with:advanced_options.pse_gateway', 'string', 'min:8', 'max:11'] |
Datos del pagador
Sección titulada «Datos del pagador»Objeto opcional que solo aplica cuando payment.checkout_type es redirect. Si lo
envías, los datos del pagador llegan precargados en el checkout y el pagador puede
modificarlos antes de pagar. Si no lo envías, el checkout pide los datos en blanco. Si
lo envías junto a checkout_type: api la solicitud es rechazada.
Todos los campos son opcionales: puedes enviar solo los que tengas (por ejemplo
únicamente name y email). Estos pares se exigen juntos: identification_type con
id_number, dialling_code con cellphone y country con state.
| Nombre del campo | Descripción | Reglas |
|---|---|---|
| customer_information | Datos del pagador para precargar el checkout. Prohibido cuando checkout_type es api |
['nullable', 'array', 'prohibited_unless:payment.checkout_type,redirect'] |
| customer_information.name | Nombre completo del pagador. No puede ser un número de tarjeta | ['nullable', 'string', 'min:5', 'max:255'] |
| customer_information.email | Correo del pagador | ['nullable', 'email:rfc,strict', 'max:255'] |
| customer_information.identification_type | Tipo de documento. Ver enumeraciones | ['nullable', 'required_with:customer_information.id_number', 'in:CC,CE,TI,PPT,DNI,NIT,Pasaporte,Otro'] |
| customer_information.id_number | Número de documento. El formato depende del tipo: CC 6–10 dígitos, TI 10–11, CE alfanumérico 6–15, PPT 7–15 dígitos, DNI alfanumérico 6–20, NIT 9–10 dígitos, Pasaporte alfanumérico 6–20, Otro máximo 30 | ['nullable', 'required_with:customer_information.identification_type'] |
| customer_information.dialling_code | Indicativo del país, con + |
['nullable', 'required_with:customer_information.cellphone', 'regex:/^\+\d{1,3}$/i'] |
| customer_information.cellphone | Celular, solo dígitos | ['nullable', 'required_with:customer_information.dialling_code', 'numeric', 'digits_between:5,15'] |
| customer_information.country | País en ISO3. Ver lista de países | ['nullable', 'required_with:customer_information.state', 'string', 'in:COL,USA,MEX,...'] |
| customer_information.state | Departamento. Si country es COL debe estar en la lista de departamentos; en otros países es texto libre |
['nullable', 'string', 'max:100'] |
| customer_information.city | Ciudad. Usa el nombre exacto de la lista de ciudades para que coincida con el checkout | ['nullable', 'string', 'max:100'] |
| customer_information.address_1 | Dirección de residencia. No puede ser un número de tarjeta | ['nullable', 'string', 'min:5', 'max:100'] |
Metadata
Sección titulada «Metadata»payment.metadata es un objeto de pares clave-valor donde guardas tus propias
referencias (id de pedido, de usuario, canal…), al estilo de Stripe. No lo usamos para
nada: te lo devolvemos.
| Límite | Valor |
|---|---|
| Claves | Hasta 50 |
| Formato de la clave | 1 a 40 caracteres, [A-Za-z0-9_-] |
| Valor | Texto o número, hasta 500 caracteres. Se guarda como texto (42 vuelve como "42") |
{ "payment": { "description": "Orden 12345", "amount": 20000, "currency_type": "COP", "checkout_type": "redirect", "metadata": { "order_id": "ORD-12345", "user_id": 42 } }, "office": 1}Lo recibes de vuelta en el webhook de la transacción,
en checkout.payment_referenceable.metadata. Un valor vacío ("" o null) no se
guarda.
Si no cumple los límites, la respuesta es un 422 con
error.code: "validation_failed" y error.param: "payment.metadata".
Idempotencia
Sección titulada «Idempotencia»La idempotencia es opcional. Si no envías el header, el endpoint se comporta igual que antes.
Sirve para que, si por un timeout, un doble clic o un reintento automático vuelves a
llamar generate-payment con las mismas referencias, la API no cree otro pago. En
su lugar responde con el pago ya generado.
| Header | Descripción | Reglas |
|---|---|---|
| X-Idempotency-Enabled | Activa la búsqueda de un pago previo con las mismas referencias | Opcional. Valores aceptados: true, 1, yes, on |
Cómo funciona
Sección titulada «Cómo funciona»- Envía
X-Idempotency-Enabled: true. - Envía
advanced_options.referencescon al menos una referencia (máximo 3). - La API busca, en el mismo comercio y ambiente (pruebas o producción), un pago generado en las últimas 24 horas con exactamente las mismas referencias y en el mismo orden.
- Si lo encuentra, responde ese mismo
payment_id(yurlsi el checkout esredirect). - Si no lo encuentra, crea el pago normalmente.
Respuestas al reintentar
Sección titulada «Respuestas al reintentar»| checkout_type | Primera respuesta | Reintento con la misma llave |
|---|---|---|
| redirect | saved, payment_id, url |
Mismo payment_id y misma url |
| api | saved, payment_id, token |
Solo saved y payment_id (el token original no se vuelve a devolver; guárdalo en la primera respuesta) |
Si otra solicitud con las mismas referencias se está procesando al mismo tiempo, la API
puede responder 429 para que reintentes unos segundos después.
Ejemplo con idempotencia
Sección titulada «Ejemplo con idempotencia»curl -X POST\"/api/v1/payment/generate-payment"\-H 'Authorization: Bearer ACCESS_TOKEN' \-H "Content-type: application/json" \-H "X-Idempotency-Enabled: true" \-d '{ "payment": { "description": "Orden 12345", "amount": 20000, "currency_type": "COP", "checkout_type": "api" }, "advanced_options": { "references": [ "ORD-12345", "FAC-67890" ] }, "office": 1}'Ejemplos
Sección titulada «Ejemplos»Solicitud en modalidad redirect
Sección titulada «Solicitud en modalidad redirect»curl -X POST\"/api/v1/payment/generate-payment"\-H 'Authorization: Bearer ACCESS_TOKEN' \-H "Content-type: application/json" \-d '{ "payment" : { "description": "Prueba Efipay", "amount": 20000, "currency_type": "COP", "checkout_type": "redirect" }, "advanced_options": { "picture": "https://mi-tienda.com/img/producto.png", "limit_date": "2030-12-24", "references": [ "123455678", "1234556789", "1234556780" ], "result_urls": { "approved": "https://mi-tienda.com/gracias", "rejected": "https://mi-tienda.com/error", "pending": "https://mi-tienda.com/procesando" }, "delivery_service": { "type": "Con Valor", "value": 2000 }, "request_address_delivery": true, "discount": { "before_on": "2030-12-07", "type": "value", "value": "2000" }, "has_comments": true, "comments_label": "Aqui tu comentario", "redeban_gateway": { "code": "12349876", "terminal": "DRA12335" } }, "customer_information": { "name": "Juan Pérez", "email": "juan@correo.com", "identification_type": "CC", "id_number": "1020304050", "dialling_code": "+57", "cellphone": "3001234567", "country": "COL", "state": "Antioquia", "city": "Medellín", "address_1": "Calle 10 #43-25" }, "office": 1}'Ejemplo de solicitud por api:
curl -X POST\"/api/v1/payment/generate-payment"\-H 'Authorization: Bearer ACCESS_TOKEN' \-H "Content-type: application/json" \-d '{ "payment" : { "description": "Ejemplo generar payment", "amount": 20000, "currency_type": "COP", "checkout_type": "api" }, "advanced_options": { "picture": "https://mi-tienda.com/img/producto.png", "limit_date": "2030-12-24", "references": [ "123455678", "1234556789", "1234556780" ], "result_urls": { "approved": "https://mi-tienda.com/gracias", "rejected": "https://mi-tienda.com/error", "pending": "https://mi-tienda.com/procesando" }, "delivery_service": { "type": "Con Valor", "value": 2000 }, "request_address_delivery": true, "discount": { "before_on": "2030-12-07", "type": "value", "value": "2000" }, "has_comments": true, "comments_label": "Aqui tu comentario", "redeban_gateway": { "code": "12349876", "terminal": "DRA12335" } }, "office": 1}'Respuestas
Sección titulada «Respuestas»Con checkout_type: redirect
Sección titulada «Con checkout_type: redirect»{ "saved": true, "payment_id": "9dc12b03-5833-496a-83e6-4dfb8eb2570b", "url": "https://sag.efipay.co/Checkout/PaymentGateway/9dc12b03-5833-496a-83e6-4dfb8eb2570b?signature=e7e33380957f87b98dab2353ccb7b3c5aed4387c44297fdefe03594b2ae17d79"}Redirige a tu cliente a url. La firma va incluida: no la modifiques ni le agregues
parámetros, o el link deja de ser válido. Si enviaste advanced_options.limit_date, el
link expira en esa fecha.
Con checkout_type: api
Sección titulada «Con checkout_type: api»{ "saved": true, "payment_id": "9dc12b26-dc55-474c-8602-5d9e00af129e", "token": "ZQZ82Ifn5fAuzKL"}No hay url: el par payment_id + token es lo que usas para
procesar el pago desde tu checkout.
Errores
Sección titulada «Errores»{ "payment.currency_type": ["El campo payment.currency_type es obligatorio."], "errors": { "payment.currency_type": ["El campo payment.currency_type es obligatorio."] }, "message": "El campo payment.currency_type es obligatorio.", "error": { "type": "invalid_request_error", "code": "validation_failed", "message": "El campo payment.currency_type es obligatorio.", "param": "payment.currency_type" }}{ "message": "Otra solicitud con las mismas referencias está siendo procesada, por favor intenta de nuevo.", "error": { "type": "rate_limit_error", "code": "too_many_requests", "message": "Otra solicitud con las mismas referencias está siendo procesada, por favor intenta de nuevo.", "param": null }}