Cobrar con QR en punto de venta (monto variable)

Con un QR estático de monto cerrado tenés un único QR impreso por caja y cobrás un monto distinto en cada venta. El monto no está en el QR: lo guarda PAGOS360. Antes de cada cobro, activás un monto con un tiempo de vigencia. Cuando el cliente escanea el QR con cualquier billetera o app bancaria, ve ese monto y lo paga.

En esta guía vas a ver el flujo completo: crear el QR de la caja una sola vez, activar un monto por venta, confirmar el pago y pasar a la venta siguiente.

Importante

todas las llamadas al módulo QR usan tu API Key (Authorization: Bearer <API Key>). Hacelas siempre desde tu servidor, nunca desde el navegador ni desde la app de la caja. Más información sobre el QR interoperable (el QR que se paga con cualquier billetera o app bancaria) en QR Interoperable.

Cómo funciona

  • Un QR por caja. Lo creás una vez y lo imprimís o lo mostrás en una pantalla. La imagen no cambia nunca.

  • Cada monto es un cobro nuevo. Cada vez que activás un monto se genera un cobro con su propio qr_id. El QR impreso es el mismo.

  • El monto vence. Cada monto tiene una vigencia en minutos. Cuando vence, activá otro monto para volver a cobrar.

  • Siempre vale el último monto. Si activás un monto nuevo, los próximos escaneos ven ese monto y ya no el anterior.

sequenceDiagram autonumber participant C as Caja participant S as Tu servidor participant P as PAGOS360 (API + módulo QR) participant B as Billetera del cliente Note over S,P: Una sola vez por caja S->>P: POST /create-qr (sin monto) P-->>S: Imagen del QR S->>P: GET /status/{qr_id} P-->>S: string del QR S-->>C: QR para imprimir o mostrar loop Cada venta C->>S: Monto de la venta S->>P: PUT /new-transaction?data={string} P-->>S: qr_id del cobro B->>P: El cliente escanea el QR P-->>B: Monto a pagar (si no venció) B->>P: Pago P-->>S: Webhook payment_request / paid S->>P: GET /status/{qr_id del cobro} P-->>S: status: Paid S-->>C: Venta cobrada end

Los ejemplos usan el entorno de pruebas: https://qr.sandbox.pagos360.com. En producción, el host es https://qr.pagos360.com.

Paso 1: crear el QR de la caja

Hacelo una sola vez por caja o punto de venta, con POST /create-qr y qr_type: static_closed_amount. No lleva monto: el monto lo activás en cada venta.

curl --request POST 'https://qr.sandbox.pagos360.com/create-qr' \ --header 'Authorization: Bearer <API Key>' \ --header 'Content-Type: application/json' \ --data '{ "id": "<account_id>-CAJA01", "qr_type": "static_closed_amount", "qr_name": "CAJA01", "multiple_payment": false, "city": "CORDOBA", "postal_code": "X5000ABC", "description": "Caja 1 - Sucursal Centro", "payer_name": "Cliente mostrador" }'

Qué valida el módulo:

Campo

Regla

id

Obligatorio. Hasta 25 caracteres y tiene que empezar con el ID de tu cuenta seguido de un guion (<account_id>-...). Tiene que ser único.

qr_type

static_closed_amount para este flujo. Si no lo enviás, se crea un QR dinámico.

qr_name

Obligatorio en QR estáticos. Solo letras y números, de 1 a 15 caracteres.

city

Obligatorio, hasta 15 caracteres. Se guarda en mayúsculas.

postal_code

Obligatorio, exactamente 8 caracteres.

multiple_payment

Obligatorio, booleano.

description

Obligatorio, de 2 a 500 caracteres.

payer_name

Obligatorio, hasta 255 caracteres.

payer_email, external_reference

Opcionales.

Enviá solo los campos documentados, sin strings vacíos.

Importante

usá un qr_name distinto para cada caja. El contenido del QR estático se arma con el CUIT de la cuenta, el qr_name y la ciudad, pero no con el id. Si dos QR de la misma cuenta tienen el mismo qr_name y la misma ciudad, generan el mismo contenido y son el mismo QR.

Respuesta. Si el pedido es correcto, la respuesta es HTML (no JSON) con la imagen del QR en un <img>. Si enviás "add_template": true, devuelve la imagen dentro de una plantilla lista para imprimir.

<img src=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...>

Errores posibles:

HTTP

Cuerpo

Motivo

400

{"errors": {"code": 400, "description": {...}}}

Algún campo no pasó la validación. description detalla el campo.

400

{"errors": {"code": 409, "description": "ID must be unique."}}

Ya existe un QR con ese id.

200

[{"code": "H001", "message": "Unauthorized"}]

API Key inválida o ausente.

Paso 2: obtener el contenido del QR

Para activar montos necesitás el string del QR: el contenido codificado en la imagen, en formato EMVCo (el estándar internacional de pagos con QR). Consultalo con GET /status/{qr_id} y guardalo junto a la caja. No cambia.

curl 'https://qr.sandbox.pagos360.com/status/<account_id>-CAJA01' \ --header 'Authorization: Bearer <API Key>'
{ "id": "<account_id>-CAJA01", "description": "Caja 1 - Sucursal Centro", "status": "Pending", "multiple_payment": false, "payer_name": "Cliente mostrador", "string": "00020101021243...6304AB12" }
Tip

podés usar la imagen que devuelve create-qr o generar la tuya a partir del string con cualquier librería de códigos QR. El contenido es el mismo, así que la imagen impresa sirve para siempre.

Paso 3: activar el monto de cada venta

Cuando la caja tiene el total de la venta, tu servidor llama a PUT /new-transaction con el string del QR en el parámetro data (codificado para URL) y el monto en el cuerpo.

curl --request PUT 'https://qr.sandbox.pagos360.com/new-transaction' \ --url-query "data=$QR_STRING" \ --header 'Authorization: Bearer <API Key>' \ --header 'Content-Type: application/json' \ --data '{ "first_total": 1500.50, "active_period": 3, "external_reference": "CAJA01-000123" }'
Tip

--url-query codifica el string por vos (curl 7.87 o posterior). Si usás otra herramienta, codificá el valor de data para URL: el string puede traer espacios u otros caracteres del nombre del comercio.

Campo

Tipo

Regla

first_total

Número

Obligatorio. Mínimo 10, hasta 2 decimales y hasta 8 dígitos enteros. Envialo como número, no como texto.

active_period

Número

Opcional. Minutos de vigencia del monto, de 1 a 90. Si no lo enviás, son 2 minutos.

external_reference

String

Opcional, de 1 a 255 caracteres. Si no lo enviás, se usa la referencia del QR de la caja.

Respuesta:

{ "code": 200, "message": "Ok", "qr_id": "<account_id>-4C7D10E2B98A6F35" }

El qr_id que devuelve identifica este cobro, no la caja. Guardalo junto a la venta: lo vas a usar para consultar el estado.

Errores posibles:

HTTP

Cuerpo

Motivo

400

{"errors": {"code": 400, "description": ...}}

Falta data o algún campo no pasó la validación.

404

{"errors": {"code": 404, "description": "Not Found"}}

No existe un QR con ese string.

409

{"errors": {"code": 409, "description": "Invalid or non-existent QR"}}

El QR no es static_closed_amount.

401

{"errors": {"code": 401, "description": "Unauthorized"}}

El QR es de otra cuenta.

200

[{"code": "H001", "message": "Unauthorized"}]

API Key inválida o ausente.

Más detalle en Generar nueva transacción.

Paso 4: el cliente escanea y paga

El cliente escanea el QR de la caja con cualquier billetera o app bancaria compatible con QR interoperable. La billetera le muestra el monto activo y el cliente confirma el pago. Este paso lo resuelven el cliente y su billetera: tu sistema espera la confirmación.

Qué ve el cliente según el momento en que escanea:

Situación

Resultado

Hay un monto activo y no venció

La billetera muestra el monto y permite pagarlo.

Todavía no activaste ningún monto

El QR no se puede pagar.

El monto venció

El QR no se puede pagar hasta que actives otro.

El último monto ya se pagó

El QR no se puede pagar hasta que actives otro.

Paso 5: confirmar el pago

Tenés dos formas de enterarte. Usá las dos.

Webhook

Cuando se aprueba el pago, PAGOS360 registra una solicitud de pago pagada y envía el webhook paid de la entidad payment_request a la URL que configuraste. Ver Webhooks.

{ "entity_name": "payment_request", "type": "paid", "entity_id": 816, "payload": { "id": "816", "request_result_id": "546", "external_reference": "CAJA01-000123" } }

Enviá en cada new-transaction un external_reference único para la venta (por ejemplo, caja + número de ticket). El webhook trae ese external_reference, pero no el qr_id del cobro: con la referencia identificás qué venta se pagó.

Consulta de estado

Consultá el cobro con su qr_id (el que devolvió new-transaction, no el de la caja):

curl 'https://qr.sandbox.pagos360.com/status/<account_id>-4C7D10E2B98A6F35' \ --header 'Authorization: Bearer <API Key>'
{ "id": "<account_id>-4C7D10E2B98A6F35", "first_total": 1500.5, "external_reference": "CAJA01-000123", "status": "Paid", "paid_at": "26-09-2026", "string": "00020101021243...6304AB12" }

Estados posibles: Pending, Paid, Refunded y Deleted. paid_at trae solo la fecha (DD-MM-AAAA), sin hora. Si el qr_id no existe, la respuesta es [{"code": "F004", ...}]. Ver Consultar QR estático.

Importante

controlá el vencimiento desde tu sistema. El monto vence active_period minutos después de la llamada a new-transaction. No existe un estado "vencido": un cobro cuyo monto venció, o que fue reemplazado por un monto nuevo, sigue figurando como Pending.

Si consultás el estado periódicamente mientras la caja espera, hacelo cada pocos segundos. Cortá cuando pase el vencimiento (con un margen) o cuando llegue el webhook.

Paso 6: la venta siguiente

Para cobrar otra venta, volvé al paso 3 con el nuevo monto. El QR impreso es el mismo: cambia el cobro que tiene detrás.

Si activás un monto nuevo antes de que se pague el anterior, el nuevo lo reemplaza para los próximos escaneos. El cobro anterior no se cancela: queda en Pending en la consulta de estado. Conciliá siempre por external_reference o qr_id, sin asumir que el cobro pagado es el último: un cliente que escaneó el QR antes del reemplazo puede pagar el cobro anterior.

Si el cliente desiste y querés anular el monto activo antes de que venza, eliminá ese cobro con DELETE /delete-qr/{qr_id del cobro}. Podés eliminarlo mientras esté en Pending.

curl --request DELETE 'https://qr.sandbox.pagos360.com/delete-qr/<account_id>-4C7D10E2B98A6F35' \ --header 'Authorization: Bearer <API Key>'
[{ "code": "H005", "message": "Ok" }]

Si el cobro no está en Pending, la respuesta es [{"code": "F005", ...}]. Ver Eliminar QR.

Buenas prácticas

  • La API Key, solo en tu servidor. La caja o el navegador hablan con tu backend; tu backend habla con PAGOS360.

  • Creá el QR una vez por caja y guardá su id y su string. No crees un QR nuevo por venta.

  • Una referencia única por venta. Enviá external_reference en cada new-transaction para cruzar el webhook con la venta.

  • Ante un timeout, verificá antes de reintentar. La operación puede haberse completado igual. Si create-qr da timeout, consultá GET /status/{id} antes de reintentar (un reintento con el mismo id responde "ID must be unique."). Si new-transaction da timeout y reintentás, el reintento genera otro cobro que reemplaza al anterior, y el qr_id del primero se pierde. Ahí la referencia única te permite identificar la venta.

  • El reloj que manda es el de PAGOS360. Un contador en la pantalla de la caja sirve de guía, pero la vigencia real la controla el módulo.

  • Limpieza. Los QR y los cobros no se borran solos. Eliminá los QR de prueba que ya no uses con DELETE /delete-qr/{qr_id} (funciona con los que están en Pending).

Probar en sandbox

Usá https://qr.sandbox.pagos360.com (el entorno de pruebas) con la API Key de tu cuenta de sandbox. Para simular el pago sin una billetera real, usá el endpoint Pagar QR (solo para homologación), disponible solo en sandbox.