Skip to main content
The TMT Platform exposes two public billing functions and three internal helpers that together handle all invoice generation. Both public functions ultimately call emitirFacturaCore, which submits the DocumentoElectronico payload to the TFHKA /api/Emision endpoint.

Billing flow

1

Order completes

A ticket sale is finalized. The order_created function writes the order to Firestore, and order_process settles transactions and payouts.
2

process_order_billing

Called separately (to avoid Cloud Function timeouts) with the completed order data. It enriches purchaser_info from u_users, fetches tercero_info from u_clients, retrieves the exchange rate from data/exchange_rates, and calls getNextInvoiceNumber() to reserve the next sequential invoice number.
3

billing_emision

process_order_billing builds the full DocumentoElectronico object via generateBillingDataFromOrder, then passes body_emision to billing_emision. billing_emision calls emitirFacturaCore, which authenticates with TFHKA and POSTs to /api/Emision.
4

TFHKA API

TFHKA validates the document, registers it with the Venezuelan tax authority (SENIAT), and returns a response containing resultado.numeroDocumento and resultado.urlConsulta.
5

Store invoice

On success, the TFHKA response is written back to the orders/{order_id} document under the billing_info field. For expense invoices, a new document is created in event_invoices/{numeroDocumento}.

billing_emision

Manually emits a digital invoice by forwarding a pre-built DocumentoElectronico payload directly to the TFHKA /api/Emision endpoint. Use this when you have already assembled the billing data and need to submit it without the enrichment logic of generateBillingFromExpenses. Trigger: POST /billing_emision

Request

data.body_emision
object
required
A fully formed DocumentoElectronico object as expected by the TFHKA API. See the DocumentoElectronico structure section below for all fields.
{
  "data": {
    "body_emision": {
      "DocumentoElectronico": {
        "Encabezado": { "..." : "..." },
        "DetallesItems": [ "..." ],
        "InfoAdicional": null
      }
    }
  }
}

Response (success)

The raw JSON response from the TFHKA API is returned directly with HTTP 200.
{
  "mensaje": "Documento emitido exitosamente",
  "resultado": {
    "numeroDocumento": "00000042",
    "urlConsulta": "https://demoemisionv2.thefactoryhka.com.ve/consulta/00000042"
  }
}
resultado.numeroDocumento
string
The sequential invoice number assigned by TFHKA, zero-padded to 8 digits.
resultado.urlConsulta
string
A URL where the issued invoice can be consulted or downloaded.

Response (failure)

HTTP 500 with the TFHKA error body or the exception message.
billing_emision does not write anything to Firestore. It is a thin pass-through to the TFHKA API. Storing the result is the responsibility of the caller.

generateBillingFromExpenses

Generates and emits an invoice from a list of expense line items tied to an event. The function resolves the event’s associated client, fetches the current exchange rate, assigns an invoice number, builds the DocumentoElectronico, submits it to TFHKA, and stores the result in event_invoices. Trigger: POST /generateBillingFromExpenses

Request

data.event_id
string
required
Firestore ID of the event the expenses belong to. Used to look up the associated client_id.
data.expense_type
string
required
Category label for the expense (e.g. "Producción", "Logística"). Appended to each line item description on the invoice.
data.currency
string
required
Currency of the expense amounts. Accepted values: "USD" or "BSD". When "USD", IGTF (3%) is applied and amounts are converted to BSD using the current exchange rate.
data.items
array
required
One or more expense line items. Each item must have:
  • name (string) — display name for the line item.
  • amount (number) — total amount including IVA in the specified currency.
{
  "data": {
    "event_id": "evt_abc123",
    "expense_type": "Producción",
    "currency": "USD",
    "items": [
      { "name": "Alquiler de tarima", "amount": 1500.00 },
      { "name": "Sonido e iluminación", "amount": 800.00 }
    ]
  }
}

Response (success)

{
  "data": {
    "success": true,
    "message": "Documento emitido exitosamente",
    "invoice_id": "00000043",
    "invoice_url": "https://demoemisionv2.thefactoryhka.com.ve/consulta/00000043",
    "full_api_response": { "..." : "..." }
  }
}
data.success
boolean
true when the invoice was emitted and stored successfully.
data.invoice_id
string
The numeroDocumento returned by TFHKA, used as the Firestore document ID in event_invoices.
data.invoice_url
string
Direct URL to view or download the issued invoice from TFHKA.
data.full_api_response
object
The complete raw response object from the TFHKA /api/Emision call.

Response (failure)

{
  "error": {
    "message": "La emisión de la factura falló en el API externo.",
    "details": { "..." : "..." }
  }
}
generateBillingFromExpenses reads the exchange rate from data/exchange_rates at the moment of the call. Make sure this document is kept up to date — it is not pulled from the order record.

Internal functions

getNextInvoiceNumber

Reads and atomically increments the last_number counter in data/invoice_counter using a Firestore transaction. Returns the new integer, which is then zero-padded to 8 digits (String(invoiceNumber).padStart(8, '0')) before being placed in NumeroDocumento.
data/invoice_counter
  last_number: number   ← incremented atomically on every invoice
If the data/invoice_counter document does not exist, getNextInvoiceNumber throws and the entire billing call fails. Create this document manually before enabling billing in a new environment.

generateBillingDataFromOrder

Builds the complete DocumentoElectronico from a completed order. Called by process_order_billing. Signature:
generateBillingDataFromOrder(orderData, orderId, eventDetails, portal, exchangeRate, invoiceNumber)
Key behaviors:
  • Groups tickets by zone and creates one DetallesItems entry per zone.
  • Computes IVA at 16% on the subtotal.
  • Applies IGTF (3%) if any transaction used payment_name in ['Zelle', 'Efectivo Dolares', 'Efectivo Divisa']. The IGTF base is min(subtotal + IVA, totalPaidInForeignCurrency).
  • Populates Comprador from orderData.purchaser_info and Tercero from orderData.tercero_info.
  • Sets Vendedor.nombre to orderData.box_office_name.

generateBillingDataFromExpenses

Builds the DocumentoElectronico from an expense record. Called by generateBillingFromExpenses. Signature:
generateBillingDataFromExpenses(expenseData, invoiceNumber, clientData, exchangeRate)
Key behaviors:
  • When currency === 'USD': assumes the item amount values are total-including-IGTF. Back-calculates: totalConIVA = total / 1.03, then subtotal = totalConIVA / 1.16.
  • When currency === 'BSD': assumes amounts are total-including-IVA only. No IGTF is applied.
  • Creates one DetallesItems entry per item, with description "${item.name} (${expenseData.expense_type})".
  • Sets Vendedor.nombre to "Trade Master Transaction L.L.C".
  • Populates Comprador from clientData (id_type, id, name_commercial, address.line, phone, email).

emitirFacturaCore

Low-level wrapper that authenticates with TFHKA and POSTs to /api/Emision. Returns a normalized result object:
// Success
{ success: true, data: <TFHKA response> }

// Failure
{ success: false, error: <TFHKA error body or exception message> }
This function never throws — errors are caught and returned in the { success: false } shape so callers can handle them without try/catch.

DocumentoElectronico structure

The following is the full structure of the DocumentoElectronico object submitted to TFHKA. All monetary strings are formatted with 2 decimal places (.toFixed(2)). Exchange rates use 4 decimal places.
{
  "DocumentoElectronico": {
    "Encabezado": {
      "IdentificacionDocumento": {
        "TipoDocumento": "01",
        "NumeroDocumento": "00000042",
        "FechaEmision": "18/03/2026",
        "FechaVencimiento": "18/03/2026",
        "HoraEmision": "10:45:00 am",
        "Anulado": false,
        "TipoDePago": "Inmediato",
        "Serie": "",
        "TipoDeVenta": "Interna",
        "Moneda": "BSD"
      },
      "Vendedor": {
        "codigo": "01",
        "nombre": "Caja 1",
        "numCajero": "01"
      },
      "Comprador": {
        "TipoIdentificacion": "V",
        "NumeroIdentificacion": "12345678",
        "RazonSocial": "Ana Gomez",
        "Direccion": "Miranda",
        "Ubigeo": null,
        "Pais": "VE",
        "Notificar": null,
        "Telefono": ["+58-412-0000000"],
        "Correo": ["[email protected]"],
        "OtrosEnvios": null
      },
      "SujetoRetenido": null,
      "Tercero": {
        "tipoIdentificacion": "J",
        "numeroIdentificacion": "123456789",
        "razonSocial": "Producciones XYZ C.A.",
        "direccion": "Av. Principal, Caracas",
        "tipo": "PRODUCTORA",
        "correo": ["[email protected]"]
      },
      "Totales": {
        "NroItems": "2",
        "MontoGravadoTotal": "64.66",
        "MontoExentoTotal": "0.00",
        "Subtotal": "64.66",
        "TotalAPagar": "77.73",
        "TotalIVA": "10.34",
        "MontoTotalConIVA": "77.73",
        "MontoEnLetras": "SETENTA Y SIETE BOLÍVARES CON 73/100",
        "TotalDescuento": null,
        "ListaDescBonificacion": null,
        "ImpuestosSubtotal": [
          {
            "CodigoTotalImp": "G",
            "AlicuotaImp": "16.00",
            "BaseImponibleImp": "64.66",
            "ValorTotalImp": "10.34"
          },
          {
            "CodigoTotalImp": "IGTF",
            "AlicuotaImp": "3.00",
            "BaseImponibleImp": "75.00",
            "ValorTotalImp": "2.25"
          }
        ]
      },
      "TotalesOtraMoneda": {
        "Moneda": "USD",
        "TipoCambio": "36.5000",
        "MontoGravadoTotal": "1.77",
        "MontoExentoTotal": "0.00",
        "Subtotal": "1.77",
        "TotalAPagar": "2.13",
        "TotalIVA": "0.28",
        "MontoTotalConIVA": "2.13",
        "MontoEnLetras": "DOS DÓLARES CON 13/100",
        "TotalDescuento": null,
        "ListaDescBonificacion": null,
        "ImpuestosSubtotal": [ "..." ]
      }
    },
    "DetallesItems": [
      {
        "NumeroLinea": "1",
        "IndicadorBienoServicio": "2",
        "Descripcion": "Tickets para Festival de Jazz 2024 - Zona: VIP",
        "Cantidad": "3",
        "UnidadMedida": "4L",
        "PrecioUnitario": "912.50",
        "PrecioUnitarioDescuento": null,
        "MontoBonificacion": null,
        "DescripcionBonificacion": null,
        "DescuentoMonto": null,
        "PrecioItem": "2737.50",
        "CodigoImpuesto": "G",
        "TasaIVA": "16",
        "ValorIVA": "438.00",
        "ValorTotalItem": "2737.50",
        "InfoAdicionalItem": [],
        "ListaItemOTI": null
      }
    ],
    "InfoAdicional": null
  }
}

Key field notes

FieldNotes
TipoDocumentoAlways "01" (factura)
NumeroDocumentoZero-padded 8-digit sequential number from data/invoice_counter
MonedaAlways "BSD" in the primary Totales block
TipoDeVentaAlways "Interna"
TipoDePagoAlways "Inmediato"
IndicadorBienoServicioAlways "2" (service)
UnidadMedidaAlways "4L"
CodigoImpuestoAlways "G" (General — IVA)
TasaIVAAlways "16"
CodigoTotalImp"G" for IVA, "IGTF" for the foreign-currency tax
AlicuotaImp"16.00" for IVA, "3.00" for IGTF
Tercero.tipoAlways "PRODUCTORA"
MontoEnLetras is generated from the BSD total using the numero-a-letras library. The function replaces "Pesos" with "Bolívares" and strips the M.N. suffix to produce the correct Spanish text.

Firestore storage

event_invoices (expense invoices)

After generateBillingFromExpenses succeeds, a document is written to event_invoices/{numeroDocumento}:
{
  "invoice_id": "00000043",
  "event_id": "evt_abc123",
  "client_id": "cli_xyz",
  "expense_type": "Producción",
  "currency": "USD",
  "billed_items": [
    { "name": "Alquiler de tarima", "amount": 1500.00 },
    { "name": "Sonido e iluminación", "amount": 800.00 }
  ],
  "created_at": "<ServerTimestamp>",
  "api_response": { "..." : "..." },
  "status": "EMITTED"
}

orders (order invoices)

After process_order_billing succeeds, the billing_info field is written to the existing orders/{order_id} document. This field contains the full TFHKA API response.
The Firestore document ID for expense invoices is the numeroDocumento string returned by TFHKA (e.g. "00000043"), making it easy to cross-reference a physical invoice number with the stored record.

Build docs developers (and LLMs) love