Refunds
Refunds pull mediante un link de reclamo privado — por qué no son automáticos, cómo le llega el link al comprador, el fee de red y las transferencias con token equivocado.
Última actualización 27 oct 2026
Todo dinero que llega a una dirección de depósito y no se le debe al seller queda retenido para el comprador en LIABILITY:REFUND_ESCROW:<invoice> y se convierte en un refund:
reason | Cuándo |
|---|---|
OVERPAYMENT | La invoice se paga con más que su total: el excedente (invoice OVERPAID). |
EXPIRED_UNDERPAID | Un pago parcial cuando la invoice vence (invoice EXPIRED_UNDERPAID, luego REFUNDED una vez devuelto). |
LATE_PAYMENT | Una transferencia confirmada después de que la invoice venció, se liquidó o se anuló. |
WRONG_TOKEN | Otro token canónico de la misma red enviado a la dirección (USDC a una invoice en USDT). |
Pull, no push
Los refunds nunca se devuelven automáticamente a la dirección que pagó. Esa dirección suele ser la hot wallet de un exchange o un contrato que no puede recibir (ni acreditar) el dinero. En cambio, el comprador reclama el refund: abre un link privado, pega una wallet que controla en la red del pago y el worker lo envía ahí.
El link de reclamo
<CRYPTO_CHECKOUT_BASE_URL>/pay/<invoice id>/refund/<claim token>. El token (256 bits) es la única credencial del comprador, así que se mantiene fuera de todos los lugares por donde viaja el id de la invoice:
- El checkout público (
GET /api/v1/public/pay/:id, la página/pay/:id) muestra que hay un refund retenido, cuánto, su estado y de dónde viene el link —refund.deliveryesemailoseller— pero nunca el link. - Con
buyer_emailen la invoice, el comprador recibe el link por email cuando el refund pasa a ser reclamable (delivery: "email"). - Si no, el seller se lo reenvía (
delivery: "seller"). El merchant lo lee deGET /api/v1/crypto/invoices/:id(refunds[].claim_url),GET /api/v1/crypto/refunds, el webhookrefund.claimable(que también llevabuyer_email) o el botón Copy claim link del dashboard. La vista de respuesta cruda del dashboard lo oculta.
Los endpoints públicos de refund se identifican solo por el claim token: GET /api/v1/public/refunds/:claimToken y POST /api/v1/public/refunds/:claimToken/claim.
Reclamar
POST …/claim { "destination_address": "0x…" } valida la dirección (válida, checksum EIP-55 cuando mezcla mayúsculas y minúsculas, que no sea una de las direcciones propias del gateway, que no sea un contrato — un contrato responde 422), fija el fee de red y el monto neto, y mueve el refund CLAIMABLE → PROCESSING (202). Reclamar de nuevo a la misma dirección es idempotente.
El fee de red es el costo de enviar el refund, y se le cobra al refund: gas estimado × max fee per gas × CRYPTO_NATIVE_PRICE_USD_<FAMILY> (Polygon 0.50, BSC 600, Base 3000 por defecto), como mínimo 0.01, registrado como REVENUE:REFUND_NETWORK_FEE. El portal muestra lo retenido, el fee y "you receive" antes de que el comprador confirme. Cuando el fee se comería todo el refund (dust), el reclamo se rechaza con 422.
El worker envía un refund solo después de que cada transferencia de esa invoice y ese token fue barrida y el sweep está confirmado, y después lo marca COMPLETED al alcanzar el umbral de confirmaciones de la red (webhook refund.completed). Una invoice EXPIRED_UNDERPAID pasa a REFUNDED.
Token equivocado
Una transferencia de otro token canónico nunca detecta, paga ni liquida la invoice. Una vez confirmada, queda en escrow en su propio token (REFUND_ESCROW:<invoice>:<network>:<token>) con un refund WRONG_TOKEN que se devuelve en ese token, cualquiera sea el estado de la invoice. No se le acredita nada a ningún seller en ese token. Los sweeps corren por invoice × token, así que el refund espera solo el sweep de su propio token. Los tokens que no están en el registry no se vigilan.
[!NOTE] Un refund que falla on chain pasa a
FAILEDy su escrow se sigue debiendo. Todavía no hay botón de reintento; necesita un operador.