Botón de pago
Pega dos líneas en tu web y cobra con «Pagar con QvaPay». Tu cliente paga en una ventana emergente y vuelve a tu web, que recibe el webhook firmado de siempre.
Cómo funciona
El botón abre el pago de QvaPay en una ventana emergente, sin que tu cliente salga de tu web. Al terminar la ventana se cierra sola, tu página recibe un evento y va a tu URL de Éxito. Tu servidor se entera por el mismo webhook firmado que una factura creada por API.
Hay dos formas de usarlo:
| Forma | Importe | Necesitas |
|---|---|---|
| Botón fijo | Siempre el mismo | Nada más que pegar el código |
| Factura | El que calcules en cada pedido | Un servidor que llame a POST /v2/create_invoice |
Quien paga necesita una cuenta de QvaPay con la identidad verificada y saldo. Si no ha iniciado sesión, entra dentro de la misma ventana y vuelve al pago.
Antes de empezar
- Crea una App en Mis Aplicaciones y déjala activa.
- Pon su Webhook URL: es la confirmación real de cada pago.
- Pon su URL de Éxito: a ella vuelve tu cliente después de pagar. Recibe
transaction_uuidyremote_iden la query.
Botón fijo
Crea el botón
Abre tu App en Mis Aplicaciones, ve a Botón de pago y escribe el nombre y el importe.
Pega el código en tu web
<div data-qvapay-button="BUTTON_UUID"></div>
<script src="https://www.qvapay.com/embed/v1/button.js" async></script>Puedes cambiar el importe o el nombre cuando quieras sin volver a pegar nada. Si desactivas un botón, deja de cobrar y tu cliente ve un aviso de que no está disponible.
Cada clic de un cliente con sesión crea una factura de la App que caduca pronto, a los 30 minutos por defecto, con remote_id BTN-<uuid del botón>-<aleatorio>. Cada factura se confirma por webhook como cualquier otra.
El botón fijo no sirve para casar un pago con un pedido concreto de tu sistema. Si necesitas eso, usa la forma con factura y tu propio remote_id.
Con factura
Crea la factura desde tu servidor y pon su transaction_uuid en la página:
curl -X POST "https://api.qvapay.com/v2/create_invoice" \
-H "app-id: TU_APP_ID" \
-H "app-secret: TU_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{ "amount": 25.00, "description": "Pedido 12345", "remote_id": "order-12345" }'<div data-qvapay-invoice="TRANSACTION_UUID"></div>
<script src="https://www.qvapay.com/embed/v1/button.js" async></script>Nunca llames a create_invoice desde el navegador: tu app-secret quedaría a la vista.
Confirmar el pago
Los eventos del navegador son para tu interfaz, nunca una prueba de pago: cualquiera puede dispararlos desde la consola. Entrega el producto solo al recibir el webhook firmado, o tras consultar POST /v2/transactions/{uuid}.
El webhook llega a tu Webhook URL con transaction_uuid, remote_id, status y amount en el cuerpo, y las cabeceras x-qvapay-signature y x-qvapay-timestamp. Consulta cómo verificar la firma HMAC.
Eventos en tu página
Los eventos salen del elemento del botón y suben hasta document:
| Evento | Cuándo | event.detail |
|---|---|---|
qvapay:paid | La factura está pagada | transaction_uuid, redirect |
qvapay:cancel | El cliente pulsó Cancelar | transaction_uuid |
qvapay:close | El cliente cerró la ventana sin pagar ni cancelar | Vacío |
Tras qvapay:paid, el script lleva a tu cliente a redirect, que es tu URL de Éxito. Para quedarte en la página, cancela el evento:
document.addEventListener('qvapay:paid', (event) => {
event.preventDefault()
mostrarGracias(event.detail.transaction_uuid)
})Con tu propio botón
window.QvaPayButton abre el pago sin usar nuestro botón. Llámalo dentro del clic del usuario: si esperas a una petición, el navegador bloquea la ventana.
miBoton.addEventListener('click', () => {
window.QvaPayButton.open({
invoice: 'TRANSACTION_UUID', // o button: 'BUTTON_UUID'
onPaid: (event) => console.log(event.detail.transaction_uuid),
onCancel: (event) => console.log('Canceló'),
onClose: (event) => console.log('Cerró la ventana'),
})
})En una aplicación de una sola página, los elementos que llegan después de cargar el script se activan con mount:
window.QvaPayButton.mount(elemento, { onPaid: (event) => console.log(event.detail) })| Método | Parámetros | Devuelve |
|---|---|---|
open(opciones) | invoice o button, y onPaid, onCancel, onClose opcionales | La ventana, o null si no se pudo abrir |
mount(elemento, opciones) | Un elemento con data-qvapay-invoice o data-qvapay-button, y las mismas funciones opcionales | Nada |
Casos especiales
| Caso | Qué pasa |
|---|---|
| El navegador bloquea la ventana, o tu web corre en un WebView | La misma pestaña va a la página de pago y vuelve por tu URL de Éxito. No hay eventos. |
| La factura ya estaba pagada | La ventana lo avisa con qvapay:paid y se cierra. |
| La factura caducó sin pagar | La ventana lo indica. Crea otra factura. |
| La App está en modo prueba | El botón fijo no cobra y lo indica. create_invoice en modo prueba devuelve la factura ya pagada, sin página de pago. |
Si tu web usa cabeceras de seguridad, necesita:
- Content-Security-Policy: añade
https://www.qvapay.comascript-src. - Cross-Origin-Opener-Policy: usa
same-origin-allow-popups. Consame-origintu página no puede hablar con la ventana yqvapay:closesalta al abrirla.
«Pagar» se habilita unos instantes después de abrir la ventana y otra vez cada vez que recupera el foco. Es una protección contra clics forzados: un clic que trae la ventana al frente no paga.
Gestionar botones por API
Las mismas operaciones del panel, con Bearer Token en el header Authorization. No aceptan credenciales de App ni tokens de alcance limitado.
GET /app/:uuid/pay-buttons
Lista los botones de la App.
{
"data": [
{
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "Curso de marketing",
"description": "Acceso de por vida",
"amount": "25.00",
"active": true,
"created_at": "2026-11-05T12:00:00.000Z",
"updated_at": "2026-11-05T12:00:00.000Z"
}
],
"limit": 20
}limit es el máximo de botones que admite cada App.
POST /app/:uuid/pay-buttons
Crea un botón. Devuelve 201 con { "data": { ... } }.
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Si | Nombre, hasta 80 caracteres |
description | string | No | Concepto de la factura, hasta 255 caracteres. Si falta se usa name |
amount | number | Si | Importe en USD, de 0.01 a 100,000, con 2 decimales como máximo |
PATCH /app/:uuid/pay-buttons/:button
Cambia name, description, amount o active. Solo los campos enviados. No hay borrado: un botón se desactiva con "active": false.
Errores
| Código | Descripción |
|---|---|
400 | Datos inválidos, o nada que actualizar |
403 | La cuenta dueña de la App debe completar su verificación (KYC_REQUIRED) |
404 | La App o el botón no existen, o no son tuyos |
409 | Llegaste al máximo de botones de esta App (PAY_BUTTON_LIMIT) |
503 | No se pudo guardar el botón. Inténtalo más tarde |
Integraciones y plugins
Conecta QvaPay con tu plataforma de e-commerce o CMS usando nuestros plugins oficiales y SDKs, sin escribir la integración desde cero.
WooCommerce
Plugin oficial de QvaPay para WooCommerce. Acepta pagos en USD desde el balance de QvaPay de tus clientes, con confirmación automática por webhook firmado.