Reembolsos y reclamos
Devuelve un pago completo o en parte desde tu banco, registra las devoluciones pendientes y atiende los reclamos de tus clientes.
Para cualquier negocio que en algún momento tenga que devolver dinero: un pedido cancelado, un artículo agotado, un cobro de más o un cliente que reclama que no le llegó lo que pagó.
El modelo
| Lo que pasa | En Billete |
|---|---|
| Devuelves todo o parte de un pago | Un reembolso (refund) del pago |
| Tu cliente tiene un problema con un pago | Un reclamo (claim) que abre él desde Billete |
| Le das la razón y le devuelves el dinero | El reclamo se resuelve con un reembolso, en el mismo paso |
- Pago Móvil no tiene contracargos. Cuando algo sale mal, tu cliente abre un reclamo: es la conversación entre los dos y su resolución. Abrirlo no mueve dinero; solo un reembolso lo hace.
- El reembolso es de un pago, no de un pedido ni de una suscripción. Billete no cancela la suscripción ni repone existencias al reembolsar: eso lo decide tu aplicación.
- Las devoluciones las haces tú, desde tu banco. Billete no guarda tu dinero: el neto de cada pago ya salió a tu Pago Móvil. Tú se lo devuelves a tu cliente por Pago Móvil y el reembolso lo registra con la referencia de tu banco (
mode: "manual"). Solo cuando el dinero sigue donde lo cobró un proveedor que devuelve solo, lo procesa él (mode: "provider") y quedapendinghasta que confirma. - Los problemas con las entregas los decide Billete. Media entre tu cliente y tu negocio y, si le da la razón a tu cliente, tú le debes el pago (paso 4).
1. Reembolsa un pago
// Sin amount, devuelve todo lo que queda por reembolsar. reference: la de tu Pago Móvil.
const refund = await billete.payments.refund(order.paymentId, {
reason: "Pedido cancelado por el cliente",
reference: "00123456789",
}, { idempotencyKey: `refund-${order.id}` });
// Una parte: el monto va en la moneda en que se cobró.
await billete.payments.refund(order.paymentId, {
amount: 400000,
reason: "Un artículo agotado",
}, { idempotencyKey: `refund-${order.id}-${item.id}` });El monto está en la moneda de liquidación del pago (payment.settlement.currency): si el precio era en dólares y se pagó en bolívares, reembolsas bolívares (precios en dólares). Puedes reembolsar varias veces mientras quede algo; pasarte responde refund_exceeds_payment, y un pago que no se confirmó, payment_not_refundable.
Con un reembolso manual, devuelve el dinero antes de llamar a refund y envía la referencia de tu Pago Móvil en reference: registrarlo le dice a Billete, y a tu cliente por correo con esa referencia, que ya lo hiciste, y queda succeeded en el acto. Las comisiones del pago no se devuelven.
En el dashboard, tu negocio puede pedir que los reembolsos desde cierto monto los apruebe otro propietario o administrador (Configuración › Cobros). Esa regla es para las personas: con tu clave secreta, la API reembolsa como siempre.
2. Registra las devoluciones pendientes
A veces el reembolso lo pide Billete (un cambio de suscripción pagado con su cotización vencida, subscription.change_failed, o una entrega con problemas) o lo pide o aprueba alguien en el dashboard, y queda pending esperando que tú devuelvas el dinero. Si tu cliente escribió su Pago Móvil completo al pagar, ese es el destino; si no, Billete le pide por correo dónde recibirlo. Desde que se sabe, tienes 7 días para devolverlo y registrarlo: si vence, tus cobros se pausan hasta que lo hagas (reglas). El destino completo está en el dashboard, en el pago. Escucha refund.created y conviértelo en una tarea para tu equipo:
// En tu webhook, ya verificado:
if (event.type === "refund.created" && event.data.mode === "manual" && event.data.status === "pending") {
await db.tasks.create({
type: "devolver_pago",
paymentId: event.data.paymentId,
amount: event.data.amount,
currency: event.data.currency,
});
}
// Cuando tu equipo ya devolvió el dinero, con la referencia que le dio su banco:
await billete.payments.refund(task.paymentId, { reference: bankReference });Registrarlo sin amount (o con el monto completo pendiente) confirma ese mismo reembolso sin crear otro; otro monto responde refund_amount_mismatch. Pendiente no significa devuelto: tu cliente espera ese dinero.
3. Atiende los reclamos
Tu cliente abre el reclamo desde la página de su pago, de su entrega o desde el portal, con un motivo (reason: not_received, overcharged, unrecognized, order_issue u other) y lo que pasó. Lo sigue desde el enlace que le llega por correo. Tú lo atiendes desde el dashboard (Reclamos) o con la API:
await billete.claims.respond(claim.id, { message: "Tu pedido sale hoy en la tarde." });
// A favor de tu cliente, con reembolso en el mismo paso (sin amount, lo que queda del pago).
await billete.claims.resolve(claim.id, {
outcome: "customer",
note: "El pedido se perdió en el camino: te devolvemos el pago.",
refund: {},
}, { idempotencyKey: `claim-${claim.id}-resolve` });
// A favor de tu negocio, sin reembolso.
await billete.claims.resolve(claim.id, { outcome: "business", note: "El pedido llegó el martes." });La primera respuesta lo pasa de open a under_review. Resolverlo con refund es el mismo reembolso del paso 1, con las mismas reglas: con Pago Móvil, devuelve el dinero antes. Si el reembolso no procede, el reclamo sigue abierto. Un reclamo resuelto o retirado no se reabre, y un pago admite un reclamo activo a la vez y hasta 3 en total.
4. Entregas con problemas
Si tu cliente abre un reclamo mientras espera una compra con entrega, la entrega pasa a problem y Billete revisa el caso: puede pedirles información a ti o a tu cliente en la conversación del reclamo (author: "billete"). Resolverlo tú a favor de tu negocio no cierra la entrega. Termina así:
- Tu cliente confirma que la recibió: el reclamo pasa a
withdrawn. - Billete lo decide a tu favor: la entrega pasa a
received(receivedBy: "billete"). - Billete lo decide a favor de tu cliente: la entrega se cancela y tú le debes el pago: queda un reembolso
pendingque le devuelves desde tu banco y registras, como en el paso 2. Billete solo decide; no mueve el dinero.
Cuando Billete lo decide, el reclamo se resuelve con esa decisión y resolution.by: "billete". Tú también puedes darle la razón a tu cliente antes y reembolsarle: un reembolso completo cancela la entrega (fulfillment.canceled); uno parcial la deja como está.
Un envío nacional sin guía válida a los 2 días hábiles se cancela (cancellationReason: "not_shipped") y le debes el pago a tu cliente de la misma forma. Tu cliente indica dónde recibir esas devoluciones en la página de su reembolso o de su entrega (devolución al cliente).
5. Escucha los eventos
| Evento | Qué pasó | Qué hace tu aplicación |
|---|---|---|
refund.created | Se registró o se pidió un reembolso. | Si es manual y pending, crea la tarea de devolver el dinero. |
payment.refunded | Un reembolso se completó: el pago queda partially_refunded o refunded. | Aplica tu política: cancela el pedido, quita el acceso o cancela la suscripción. |
refund.failed | El proveedor no pudo devolver el dinero. | Avisa a tu equipo y vuelve a intentarlo. |
claim.created | Tu cliente abrió un reclamo. | Avisa a tu equipo y pausa lo que dependa de ese pago. |
claim.updated | Un mensaje nuevo, o empezaste a revisarlo. | Muéstralo en tu herramienta de soporte. |
claim.resolved | Lo resolviste tú, o Billete en una entrega con problema; resolution.refundId dice si hubo reembolso. | Cierra el caso. |
claim.withdrawn | Tu cliente lo retiró o confirmó que recibió su compra. | Cierra el caso. |
Por ejemplo, para cortar el acceso de una suscripción reembolsada por completo:
if (event.type === "payment.refunded") {
const payment = await billete.payments.retrieve(event.data.id);
if (payment.status === "refunded" && payment.subscriptionId) {
await billete.subscriptions.cancel(payment.subscriptionId, { atPeriodEnd: false });
}
}Si el plan tiene respaldo, la suscripción vuelve a su plan gratuito en vez de terminar (planes personales).
Reembolsos · Reglas de reembolsos · Reclamos · Reglas de reclamos