Documentación

Cómo usar ViaPay hoy

Guía completa del producto demo: cobros non-custodial en Stellar, comisiones, links, redirect, split, checkout, webhooks, API, SDK y pagos de agentes (x402). Lo diferido (email, embed, plugins) no está aquí a propósito.

Qué es ViaPay

Cobros non-custodial en Stellar: el pagador firma, el dinero llega al instante a las wallets del comercio (y del partner si hay split). Sin custodiar claves ni fondos.

ViaPay es un checkout hosted más una API. Creás un cobro (payment intent), compartís el link o redirigís desde tu tienda, y el cliente paga con wallet Stellar o QR SEP-7. Un agente de IA puede pagar el mismo cobro por HTTP 402 (x402).

Demo diaria en testnet. La liquidación on-chain va por el contrato Soroban payment-router (pay() sobre SAC SEP-41): hasta tres patas + evento Paid. Mainnet vivo para evidencia (CA4FJAYS…); el panel puede crear cobros mainnet cuando la API tiene PAYMENT_ROUTER_CONTRACT_ID_MAINNET.

Sitio único: https://viapay.vercel.app (landing `/`, docs `/docs`, login `/login`, panel `/app`, checkout `/pay/[id]`, legal `/privacy` `/terms` `/data-deletion`). Local: panel+landing `:3000`. API `:3001`.

  • Sin custodia: ViaPay no guarda la clave del pagador.
  • El pagador ve solo el total en el checkout; el desglose vive en el panel y en la API.
  • Legal: política de privacidad, términos y eliminación de datos (Meta / WhatsApp).
  • Invoices: contacto, email o WhatsApp (panel + NL). Diferido: HSM/SMS, embed, plugins — docs/FUTURO.md.
  • Auditar claims: /evidence · GET /v1/verify · docs/VERIFY.md (sin API key).

Comisiones y split

Hasta tres patas en una sola transacción Stellar: ViaPay 1%, partner opcional, y el resto al comercio.

ViaPay cobra siempre 1% (100 basis points). Ese porcentaje lo fija el servidor (FEE_BPS); el body de la API no puede bajarlo ni quitarlo.

Si hay marketplace o afiliado, pasás reseller_fee_bps (ej. 700 = 7%) y reseller_address (cuenta G…). El comercio recibe el neto. ViaPay + revendedor deben sumar menos del 100%.

Ejemplo Hubby: curso de $20 → ~$0,20 a ViaPay, ~$1,40 al partner (7%), ~$18,40 al creador. Las tres patas siempre suman el total (redondeo hacia abajo en fee/partner; el comercio absorbe el resto).

  • Sin reseller: 99% comercio + 1% ViaPay.
  • Con reseller: (100 − 1 − partner)% comercio + 1% ViaPay + partner%.
  • Modo agente simple: mismo split 99/1. Modo agente con partner: ej. 96/1/3.

La matemática está en calcFeeSplit (packages/shared). Un XDR firmado que no coincida con las patas esperadas se rechaza antes de Horizon.

Primer cobro en dos minutos

Sin código: panel → crear cobro → copiar link → pagar en testnet.

En local necesitás el monorepo corriendo (pnpm) y el seed con una API key de prueba (sk_test_…). En producción el panel vive junto al sitio; el login puede ser local o OAuth vía Supabase si está configurado.

  1. Entrá al panelAbrí el dashboard y usá “Continuar en local” (o el login configurado). Vas a ver tus cobros y wallets de destino.
  2. Creá un cobroIndicá monto, asset (XLM o USDC testnet) y descripción opcional. Si querés split, agregá bps y dirección del partner.
  3. Compartí el checkoutCopiá el checkout_url. El cliente abre ese link, ve el total y paga con Freighter, Lobstr, xBull, Pollar (si hay key) o QR móvil.
  4. Confirmá el pagoAl firmar y enviar, la API valida destinos/montos/memo y marca succeeded. También podés reconciliar mirando Horizon si la wallet no llamó al callback.

Ecommerce con redirect

El mínimo viable para tu plataforma: creás el cobro por API, mandás al comprador al checkout ViaPay y lo recibís de vuelta en success_url / cancel_url.

Flujo: carrito → POST /v1/payment_intents (o SDK createCheckout) con amount, asset, success_url y cancel_url → redirigís a checkout_url → tras pagar, ViaPay vuelve a success_url con payment_intent y tx_hash. Si cancela, va a cancel_url.

Auth del comercio: Authorization: Bearer sk_test_…. API local: http://localhost:3001. Checkout: :3004.

Referencia lista para probar: apps/shop (puerto 3005). Es una tienda de prueba que implementa exactamente este flujo.

  1. Crear el intentPOST /v1/payment_intents con amount (string Stellar, ej. "20.0000000"), asset, description opcional, success_url, cancel_url y, si aplica, reseller_*.
  2. Redirigir al humanoUsá el checkout_url de la respuesta (window.location o 302). Guardá id si vas a consultar estado o esperar webhook.
  3. Cerrar el pedidoEn success_url leé payment_intent / tx_hash. En producción, confirmá también con el webhook payment_intent.succeeded.

Respuesta útil: id, client_secret, checkout_url, fee_amount, net_amount, reseller_amount. Demo local: pnpm --filter @viapay/shop dev

Split / marketplace

Un solo pago on-chain reparte a comercio, ViaPay y partner (ej. Hubby).

Pasá reseller_fee_bps y reseller_address al crear el payment intent. No hace falta un segundo cobro ni un batch aparte.

Caso curso $20 con partner 7%: ~0,20 ViaPay + ~1,40 partner + ~18,40 creador. En onchain el mismo split lo ejecuta payment-router (una invocación Soroban + evento Paid).

  • El pagador no ve el desglose en el checkout (solo el total).
  • El panel y la API sí muestran fee, reseller y neto.
  • Submit rechaza cualquier XDR que no sea la invocación al router con las patas esperadas.

Pagos de agentes (x402)

El mismo payment intent tiene otra puerta: GET responde 402; el agente firma y POST liquida.

GET /v1/x402/:id?client_secret=… → HTTP 402 con accepts[] (forma PaymentRequirements x402 v2) más un bloque viapay con desglose (rol, dirección, monto, bps) y URLs para liquidar.

POST al mismo recurso con header X-PAYMENT (base64 JSON con payload.signed_xdr) o signed_xdr en el body → 200 y X-PAYMENT-RESPONSE. Con {"reconcile": true} busca un pago ya hecho en Horizon.

No hay facilitator x402 aparte: el agente firma la transacción completa (mismas patas que el checkout humano, vía payment-router). Demo: examples/agent-pay.mjs.

  • Modo simple: 99% comercio / 1% ViaPay.
  • Modo con partner: ej. 96 / 1 / 3 (configurable con reseller_*).
  • Sin AGENT_SECRET_KEY el script de demo imprime el 402 y para.
GET  /v1/x402/:id?client_secret=…  → 402 + accepts[] + viapay.breakdown
POST /v1/x402/:id
  Header: X-PAYMENT: <base64 JSON { payload: { signed_xdr } }>
  → 200 + X-PAYMENT-RESPONSE

# Demo: node examples/agent-pay.mjs

Checkout hosted (humano)

Endpoints que usa la UI del pagador — y que podés reutilizar si armás tu propio cliente.

GET /v1/checkout/:id?client_secret= — estado público, sep7_tx, y si las cuentas pueden recibir el asset.

POST /v1/checkout/:id/prepare — arma el XDR sin firmar (source del pagador).

POST /v1/checkout/:id/submit — envía el XDR firmado; la API verifica y manda a Horizon.

POST /v1/checkout/:id/sep7 — callback de wallet móvil (form x-www-form-urlencoded, campo xdr).

confirm solo existe en STELLAR_MODE=simulated. En on-chain responde 400.

Webhooks

Cuando el cobro pasa a succeeded, ViaPay avisa a tus endpoints con firma HMAC.

Alta: POST /v1/webhook_endpoints. El secreto se muestra una sola vez; guardalo. Lista y baja lógica con GET/DELETE.

Header: ViaPay-Signature: t=<unix>,v1=<hex hmac-sha256>. Mensaje firmado: `${t}.${rawBody}`. Ventana de 5 minutos. Hasta 5 reintentos. Localhost http permitido; el resto exige https.

Evento principal: payment_intent.succeeded. El SDK verifica con ViaPay.verifyWebhook(rawBody, header, secret).

  • GET /v1/webhook_deliveries lista y reintenta entregas vencidas.
  • Usá webhooks para marcar pedidos pagados sin depender solo del redirect.

Verificar settlement (público)

Cualquiera re-chequea un pay() on-chain sin API key. Copiá el curl.

GET /v1/verify?network=mainnet&tx_hash=… decodifica el envelope Soroban y devuelve verified, patas, contrato y explorer.

GET /v1/rails es el manifest máquina (primitive payment_intent, human/agent, MCP, contracts). UI: /evidence. CLI: pnpm verify.

Kit completo para IAs/jurado: docs/VERIFY.md en el repo. Expect mínimo: verified true, contract CA4FJAYS…, event Paid, net 0.099, fee 0.001.

  • curl -sS 'https://viapay-api.vercel.app/v1/verify?network=mainnet&tx_hash=b28aafbdce81e0b01e9cb3d2e3d0c037d3f5742a4d7a1b557612d6e12028380e'
  • networks.mainnet.ready false ≠ 'no hay contrato': es panel env; la evidencia on-chain sí está.

Límites honestos en /evidence y docs/VERIFY.md.

Crear un cobro por API

POST /v1/payment_intents con tu API key. La respuesta trae checkout_url listo para redirigir.

Autenticación: Authorization: Bearer sk_test_…. CORS abierto en /v1/*.

Campos frecuentes: amount, asset (USDC | XLM), description, success_url, cancel_url, reseller_fee_bps, reseller_address.

También: GET /v1/payment_intents (lista y reconcilia pendientes en Horizon), GET /v1/health, GET /v1/readiness (Friendbot, trustlines, fee_bps, resellers), GET /v1/verify, GET /v1/rails.

curl -X POST $VIAPAY_API/v1/payment_intents \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "20.0000000",
    "asset": "USDC",
    "external_user_id": "user_42",
    "success_url": "https://tu-tienda.example/gracias",
    "cancel_url": "https://tu-tienda.example/carrito",
    "reseller_fee_bps": 700,
    "reseller_address": "G…"
  }'

OpenAPI en el repo: docs/openapi.yaml. Guía larga: docs/INTEGRATION.md.

SDK JavaScript

Paquete @viapay/sdk: createPaymentLink, createCheckout, verifyWebhook y helpers x402.

Instanciá ViaPay con apiKey (secreta sk_…) y baseUrl. createPaymentLink crea un cobro único con externalUserId y te devuelve url (/pay/…), id y secret. createCheckout es el equivalente snake_case.

Para agentes: parseX402Challenge sobre el JSON del 402, y encodePaymentHeader con el signed_xdr.

import { ViaPay } from "@viapay/sdk";

const via = new ViaPay({ apiKey, baseUrl });
const link = await via.createPaymentLink({
  amount: "20",
  asset: "USDC",
  externalUserId: "user_42",
  successUrl: "https://tu-tienda.example/gracias",
  resellerFeeBps: 700,
  resellerAddress: "G…",
});
// window.location = link.url;

const ok = await ViaPay.verifyWebhook(rawBody, signatureHeader, secret);

Redes y readiness

Demo en testnet; mainnet para evidencia y cobros opt-in.

USDC testnet (Circle): GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5. Mainnet USDC: GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN. Tesorería: VIAPAY_TREASURY_ADDRESS.

payment-router testnet CDI6XC5Q… · mainnet CA4FJAYS… (mismo wasm 2ef55539…). GET /v1/health → networks + seps.

GET /v1/readiness reporta Friendbot, trustline, fee_bps y resellers. Cada cobro elige network (testnet|mainnet).

  • Wallets: Freighter, Lobstr, xBull (red del cobro).
  • Prioridad: docs/AHORA.md. Diferido: docs/FUTURO.md.