QvaPay API
Mini-apps

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:

CampoEjemplo
Slugmi-tienda
NombreMi tienda
URL de entradahttps://mitienda.com
Orígenes permitidoshttps://mitienda.com
Datos que podrás pedirPerfil

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