POS/ERP fiscal directo
La ruta directa @timbro/fiscal es para un POS o ERP que ya posee la venta y necesita fiscalizarla. Esa operación crea una operación fiscal contra Timbro y no crea un Payment, no entrega Checkout y no expone conceptos de proveedor de pagos. Mantén la responsabilidad de la venta, numeración interna y líneas en el POS/ERP.
El flujo aprobado separa dos carriles:
- Para cobrar, el servidor usa
@timbro/paymentsypayments.create; la autorización y suPaymenttienen sus propios estados. - Para fiscalizar una venta ya cobrada o pagada por otro medio, el servidor usa
@timbro/fiscalcon su credencial fiscal y la revisión inmutable de la venta. No mezcles una referencia de Checkout ni un tender write-back con esa operación.
El adaptador directo debe recibir identidad del emisor, comprador, líneas y una clave de idempotencia. Guarda sourceRecordId y sourceRevision; un timeout se recupera consultando la misma operación, no creando otra. Los estados accepted, processing, issued, rejected, unavailable y operator_required pertenecen a la operación fiscal y no se traducen a estados de Payment.
La forma orientativa del cliente fiscal es explícita sobre esas claves de recuperación:
import { createTimbroFiscal, type SourceDocument } from "@timbro/fiscal";
const baseUrl = process.env.TIMBRO_FISCAL_ORIGIN;const credential = process.env.TIMBRO_FISCAL_CREDENTIAL;if (!baseUrl || !credential) throw new Error("Missing fiscal origin or credential");const fiscal = createTimbroFiscal({ baseUrl, credential,});const sourceDocument: SourceDocument = { kind: "consumer", buyer: { identityKind: "anonymous" }, sale: { currency: { kind: "dop" }, declaredTotals: { net: "1050.85", tax: "199.15", total: "1250.00", nonBillable: "0.00" }, lines: [{ sourceLineId: "sku-1", description: "Servicio", quantity: "1", measurementUnit: "unit", unitPrice: "1250.00", itemKind: "service", taxTreatment: "itbis_18" }], paymentTerms: { terms: "cash" }, },};const idempotencyKey = "pos-sale-123-r4"; // derive this from the immutable sale revisionconst operation = await fiscal.fiscalOperations.create({ source: { recordId: "pos-sale-123", revision: 4, contentSha256: "...sha256-of-canonical-sale..." }, idempotencyKey, action: { kind: "issue", document: sourceDocument }, printedRepresentation: { profile: "receipt_80mm" },});El paquete fiscal publicado debe conservar esta semántica de sourceRecordId, sourceRevision, digest e idempotencia; no agregues un paymentId o checkoutUrl a este carril.
Antes de registrar un tender write-back, reconcilia la respuesta del POS, la autoridad adquirente y el resultado fiscal por separado. Si una frontera queda ambigua, conserva la venta en revisión y asigna recuperación operativa.