Skip to content
Documentation
Get started

Collect

Refund a payment

Read refundable first to learn whether the money will be returned without the Timbro team stepping in. Then use the id of the captured payment and a new reference for the refund:

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",
});

Omitting amount reserves the remaining refundable balance once. Repeat the same request with the same refundReference if you lose the response; you will recover that reservation and its current status. To find it without repeating the request, list refunds with the exact paymentId and refundReference filters. Do not use a new reference to recover an earlier request.

To request a partial refund, add amount: { currency: "DOP", value: "100.00" } and the exact returned ITBIS in taxAmount, for example { currency: "DOP", value: "15.25" }. Use the tax from the returned items or credit note; Timbro does not estimate it proportionally. Do not change the amount on a refund that has already been created. Read refundable before you choose the amount, as described next.

A succeeded payment includes refundable. Retrieve it before you call POST /v1/refunds to learn what Timbro will do with each amount:

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 is the balance a new refund can reserve: the captured amount minus every processing, succeeded, verifying, and operator_required refund.
  • refundable.automaticAmount is the one amount whose refund moves money without the Timbro team stepping in. It is null when every refund needs the Timbro team.
  • refundable is null until the payment reaches succeeded.

When automaticAmount has a value, send it as amount. Timbro returns the money and the refund reaches succeeded without the Timbro team stepping in.

When automaticAmount is null, or when you send any other amount, Timbro accepts the request but creates the refund as operator_required: it keeps its reservation, and the Timbro team returns the money. Decide before you create it:

  • If the customer needs the money now, return it another way, such as cash, and do not also create the refund in Timbro.
  • If the customer can wait, create the refund with your refundReference and let the Timbro team act. You receive refund.operator_required when it is waiting for the Timbro team.

A payment offers a voluntary tip only when it includes a legal-tip item and checkoutPreferences.tips is enabled; see how to configure it.

amount is the refund total, including any tip being returned. For a partial refund, omitting voluntaryTipAmount returns zero voluntary tip. To return RD$ 100.00, including RD$ 20.00 of voluntary tip:

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" },
});

When amount is omitted to return the remaining balance, the remaining voluntary tip is included by default, and the refund reports kind: "full" even after an earlier partial refund. A refund for the full captured total also includes the remaining tip by default. The sale and tip portions must each fit their remaining balances. The legal tip belongs to the sale; voluntaryTipAmount identifies only the voluntary tip.

The credit note (E34) must reflect the refunded fiscal sale and exclude voluntary tip. Issuing the note and returning the money are separate tasks. After issuing it, associate it with the refund:

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

Timbro checks that the credit note total equals the refund amount minus the refunded voluntary tip and that its ITBIS matches the refund’s ITBIS when known. The association does not change the refund outcome.

refundable.automaticAmount has a value only when all of these are true:

  • The payment is a Test mode, Dominican peso (DOP), unsplit card payment.
  • The refund is for the full captured amount.
  • The payment has no earlier refund other than declined ones.
  • The payment and the refund go through Azul or CardNet, with the original payment’s merchant account and credentials.

refundable.automaticPartial separately identifies payments eligible for a smaller refund, with maximumAmount and maximumTaxAmount. It currently supports one partial refund of an Azul DOP Sale in Test mode when the request declares exact taxAmount. The amount must be below the captured total, and the tax must fit both the refund amount and the original captured tax. Repeat both values unchanged when recovering a request.

Partial refunds through CardNet or of held amounts that were later captured, partial requests without exact tax, split payments, Live payments, Apple Pay and Google Pay payments, and payments with an earlier successful or unresolved refund remain operator_required. So does a payment whose merchant account lacks the capture evidence or refund credentials; no money movement is attempted. Contact the Timbro team to continue those cases.

The refund keeps the original merchant account and capture even if the merchant’s default merchant account changes later.

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

succeeded confirms the refund; pending, processing, and verifying do not confirm it yet. If the outcome is unknown, retrieve this refund: do not create another. declined means it was rejected, and operator_required keeps its reservation and needs the Timbro team.

To avoid polling, subscribe your webhook to refund.succeeded and refund.declined. When one arrives, retrieve the refund with event.data.id and act on that status.

A refund does not issue an e-CF. If the sale requires a credit note, issue it as type E34 through /v1/invoices and keep the refund and invoice outcomes separate. Then associate the note with refunds.attachInvoice.