Ir al contenido
Documentación

Cobrar

Reembolsar un pago

Para devolver el dinero de un pago confirmado, primero lee refundable para saber si el dinero se devolverá sin que intervenga el equipo de Timbro. Luego usa el id del pago y una referencia que identifique este reembolso en tu sistema:

import { createTimbroPayments } from "@timbro/payments";
const timbro = createTimbroPayments({ apiKey: process.env.TIMBRO_PAYMENTS_SECRET_KEY! });
const refund = await timbro.refunds.create({
paymentId: "payment_123456789abcdefghjkmnpqr",
refundReference: "order-123-return-1",
});

Si no envías amount, solicitas todo lo que queda por devolver de ese pago. Timbro aparta ese monto al aceptar la solicitud, para que otro reembolso no use el mismo dinero.

Si pierdes la respuesta, repite la solicitud con el mismo refundReference: recibirás el mismo reembolso y su estado actual. También puedes encontrarlo sin repetir la solicitud: lista los reembolsos con los filtros exactos paymentId y refundReference. No uses una referencia nueva para recuperar una solicitud anterior.

Para solicitar solo una parte del dinero, añade amount: { currency: "DOP", value: "100.00" } y el ITBIS exacto del reembolso en taxAmount, por ejemplo { currency: "DOP", value: "15.25" }. Usa el impuesto de los artículos devueltos o de la nota de crédito; Timbro no calcula una proporción estimada. No puedes cambiar el monto de un reembolso ya creado. Antes de elegir el monto, lee refundable, como se explica a continuación.

Un pago succeeded incluye refundable. Consúltalo antes de llamar a POST /v1/refunds para saber qué hará Timbro con cada monto:

const payment = await timbro.payments.retrieve("payment_123456789abcdefghjkmnpqr");
const automaticAmount = payment.refundable?.automaticAmount ?? null;
if (automaticAmount !== null) {
await timbro.refunds.create({
paymentId: payment.id,
refundReference: "order-123-return-1",
amount: automaticAmount,
});
}
  • refundable.amount es el saldo que un reembolso nuevo puede apartar: lo capturado, menos los reembolsos en curso, exitosos, en verificación o en operator_required.
  • refundable.automaticAmount es el único monto cuyo reembolso mueve el dinero sin que intervenga el equipo de Timbro. Es null cuando todo reembolso necesita al equipo de Timbro.
  • refundable es null hasta que el pago llega a succeeded.

Si automaticAmount tiene valor, envía ese monto en amount. Timbro devuelve el dinero y el reembolso llega a succeeded sin intervención del equipo de Timbro.

Si automaticAmount es null, o si envías otro monto, Timbro acepta la solicitud, pero crea el reembolso en operator_required: mantiene apartado el monto y el equipo de Timbro devuelve el dinero. Decide antes de crearlo:

  • Si el cliente necesita el dinero ahora, devuélvelo por otro medio, por ejemplo en efectivo, y no crees también el reembolso en Timbro.
  • Si puede esperar, crea el reembolso con tu refundReference y deja que el equipo de Timbro actúe. Recibirás refund.operator_required cuando quede esperando al equipo de Timbro.

Un pago solo ofrece propina voluntaria si incluye un artículo con propina legal y activas checkoutPreferences.tips, como explica Ofrecer propina voluntaria.

amount es el total del reembolso, incluida cualquier propina que devuelvas. En un reembolso parcial, omitir voluntaryTipAmount devuelve cero propina voluntaria. Para devolver RD$ 100.00, de los cuales RD$ 20.00 son propina, usa:

const partialRefund = await timbro.refunds.create({
paymentId: "payment_123456789abcdefghjkmnpqr",
refundReference: "order-123-return-2",
amount: { currency: "DOP", value: "100.00" },
voluntaryTipAmount: { currency: "DOP", value: "20.00" },
});

Si omites amount para devolver todo el saldo restante, también se devuelve la propina voluntaria restante por defecto, y el reembolso se reporta como kind: "full" aunque antes hubiera un reembolso parcial. Un reembolso por el total capturado también incluye la propina restante por defecto. La porción de venta y la de propina deben caber en sus respectivos saldos. La propina legal forma parte de la venta; voluntaryTipAmount solo identifica la propina voluntaria.

La nota de crédito (E34) debe usar el monto fiscal de la venta devuelta y excluir la propina voluntaria. Emitirla y devolver el dinero son tareas separadas. Después de emitirla, asóciala al reembolso:

await timbro.refunds.attachInvoice({
refundId: refund.id,
invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5",
idempotencyKey: "order-123-return-2-credit-note",
});

Timbro valida que el total de la nota de crédito sea el monto del reembolso menos la propina voluntaria devuelta y que su ITBIS coincida con el ITBIS del reembolso, cuando se conoce. Esta asociación no cambia el resultado del reembolso.

refundable.automaticAmount tiene valor solo cuando se cumplen todas estas condiciones:

  • El pago es del modo de prueba, en pesos dominicanos (DOP), con tarjeta y sin división.
  • El reembolso es por el total capturado.
  • El pago no tiene reembolsos anteriores, salvo los rechazados.
  • El pago y el reembolso pasan por Azul o CardNet, con la afiliación y las credenciales del pago original.

refundable.automaticPartial indica por separado si el pago permite un reembolso menor, con los límites maximumAmount y maximumTaxAmount. Actualmente admite un reembolso parcial de una venta Azul en DOP, en modo de prueba, si envías el taxAmount exacto. El monto debe ser menor al total cobrado y el ITBIS no puede superar el monto devuelto ni el impuesto del pago original. Repite ambos valores sin cambios al recuperar una solicitud.

Los reembolsos parciales por CardNet o de montos retenidos que luego se cobraron, los parciales sin impuesto exacto, los pagos divididos, los pagos en vivo, los pagos con Apple Pay o Google Pay y los pagos con un reembolso anterior completado o pendiente quedan en operator_required. Lo mismo ocurre si a la afiliación le falta la evidencia de la captura o las credenciales de reembolso; en ese caso no se intenta mover dinero. Escribe al equipo de Timbro para continuar esos casos.

Timbro devuelve el dinero a través de la misma afiliación de Azul o CardNet que hizo el pago, aunque después configures otra afiliación para nuevos pagos.

const current = await timbro.refunds.retrieve(refund.id);

succeeded confirma que se devolvió el dinero. Si recibes pending, processing o verifying, espera y vuelve a consultar el mismo reembolso. Si el resultado sigue sin conocerse, no crees otro: el primero podría completarse. declined indica que fue rechazado; operator_required indica que el reembolso mantiene apartado el monto y necesitas ayuda del equipo de Timbro.

Para no consultar en un ciclo, suscribe tu webhook a refund.succeeded y refund.declined. Al recibir uno, consulta el reembolso con event.data.id y usa ese estado.

Devolver el dinero no emite un e-CF. Si la venta requiere una nota de crédito, emítela con el tipo E34 en /v1/invoices y conserva por separado el resultado del reembolso. Luego asocia la nota con refunds.attachInvoice.