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 → un url al que rediriges a tu cliente. Nosotros capturamos el pago.api → un token con 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
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).
{warning} Reusar el mismo
payment_id,tokeno URL de checkout después del primer intento responde 403 con el mensajeEste cobro ya tiene una transacción y no permite reintentos.
Sí consume el intento
No consume el intento
422 de 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.
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 |
{success} Si estás empezando, usa
redirect. Cambiar aapidespués no obliga a rehacer nada de este paso: solo cambia el valor decheckout_type.
| 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.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'] |
{warning}
selected_taxesytax_amountson excluyentes: o nos dices qué impuestos aplicar, o nos das el valor ya calculado. Enviar los dos da error de validación.
{danger} Si omites
payment.currency_type, la validación se detiene ahí y no verás los demás errores: los límites de monto dependen de la moneda. Corrígelo y vuelve a enviar.
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'] |
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'] |
{info} Departamento y ciudad deben coincidir con
/api/v1/resources/get-departmentsy/api/v1/resources/get-cities/{department}. Un departamento que no esté en esa lista rechaza la solicitud.
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 |
X-Idempotency-Enabled: true.advanced_options.references con al menos una referencia (máximo 3).payment_id (y url si el checkout es
redirect).{info} Sin el header, las referencias solo sirven para trazabilidad y consultas. No evitan un cobro o pago duplicado.
{warning} El conjunto completo de referencias es la llave.
["ORD-1"]y["ORD-1", "EXTRA"]se tratan como operaciones distintas. Pasadas 24 horas, la misma llave puede crear un pago nuevo.
| 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.
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
}'
redirectcurl -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
}'
checkout_type: redirect{success} Respuesta satisfactoria code:
200 { "saved": true, "payment_id": "9dc12b03-5833-496a-83e6-4dfb8eb2570b", "url": "https://sag-qa.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.
checkout_type: api{success} Respuesta satisfactoria code:
200 { "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.
{danger} El
tokense devuelve una sola vez. Guárdalo junto alpayment_id; en nuestra base solo queda su hash. Si lo pierdes, genera un cobro nuevo.
{info} Si reintentas con idempotencia y
checkout_type: api, la respuesta solo incluyesavedypayment_id. Eltokensolo se entrega en la primera creación; guárdalo del lado del comercio.
{danger} Validación code:
422 { "message": "El campo payment.currency_type es obligatorio.", "errors": { "payment.currency_type": ["El campo payment.currency_type es obligatorio."] } }
{warning} Si omites
payment.currency_type, es el único error que verás: sin la moneda no se pueden aplicar las demás reglas (los límites de monto dependen de ella). Corrígelo y vuelve a enviar para ver el resto.
{danger} Conflicto de idempotencia code:
429 { "error": "Otra solicitud con las mismas referencias está siendo procesada, por favor intenta de nuevo." }