Inicio rápido
De cero a una mini-app con login y cobros, funcionando en el simulador.
Vas a construir una mini-app mínima: un botón Entrar con QvaPay y un botón Pagar $1. Usamos Node.js con Express para el servidor, pero cualquier lenguaje sirve: solo hacen falta tres rutas.
Crea tu App
En el panel de desarrollador, pulsa Crear aplicación. Guarda dos datos:
app-id: el UUID de la App.app-secret: solo se muestra una vez. Guárdalo en una variable de entorno de tu servidor.
En la App, configura el Callback con una URL https de tu servidor, por ejemplo https://mitienda.com/qvapay/webhook. Ahí te avisaremos de cada pago.
Mientras desarrollas, activa Modo prueba en la App: ninguna factura moverá dinero real.
Escribe la página
Crea index.html. El script sdk-sim.js solo sirve para el simulador: dentro de la app real y en un navegador normal no hace nada, así que puedes dejarlo en producción.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mi tienda</title>
<script src="https://www.qvapay.com/miniapps/sdk-sim.js"></script>
</head>
<body>
<h1 id="hola">Mi tienda</h1>
<button id="entrar">Entrar con QvaPay</button>
<button id="pagar">Pagar $1</button>
<script>
const Q = window.QvaPay
if (!Q) {
document.body.textContent = 'Abre esta página desde la app de QvaPay.'
} else {
Q.ready() // oculta el loader de la app
document.getElementById('entrar').onclick = async () => {
const { init_data } = await Q.auth.requestLogin({ scopes: ['profile'] })
const res = await fetch('/api/login', { method: 'POST', body: init_data })
const { username } = await res.json()
document.getElementById('hola').textContent = `Hola, @${username}`
}
document.getElementById('pagar').onclick = async () => {
const { invoice_uuid } = await fetch('/api/orders', { method: 'POST' }).then(r => r.json())
try {
await Q.payments.payInvoice(invoice_uuid)
Q.ui.toast('¡Pagado!', { type: 'success' })
} catch (e) {
if (e.code !== 'USER_CANCELLED') Q.ui.toast(e.message, { type: 'error' })
}
}
}
</script>
</body>
</html>Escribe el servidor
Tres rutas: verificar el login, crear la factura y recibir el webhook. verifyQvaPayInitData está completa en Identidad.
import express from 'express'
import crypto from 'node:crypto'
import { verifyQvaPayInitData } from './qvapay.js'
const { APP_ID, APP_SECRET } = process.env
const app = express()
app.use(express.static('public'))
// 1. Login: el init_data llega tal cual desde el frontend
app.post('/api/login', express.text({ type: '*/*' }), (req, res) => {
try {
const { user } = verifyQvaPayInitData(req.body, APP_SECRET, APP_ID)
// Aquí creas TU sesión para user.uuid
res.json({ username: user.username })
} catch {
res.status(401).json({ error: 'Firma inválida' })
}
})
// 2. Pedido: la factura se crea en el servidor, con tus credenciales
app.post('/api/orders', async (req, res) => {
const r = await fetch('https://api.qvapay.com/v2/create_invoice', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'app-id': APP_ID, 'app-secret': APP_SECRET },
body: JSON.stringify({ amount: 1, description: 'Pedido de prueba', remote_id: `order-${Date.now()}` }),
})
const invoice = await r.json()
res.json({ invoice_uuid: invoice.transaction_uuid })
})
// 3. Webhook: la ÚNICA fuente de verdad de un pago
app.post('/qvapay/webhook', express.raw({ type: '*/*' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', APP_SECRET).update(req.body).digest('hex')
const received = req.get('x-qvapay-signature') || ''
const valid = expected.length === received.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
if (!valid) return res.status(401).end()
const event = JSON.parse(req.body)
if (event.status === 'paid') {
// Marca el pedido event.remote_id como pagado y entrégalo
}
res.json({ ok: true })
})
app.listen(3001)Configura la mini-app
En tu App del panel de desarrollador, en la sección Mini-app, pestaña Configuración:
| Campo | Ejemplo |
|---|---|
| Slug | mi-tienda |
| Nombre | Mi tienda |
| URL de entrada | https://mitienda.com |
| Orígenes permitidos | https://mitienda.com |
| Datos que podrás pedir | Perfil |
Guarda. Tu mini-app queda en Borrador: solo tú la ves.
Pruébala en el simulador
Abre la pestaña Simulador. Mientras desarrollas, escribe tu servidor local en URL de prueba (por ejemplo http://localhost:3001) y pulsa Abrir.
- Entrar con QvaPay te pide permiso y devuelve un
init_datafirmado de verdad: tu servidor lo verifica igual que en producción. - Pagar $1 abre la hoja de pago. Con el Modo prueba activo la factura es de prueba: se paga sin dinero real y tu webhook recibe el aviso.
La consola del puente muestra cada llamada a window.QvaPay y su respuesta.
Pruébala en tu móvil
Abre la app de QvaPay con tu cuenta y entra en Mini-apps. Tu borrador aparece solo para ti, como vista previa. Aquí la URL de entrada debe ser https y estar desplegada.
Envíala a revisión
Cuando todo funcione, desactiva el Modo prueba de la App y, en la pestaña Resumen, completa la Lista para publicar y pulsa Enviar a revisión. El estado cambia a Publicada en tu panel cuando la aprobemos. Ver Publicar.