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 pasaEn Billete
Devuelves todo o parte de un pagoUn reembolso (refund) del pago
Tu cliente tiene un problema con un pagoUn reclamo (claim) que abre él desde Billete
Le das la razón y le devuelves el dineroEl 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 queda pending hasta 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 pending que 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

EventoQué pasóQué hace tu aplicación
refund.createdSe registró o se pidió un reembolso.Si es manual y pending, crea la tarea de devolver el dinero.
payment.refundedUn 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.failedEl proveedor no pudo devolver el dinero.Avisa a tu equipo y vuelve a intentarlo.
claim.createdTu cliente abrió un reclamo.Avisa a tu equipo y pausa lo que dependa de ese pago.
claim.updatedUn mensaje nuevo, o empezaste a revisarlo.Muéstralo en tu herramienta de soporte.
claim.resolvedLo resolviste tú, o Billete en una entrega con problema; resolution.refundId dice si hubo reembolso.Cierra el caso.
claim.withdrawnTu 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