Ir al contenido

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:

  1. Para cobrar, el servidor usa @timbro/payments y payments.create; la autorización y su Payment tienen sus propios estados.
  2. Para fiscalizar una venta ya cobrada o pagada por otro medio, el servidor usa @timbro/fiscal con 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 revision
const 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.