E-commerce · muchos pagadores
PIX en el checkout, stablecoins en la tesorería
Cada comprador paga un PIX común en su banco. La tienda recibe stablecoin en una billetera corporativa. Como el límite es del CPF del que paga, diez compradores representan diez límites independientes.
A quién va dirigido
- Tiendas o marketplaces B2C que venden en Brasil y quieren mantener su tesorería en stablecoins.
- Operaciones con muchos pagadores distintos y un importe medio de compra bajo o medio.
- Quienes hoy concilian PIX manualmente y quieren que un pedido pagado genere un evento en el sistema.
El problema
El temor de cualquier comercio electrónico que liquida en criptomonedas es el límite máximo: «¿Y si el volumen del día supera el límite de la cuenta?». Esta pregunta parte de una idea equivocada del producto. El límite que rige aquí es el del documento del que paga, y se aplica por operación: la cuenta receptora no tiene un límite diario propio que sume todo.
El segundo problema es la conciliación. Un PIX enviado a una clave fija llega sin una referencia de pedido: ves el importe y no sabes a qué pedido corresponde. Aquí cada solicitud de cobro se vincula al CPF del comprador y a tu external_id, y un PIX pagado por otro documento se devuelve en lugar de convertirse en un crédito aislado.
El flujo de principio a fin
- En el checkout, llamas a
POST /cashin/chargecon el total, el CPF del comprador y el número del pedido enexternal_id. - Muestra el
qr_copypasteen la pantalla. Es válido durante 15 minutos: genéralo al pagar, como hacen Mercado Pago y los bancos. - El comprador realiza el pago. Llega
cashin.paid: es la señal para liberar el pedido. - Las stablecoins llegan a la billetera de la empresa y se recibe
cashin.settledcon el hash. - Conciliación:
GET /cashin/charges?external_id=devuelve siempre la solicitud de cobro de ese pedido.
Endpoints utilizados
| Endpoint | Para qué |
|---|---|
POST /cashin/charge | Una solicitud de cobro por pedido, con el CPF del comprador y tu número de pedido. |
GET /cashin/limits?payer_tax= | Cuánto puede pagar ese comprador ahora; útil para importes altos. |
GET /cashin/charges | Lista con filtro por external_id, estado y período: es la conciliación. |
POST /webhooks/test | Prueba tu URL de webhook antes de que la primera venta dependa de ella. |
GET /cashin/events | Flujo de eventos operativos de tu clave para una pantalla interna, sin guardar estado. |
Ejemplo ejecutable
# un pedido = una solicitud de cobro, identificada con el CPF del comprador
curl -X POST https://api.luniumpay.com/cashin/charge -H "X-API-Key: $LUNIUM_KEY" \
-H "Content-Type: application/json" \
-d '{"amount_cents":34990,"asset":"usdt","chain":"polygon",
"payout_address":"0xCarteiraDaEmpresa",
"payer_tax_number":"12345678909",
"payer_name":"Maria S.",
"external_id":"pedido-2026-33871"}'// checkout: genera el QR cuando el comprador va a pagar
app.post("/checkout/:pedido/pix", async (req, res) => {
const { total_cents, cpf, nome } = req.body;
const r = await fetch(`${API}/cashin/charge`, {
method: "POST",
headers: { "X-API-Key": process.env.LUNIUM_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
amount_cents: total_cents, asset: "usdt", chain: "polygon",
payout_address: process.env.CARTEIRA_EMPRESA,
payer_tax_number: cpf, payer_name: nome,
external_id: `pedido-${req.params.pedido}`,
}),
});
const c = await r.json();
if (!r.ok) return res.status(r.status).json({ erro: c.erro, detalhe: c.detail });
res.json({ copiaECola: c.qr_copypaste, expiraEm: c.expires_at });
});
// webhook: marca el pedido como pagado solo después de verificar la firma
app.post("/webhooks/lunium", verificarAssinatura, async (req, res) => {
if (req.body.event === "cashin.paid") await marcarPago(req.body.data.external_id);
res.sendStatus(200);
});import os, requests
from flask import Flask, request, jsonify
API, H = "https://api.luniumpay.com", {"X-API-Key": os.environ["LUNIUM_KEY"]}
app = Flask(__name__)
@app.post("/checkout/<pedido>/pix")
def cobrar(pedido):
dados = request.get_json()
r = requests.post(f"{API}/cashin/charge", headers=H, timeout=30, json={
"amount_cents": dados["total_cents"], "asset": "usdt", "chain": "polygon",
"payout_address": os.environ["CARTEIRA_EMPRESA"],
"payer_tax_number": dados["cpf"], "payer_name": dados.get("nome"),
"external_id": f"pedido-{pedido}"})
c = r.json()
if not r.ok:
return jsonify(erro=c.get("erro"), detalhe=c.get("detail")), r.status_code
return jsonify(copia_e_cola=c["qr_copypaste"], expira_em=c["expires_at"])Sandbox: prueba antes de gastar
Una llamada, sin aprobación previa ni tarjeta; solo es obligatorio tu correo (al que enviamos el manual, el diagrama de flujo y la guía de integración paso a paso). La clave lun_test_ permite probar toda la interfaz sin mover dinero.
curl -X POST https://api.luniumpay.com/keys/sandbox \
-H "Content-Type: application/json" \
-d '{"email":"voce@empresa.com","name":"meu-teste"}'
Lo que el sandbox no demuestra: liquidación real. No se mueven criptomonedas ni se paga ningún PIX; sirve para ajustar el contrato, no para medir la vía de procesamiento.
Activos y redes
El catálogo de entrada de criptomonedas es GET /catalog; el de entrega tras Pix es GET /cashin/catalog. Consulta el activo y la red del flujo elegido antes de cotizar. La disponibilidad y los mínimos pueden cambiar.
Usa deposit_tag en el depósito cuando la aceptación indique un memo. En la entrega, envía payout_tag cuando lo requiera la ruta. El manual de la API describe cada campo.
Límites y retenciones
Consulta GET /keys/me para conocer los límites y las tarifas de la cuenta, GET /cashin/limits?payer_tax=... para el pagador y las vistas previas para el mínimo de la ruta. No uses un límite máximo fijo compartido entre cash-in, cash-out y payout.
Una solicitud de cobro pagada puede quedar retenida antes de la liquidación. Usa los estados, la cronología y los eventos firmados para distinguir entre cobro, saldo disponible y entrega final. La validez de la operación figura en expires_at.
Mediciones, no promesas
El plazo depende del flujo, la ruta disponible, las confirmaciones de la red y las posibles retenciones. Sigue la operación hasta su estado final y consulta el estado del servicio. El tiempo de una simulación no mide una liquidación real.
Idempotencia
Envía external_id en cada operación que mueve dinero. Repetir la misma llamada devuelve la misma operación, nunca una segunda; los mismos parámetros con un id distinto crean dos, y el mismo id con parámetros distintos devuelve 409 external_id_divergente en lugar de intentar adivinar.
Un timeout no es un rechazo. Si la llamada no respondió, consulta por el external_id antes de repetirla: la operación puede existir.
Webhooks y conciliación
Cada transición genera un evento firmado con HMAC SHA-256 y una marca de tiempo (cabecera X-Lunium-Signature, formato t=…,v1=…). La marca de tiempo impide reutilizar una entrega capturada. Verifica la firma antes de confiar en el cuerpo.
El webhook es la señal principal; el polling es opcional. Cuando necesites consultar, el estado incluye la timeline marcada por el servidor — representa la etapa a partir de ella, porque el cronómetro de tu polling mide tu red, no la operación.
¿No respondió con 2xx? Se reintenta la entrega con esperas progresivas. Puedes reenviarla manualmente en POST /webhooks/deliveries/{event_id}/retry y probar tu URL en POST /webhooks/test.
Cuando algo falla
Comprador nuevo con un importe alto: el tramo inicial es de R$ 100,00. Por encima, el QR sigue aceptándose con una retención de 24 h: el pedido puede esperar las 24 h, o puedes solicitar allow_hold: false y la solicitud de cobro se rechaza al crearla, indicando el importe permitido.
Pagó otra persona: Un PIX procedente de un documento diferente se devuelve automáticamente. Es uno de los casos que más pérdidas genera con una clave fija; aquí no pasa inadvertido.
QR caducado: la solicitud de cobro pasa a expired. Genera otra con un external_id nuevo: repetir el mismo id devolvería el QR anterior, ya caducado.
Seguridad
- webhook firmado — Cada webhook se firma con HMAC SHA-256 y una marca de tiempo (cabecera X-Lunium-Signature, formato t=…,v1=…), lo que impide reutilizar una entrega capturada.
- idempotencia — external_id en cash-in, cash-out y payout: repetir la misma llamada devuelve la MISMA operación, nunca una segunda. Los parámetros distintos con el mismo external_id devuelven 409.
- tráfico de salida validado — La URL del webhook se resuelve y valida antes de cada entrega: se rechazan direcciones internas, metadatos de la nube y redirecciones a redes privadas, y la conexión se fija en la IP validada.
- retención con bloqueo ante fallos — La retención se verifica al liquidar y de nuevo al reclamar la operación en SQL: ninguna vía (worker, consulta de estado o cola) puede liquidar antes de tiempo.
- payout duradero — El retiro registra la operación y debita el saldo en la MISMA transacción, antes de cualquier llamada al proveedor: no existe un débito sin una orden que conciliar.
- verificación abierta — GET /v1/verificar/{e2e} comprueba un PIX liquidado sin ninguna clave; la contraparte no tiene que confiar en nuestra palabra.
La verificación abierta es especialmente importante en una negociación: tu contraparte comprueba el PIX por su cuenta, sin clave y sin depender de nuestra palabra.
Tiempo hasta la primera integración
Un endpoint en el checkout y un manejador de webhook. Los equipos que lo implementaron tardaron una tarde, porque no hay un proceso de alta previo: la clave del sandbox se obtiene con una llamada y el contrato es el mismo que en producción.
Preguntas de los integradores
¿Existe un límite diario que bloquee la tienda?
El límite que rige es el del CPF/CNPJ del pagador, por operación: R$ 100,00 en la primera, R$ 200,00 durante las primeras 24 h y R$ 6.000,00/día después. No hay un límite diario por clave; los límites vigentes están en GET /keys/me y diez compradores siguen teniendo diez límites separados.
¿Puedo usar una clave PIX fija en lugar de generar un QR por pedido?
No, y es intencionado. Una clave fija recibe fondos de cualquier persona sin identificar el pedido: no sabrías a qué pedido corresponde el importe, y ese es precisamente el escenario que genera devoluciones y disputas. Una solicitud de cobro por pedido permite automatizar la conciliación.
¿El comprador necesita tener criptomonedas o una billetera?
No. Paga un PIX normal en la aplicación de su banco. La parte de las criptomonedas te corresponde a ti.
¿Cómo devuelvo el importe de un pedido cancelado?
Tú gestionas la devolución del PIX al comprador con el dinero ya liquidado; la API no revierte un PIX pagado por una decisión comercial. Lo que devuelve automáticamente es el PIX procedente de un documento incorrecto.
Contrato OpenAPI vigente · datos de esta página en product-truth.json · estado