Ir al contenido
Documentación

Cobrar y facturar

Cobrar una factura que ya emitiste

Cuando tu sistema ya emitió la factura con Timbro, puedes enviarle al cliente un link de pago por esa factura o mostrarle un QR en la caja. Timbro toma de la factura el total, los artículos y los impuestos, así que el cliente paga exactamente lo que dice la factura, y el pago queda relacionado con ella.

Para cobrar una factura necesitas:

  • Una clave de API del mismo negocio y del mismo modo con que emitiste la factura, con acceso a Cobra y a Facturación. En estos ejemplos la guardas en TIMBRO_API_KEY.
  • Una afiliación de Azul, CardNet o Portal conectada en ese modo.
  • La factura en accepted o acceptedWithObservations y en pesos dominicanos. Emítela de contado si el cliente paga en el momento con el link o el QR, o a crédito si te va a pagar más adelante. Si todavía no la emites, sigue Emitir una factura desde tu sistema.

Envía solo la referencia del pago y el invoiceId de la factura. No envíes monto, moneda ni artículos, porque Timbro los toma de la factura:

import { createTimbroPayments } from "@timbro/payments";
const timbro = createTimbroPayments({ apiKey: process.env.TIMBRO_API_KEY! });
const payment = await timbro.payments.create({
paymentReference: "venta-842-cobro",
returnUrl: "https://merchant.example/ventas/842",
invoicing: { mode: "existing", invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" },
});
const paymentLink = payment.checkout.url;

checkout.url es el link de pago. Envíaselo al cliente por WhatsApp o por correo, o conviértelo en un QR para la caja. Guarda el id del pago junto a la venta.

El monto del pago es el total a pagar de la factura, incluidos los cargos que no se facturan. Si activaste las propinas, el cliente puede añadir una propina voluntaria al pagar. Timbro no emite otra factura por este pago.

Si quieres que Timbro le envíe la factura al cliente por correo, añade recipientEmail dentro de invoicing. Si tu sistema ya le envía la factura, no añadas recipientEmail, para que el cliente no la reciba dos veces.

Cuando recibas el webhook payment.succeeded, o cuando el cliente vuelva a returnUrl, consulta el pago desde tu servidor. Marca la factura como pagada en tu sistema solo si status es succeeded, y hazlo una sola vez aunque recibas el aviso más de una vez. El pago conserva el invoiceId, así que puedes saber qué factura cobró.

Si el banco rechaza la tarjeta, el pago sigue abierto y el cliente puede intentarlo de nuevo con el mismo link. Los demás estados están en Estados del pago.

Si cobras la factura completa en un solo pago, solo puede haber un pago abierto o pagado para esa factura a la vez. Si el cliente perdió el link, consulta el pago: mientras su estado sea requires_customer_action, vuelve a enviarle el mismo checkout.url.

Si el pago venció o lo cancelaste, crea otro pago para la misma factura con una paymentReference nueva. El pago nuevo queda relacionado con la factura. Para reemplazar un link que todavía está abierto, por ejemplo porque lo enviaste a otra persona, cancela primero el pago anterior:

await timbro.payments.cancel({ paymentId: payment.id });
const replacement = await timbro.payments.create({
paymentReference: "venta-842-cobro-2",
invoicing: { mode: "existing", invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" },
});

Si la factura ya tiene otro pago abierto, o ya está pagada, la API responde 409 payment_invoice_not_allowed. Si repites la misma solicitud con la misma referencia, recibes el mismo pago.

La API responde con un error en /invoicing/invoiceId cuando no puedes cobrar la factura por su invoiceId:

  • Si la DGII todavía no acepta la factura, espera el webhook invoice.accepted y crea el pago después.
  • Si la factura es de otro negocio, o tu clave es de otro modo, usa la clave del negocio y del modo con que se emitió la factura.
  • Si la factura está en otra moneda, no puedes cobrarla con Timbro, porque Timbro solo cobra en pesos dominicanos (DOP).
  • Si la factura es gratuita (paymentTerms.type: "free"), no hay nada que cobrar.
  • Si la factura tiene retenciones u otros impuestos que un pago no puede llevar, crea un pago por monto, sin invoiceId, con el total y el ITBIS de la factura.
  • Si las formas de pago declaradas en la factura no suman su total, corrige paymentTerms.allocations en tu sistema antes de emitir.

Si el cliente va a pagar la factura en abonos, por ejemplo en una venta a crédito o en una venta en la que paga una cantidad en efectivo y el resto con un QR, crea un pago por cada abono que el cliente te pague con Timbro. Envía el invoiceId de la factura y, en amount, lo que el cliente paga en ese abono. Cada abono queda relacionado con la factura:

const firstAbono = await timbro.payments.create({
paymentReference: "venta-850-abono-1",
amount: { currency: "DOP", value: "5900.00" },
returnUrl: "https://merchant.example/ventas/850",
invoicing: { mode: "existing", invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" },
});
const abonos = await timbro.payments.list({ invoiceId: "f35b7ae4-c484-4c63-981e-4f0f15e913b5" });

Si no envías tax.includedAmount, Timbro le asigna a cada abono el ITBIS proporcional a su monto, redondeado al centavo. Por ejemplo, si una factura de RD$ 11,800.00 incluye RD$ 1,800.00 de ITBIS, un abono de RD$ 5,900.00 lleva RD$ 900.00. Cuando todos los abonos usan ese cálculo, el abono que completa el total lleva el ITBIS que queda, y los abonos suman exactamente el ITBIS de la factura. Si tu contabilidad calcula otro ITBIS para un abono, envíalo en tax.includedAmount. En ese caso, tu sistema debe cuidar que los abonos sumen el ITBIS de la factura. La API rechaza un ITBIS mayor que el que todavía queda por cubrir. Si no envías description, el abono se describe como Factura <e-NCF>.

Varios abonos pueden estar abiertos a la vez, pero Timbro no deja que los abonos abiertos o pagados sumen más que el total a pagar de la factura. Un abono cancelado o vencido libera su monto. Mientras la factura tenga abonos abiertos o pagados, la API rechaza un pago por la factura completa con 409 payment_invoice_not_allowed. Mientras un pago por la factura completa esté abierto, la API también rechaza los abonos.

Un abono no acepta recipientEmail. Si quieres que Timbro le envíe la factura al cliente, sigue Envía la factura por correo.

Tu sistema sigue llevando el saldo de la factura, porque el cliente también puede pagar en efectivo o por transferencia fuera de Timbro. Con payments.list filtrado por invoiceId encuentras todos los pagos de esa factura, en cualquier estado. Para saber cuánto cobró Timbro, suma solo los que tienen status succeeded. Usa una paymentReference distinta para cada abono.

Si la factura tiene retenciones, Timbro no puede cobrarla por su invoiceId, ni completa ni en abonos. En ese caso, crea un pago por monto, sin invoiceId, por cada abono, con su ITBIS en tax.includedAmount. Esos pagos no quedan relacionados con la factura, así que envía en saleReference la invoiceReference de la factura para encontrarlos después con payments.list.

Reembolsar el pago y corregir la factura ante la DGII son dos pasos separados. Cuando el cliente devuelve una compra que pagó con Timbro:

  1. Crea el reembolso del pago. Para un reembolso parcial, envía el monto y su ITBIS en amount y taxAmount. Si también le devuelves la propina voluntaria, inclúyela en el reembolso.
  2. Emite una nota de crédito (E34) que modifica la factura original. La nota lleva solo lo que el cliente devolvió, sin la propina voluntaria.
  3. Cuando la DGII acepte la nota, relaciónala con el reembolso:
await timbro.refunds.attachInvoice({
refundId: "refund_123456789abcdefghjkmnpqr",
invoiceId: "0c1f8d6e-5b7a-4e0f-9a51-3f8e2d7c6b10",
idempotencyKey: "venta-842-nota-credito-1",
});

Timbro comprueba que el total de la nota sea el monto reembolsado menos la propina voluntaria devuelta, y que su ITBIS coincida con el del reembolso. Esta relación no cambia el resultado del reembolso. Cuando se completa, Timbro envía el webhook refund.invoiceAttached.

Cobrar una factura emitida en un servidor de Timbro dedicado

Algunos negocios emiten sus facturas en un servidor de Timbro dedicado, distinto de https://api.timbro.do. La clave de Cobra no puede leer esas facturas. En el servidor que emitió la factura, crea un acceso limitado a ella:

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: "venta-126-cobro",
currency: "DOP",
items: [{
description: "Servicio",
quantity: "1",
unit: "unit",
kind: "service",
unitPrice: "1250.00",
tax: { kind: "itbis_18", included: true },
}],
returnUrl: "https://merchant.example/ventas/126",
invoicing: {
mode: "existing",
invoiceId: access.invoiceId,
receipt: { id: access.id, accessToken: access.accessToken },
},
});

Envía los artículos reales de la factura; Timbro comprueba que coincidan con ella. El equipo de Timbro debe registrar antes ese origen como confiable. Guarda accessToken como un secreto y no lo envíes al navegador ni lo escribas en URLs o registros. Con este acceso, Timbro puede leer la factura y descargar su PDF, pero no su XML firmado. La factura se la envía al cliente el servidor que la emitió, así que no añadas recipientEmail.