Generar un pago


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:

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

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, token o URL de checkout después del primer intento responde 403 con el mensaje Este cobro ya tiene una transacción y no permite reintentos.

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 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.

Elige tu modalidad

redirect api
Quién captura el medio de pago Nosotros
Qué recibes url token
Peticiones para cobrar 1 2
Requiere certificación PCI DSS No
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 a api después no obliga a rehacer nada de este paso: solo cambia el valor de checkout_type.

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.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_taxes y tax_amount son 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.

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

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-departments y /api/v1/resources/get-cities/{department}. Un departamento que no esté en esa lista rechaza la solicitud.

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

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

  1. Envía X-Idempotency-Enabled: true.
  2. Envía advanced_options.references con al menos una referencia (máximo 3).
  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.
  4. Si lo encuentra, responde ese mismo payment_id (y url si el checkout es redirect).
  5. Si no lo encuentra, crea el pago normalmente.

{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.

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

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

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

Con 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.

Con 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 token se devuelve una sola vez. Guárdalo junto al payment_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 incluye saved y payment_id. El token solo se entrega en la primera creación; guárdalo del lado del comercio.

Errores

{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."
}