Collect and invoice
Collect an invoice you already issued
When your system has already issued the invoice with Timbro, you can send the customer a payment link for that invoice or show a QR at the register. Timbro takes the total, items, and taxes from the invoice, so the customer pays exactly what the invoice says, and the payment is linked to it.
To collect an invoice you need:
- An API key for the same business and the same mode you issued the invoice with, with Collect and Invoicing access. These examples store it in
TIMBRO_API_KEY. - An Azul, CardNet, or Portal merchant account connected in that mode.
- The invoice in
acceptedoracceptedWithObservationsand in Dominican pesos. Issue it with cash terms if the customer pays on the spot with the link or the QR, or with credit terms if they will pay you later. If you have not issued it yet, follow Issue an invoice from your system.
Create the payment link
Section titled “Create the payment link”Send only the payment reference and the invoice’s invoiceId. Do not send an amount, currency, or items, because Timbro takes them from the invoice:
import { createTimbroPayments } from "@timbro/payments";
const timbro = createTimbroPayments({ apiKey: process.env.TIMBRO_API_KEY! });const payment = await timbro.payments.create({ paymentReference: "sale-842-payment", returnUrl: "https://merchant.example/sales/842", invoicing: { mode: "existing", invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" },});const paymentLink = payment.checkout.url;import os
from timbro_payments import TimbroPayments
timbro = TimbroPayments(api_key=os.environ["TIMBRO_API_KEY"])payment = timbro.payments.create({ "paymentReference": "sale-842-payment", "returnUrl": "https://merchant.example/sales/842", "invoicing": {"mode": "existing", "invoiceId": "f35b7ae4-c484-4c63-981e-4f0f15e913b5"},})payment_link = payment["checkout"]["url"]checkout.url is the payment link. Send it to the customer by WhatsApp or email, or turn it into a QR for the register. Store the payment’s id with the sale.
The payment amount is the invoice’s total payable, including charges that are not invoiced. If you enabled tips, the customer can add a voluntary tip when paying. Timbro does not issue another invoice for this payment.
If you want Timbro to email the invoice to the customer, add recipientEmail inside invoicing. If your system already emails the invoice, do not add recipientEmail, so the customer does not receive it twice.
Confirm the payment
Section titled “Confirm the payment”When you receive the payment.succeeded webhook, or when the customer returns to returnUrl, retrieve the payment from your server. Mark the invoice as paid in your system only if status is succeeded, and do it only once even if you receive the notification more than once. The payment keeps the invoiceId, so you can tell which invoice it collected.
If the bank declines the card, the payment stays open and the customer can try again with the same link. The other statuses are in Payment statuses.
Send a new link
Section titled “Send a new link”If you collect the invoice in full with one payment, that invoice can have only one open or paid payment at a time. If the customer lost the link, retrieve the payment: while its status is requires_customer_action, send the same checkout.url again.
If the payment expired or you canceled it, create another payment for the same invoice with a new paymentReference. The new payment is linked to the invoice. To replace a link that is still open, for example because you sent it to the wrong person, cancel the previous payment first:
await timbro.payments.cancel({ paymentId: payment.id });const replacement = await timbro.payments.create({ paymentReference: "sale-842-payment-2", invoicing: { mode: "existing", invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" },});timbro.payments.cancel(payment["id"])replacement = timbro.payments.create({ "paymentReference": "sale-842-payment-2", "invoicing": {"mode": "existing", "invoiceId": "f35b7ae4-c484-4c63-981e-4f0f15e913b5"},})If the invoice already has another open payment, or is already paid, the API responds 409 payment_invoice_not_allowed. If you repeat the same request with the same reference, you get the same payment.
When the API does not accept the invoice
Section titled “When the API does not accept the invoice”The API responds with an error at /invoicing/invoiceId when the invoice cannot be collected by its invoiceId:
- If the DGII has not accepted the invoice yet, wait for the
invoice.acceptedwebhook and create the payment afterward. - If the invoice belongs to another business, or your key is for another mode, use the key of the business and mode that issued the invoice.
- If the invoice is in another currency, Timbro cannot collect it, because Timbro only collects in Dominican pesos (DOP).
- If the invoice is free (
paymentTerms.type: "free"), there is nothing to collect. - If the invoice has withholdings or other taxes that a payment cannot carry, create an amount payment, without
invoiceId, with the invoice’s total and ITBIS. - If the payment methods declared on the invoice do not add up to its total, correct
paymentTerms.allocationsin your system before issuing.
Collect an invoice in partial payments
Section titled “Collect an invoice in partial payments”If the customer will pay the invoice in partial payments, for example in a credit sale, or in a sale where they pay some in cash and the rest with a QR, create one payment for each partial payment the customer makes with Timbro. Send the invoice’s invoiceId and, in amount, what the customer pays in that partial payment. Each partial payment stays linked to the invoice:
const firstPartialPayment = await timbro.payments.create({ paymentReference: "sale-850-partial-1", amount: { currency: "DOP", value: "5900.00" }, returnUrl: "https://merchant.example/sales/850", invoicing: { mode: "existing", invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" },});const partialPayments = await timbro.payments.list({ invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" });first_partial_payment = timbro.payments.create({ "paymentReference": "sale-850-partial-1", "amount": {"currency": "DOP", "value": "5900.00"}, "returnUrl": "https://merchant.example/sales/850", "invoicing": {"mode": "existing", "invoiceId": "f35b7ae4-c484-4c63-981e-4f0f15e913b5"},})partial_payments = timbro.payments.list(invoice_id="f35b7ae4-c484-4c63-981e-4f0f15e913b5")Without tax.includedAmount, Timbro gives each partial payment the ITBIS proportional to its amount, rounded to the cent. For example, if an invoice of RD$ 11,800.00 includes RD$ 1,800.00 of ITBIS, a partial payment of RD$ 5,900.00 carries RD$ 900.00. When every partial payment uses that calculation, the one that completes the total takes the ITBIS left, and together they carry exactly the invoice’s ITBIS. If your accounting computes a different ITBIS for a partial payment, send it in tax.includedAmount. Your system then makes sure the partial payments add up to the invoice’s ITBIS. The API rejects an ITBIS larger than what is still uncovered. Without description, the partial payment is described as Factura <e-NCF>.
Several partial payments can be open at once, but Timbro never lets the open or paid ones add up to more than the invoice’s total to pay. A canceled or expired partial payment frees its amount. While the invoice has open or paid partial payments, the API rejects a payment for the full invoice with 409 payment_invoice_not_allowed. While a payment for the full invoice is open, the API also rejects partial payments.
A partial payment does not accept recipientEmail. To have Timbro email the invoice to the customer, follow Email the invoice.
Your system still keeps the invoice’s balance, because the customer can also pay in cash or by transfer outside Timbro. payments.list filtered by invoiceId finds every payment of that invoice, in any status. To know how much Timbro collected, add up only those whose status is succeeded. Use a different paymentReference for each partial payment.
If the invoice has withholdings, Timbro cannot collect it by its invoiceId, neither in full nor in partial payments. In that case, create an amount payment without invoiceId for each partial payment, with its ITBIS in tax.includedAmount. Those payments are not linked to the invoice, so send the invoice’s invoiceReference in saleReference to find them later with payments.list.
Refund an invoiced sale
Section titled “Refund an invoiced sale”Refunding the payment and correcting the invoice with the DGII are two separate steps. When the customer returns a purchase they paid with Timbro:
- Create the refund for the payment. For a partial refund, send the amount and its ITBIS in
amountandtaxAmount. If you also return the voluntary tip, include it in the refund. - Issue a credit note (E34) that modifies the original invoice. The note carries only what the customer returned, without the voluntary tip.
- When the DGII accepts the note, link it to the refund:
await timbro.refunds.attachInvoice({ refundId: "refund_123456789abcdefghjkmnpqr", invoiceId: "0c1f8d6e-5b7a-4e0f-9a51-3f8e2d7c6b10", idempotencyKey: "sale-842-return-1-note",});timbro.refunds.attach_invoice( "refund_123456789abcdefghjkmnpqr", {"invoiceId": "0c1f8d6e-5b7a-4e0f-9a51-3f8e2d7c6b10"}, idempotency_key="sale-842-return-1-note",)Timbro checks that the note’s total is the refunded amount minus the returned voluntary tip, and that its ITBIS matches the refund’s. This link does not change the refund’s result. When it completes, Timbro sends the refund.invoiceAttached webhook.
Collect an invoice issued on a dedicated Timbro server
Some businesses issue their invoices on a dedicated Timbro server, other than https://api.timbro.do. The Collect key cannot read those invoices. On the server that issued the invoice, create limited access to it:
import { createTimbroInvoices } from "@timbro/invoices";
const issuer = createTimbroInvoices({ apiKey: process.env.ISSUER_TIMBRO_INVOICES_API_KEY!, baseUrl: process.env.ISSUER_TIMBRO_INVOICES_ORIGIN!,});const access = await issuer.invoices.shareReceipt("f35b7ae4-c484-4c63-981e-4f0f15e913b5");const externalInvoicePayment = await timbro.payments.create({ paymentReference: "sale-126-payment", currency: "DOP", items: [{ description: "Service", quantity: "1", unit: "unit", kind: "service", unitPrice: "1250.00", tax: { kind: "itbis_18", included: true }, }], returnUrl: "https://merchant.example/sales/126", invoicing: { mode: "existing", invoiceId: access.invoiceId, receipt: { id: access.id, accessToken: access.accessToken }, },});from timbro_invoices import TimbroInvoices
issuer = TimbroInvoices( api_key=os.environ["ISSUER_TIMBRO_INVOICES_API_KEY"], base_url=os.environ["ISSUER_TIMBRO_INVOICES_ORIGIN"],)access = issuer.invoices.share_receipt("f35b7ae4-c484-4c63-981e-4f0f15e913b5")external_invoice_payment = timbro.payments.create({ "paymentReference": "sale-126-payment", "currency": "DOP", "items": [{ "description": "Service", "quantity": "1", "unit": "unit", "kind": "service", "unitPrice": "1250.00", "tax": {"kind": "itbis_18", "included": True}, }], "returnUrl": "https://merchant.example/sales/126", "invoicing": { "mode": "existing", "invoiceId": access["invoiceId"], "receipt": {"id": access["id"], "accessToken": access["accessToken"]}, },})Send the invoice’s real items; Timbro checks that they match it. The Timbro team must first register that origin as trusted. Store accessToken as a secret and do not send it to the browser or write it in URLs or logs. With this access, Timbro can read the invoice and download its PDF, but not its signed XML. The server that issued the invoice sends it to the customer, so do not add recipientEmail.