Saltar al contenido principal
Estás viendo el contrato actual (v1) de order.completed — agrega descuentos de orden y de producto (objeto Discount), impuestos granulares, el enum completo de itemType (PRODUCT / COMBO / MODIFIER / PACKAGING) y fulfillment.delivery.deliveryConfirmationCode.
order.completed dispara cuando una orden se inyecta exitosamente y está pagada. Lleva el snapshot V4 de la orden como trigger.data — todos los campos que tu flow necesita para actuar sobre la orden sin volver a llamar a Fire.

Condición de disparo

Fire emite order.completed exactamente una vez por orden, la primera vez que ambos son verdaderos en momento de inyección:
  • order.status === "COMPLETED"
  • order.paymentStatus === "SUCCEEDED"
Las órdenes que siguen en PENDING de pago, o que fallan el pago, nunca producen order.completed. Las cancelaciones después de completar producen un evento separado order.cancelled — no retraen order.completed.
CoberturaGlobal (todos los países, todos los canales)
Llave de idempotenciaevent.id (= flow_executions.id)
Dispara más de una vezNo, salvo en reintentos — usa event.id para deduplicar
OrdenNo garantizado entre órdenes — ordena por data.createdAt si necesitas orden
ReintentosHasta 5 intentos con backoff exponencial

Qué hay en trigger.data

trigger.data es el snapshot V4 de la orden — el mismo objeto que se persiste en flow_queue.trigger_data y se expone a los templates de tu flow. Las claves top-level, en orden:
ClaveTipoSiempre presente
orderIdstring (UUID)
orderCodestring | null
businessDayDatestring (YYYY-MM-DD)
externalOrderIdstring
redeemPointsboolean
accumulatePointsboolean
discountboolean
createdAtstring | null (ISO 8601 UTC)
orderCommentstring (puede ser "")
paymentStatusstring (siempre "SUCCEEDED")
statusstring (siempre "COMPLETED")
marketingobject | null
storeobject
deviceobject
channelobject
operatorobject
clientobject | null
paymentsobject
kdsobject
metadataobject (a menudo {})
orderLinesobject[]
fulfillmentobject

Ejemplo — payload real de producción (BR, sanitizado)

El ejemplo abajo viene de una fila real de flow_queue.trigger_data (tenant sandbox brasileño, canal KIOSK, servicio dine-in). Los campos PII se reemplazan por placeholders; el resto de los campos y formas son verbatim.
{
  "orderId": "21ec1f6c-c301-4528-b999-7836c1d21c6c",
  "orderCode": "OC-br-001",
  "businessDayDate": "2026-03-31",
  "externalOrderId": "7805610b-97cf-461f-a2d6-d86f63a80833",
  "redeemPoints": false,
  "accumulatePoints": false,
  "discount": false,
  "createdAt": "2026-05-06T01:22:59.028Z",
  "orderComment": "Comentario de prueba orden",
  "paymentStatus": "SUCCEEDED",
  "status": "COMPLETED",
  "marketing": null,
  "store": {
    "uid": "a4019cad-bbac-4269-9f8d-f29654e92c45",
    "code": "BR-SP-001",
    "name": "Loja Centro - SP",
    "phone": "1132094347",
    "address": "Av. Paulista 1578, Bela Vista, São Paulo - SP",
    "externalId": "a4019cad-bbac-4269-9f8d-f29654e92c45",
    "vendor": {
      "uid": "100.2.1",
      "name": "Sandbox Brand",
      "description": "Sandbox Brand",
      "loyaltyPlan": true
    },
    "account": {
      "uid": "100",
      "name": "Sandbox",
      "description": "Sandbox"
    },
    "locationInfo": {
      "city":     { "uid": "1", "code": "SAO", "name": "São Paulo" },
      "country":  { "uid": "6", "code": "BR",  "name": "Brasil" },
      "location": { "lat": "-23.5952979", "lon": "-46.6866818" },
      "timezone": "America/Sao_Paulo",
      "currencyCode": "BRL"
    },
    "storeFiscalConfig": {
      "enabled": true,
      "company": {
        "govIdType": "CNPJ",
        "govIdNumber": "00000000000000",
        "legalName": "Sandbox LTDA",
        "tradeName": "Sandbox"
      },
      "govIdType": "CNPJ",
      "govIdNumber": "00000000000000",
      "secondaryGovIdType": "INSCRICAO_ESTADUAL",
      "secondaryGovIdNumber": "000000000000",
      "metadata": {
        "neverstop":   { "url": "", "enabled": false },
        "storeCode3S": "50000001",
        "serialNumber": 1
      }
    }
  },
  "device": {
    "uid": "device_kiosk_001",
    "name": "KIOSK",
    "platform": "android",
    "metadata": { "ip": "10.0.0.0" }
  },
  "channel": {
    "uid": "c784d4ba-23c7-4929-b2f0-1a1960d9cdc2",
    "code": "KIOSK",
    "metadata": {}
  },
  "operator": {
    "uid": "op_001",
    "name": "Operator Name",
    "session": { "uid": "sess_001" }
  },
  "client": {
    "uid": "usr_consumidorfinal_001",
    "name": "CONSUMIDOR",
    "lastName": "FINAL",
    "email": "consumidor@example.com",
    "phone": "0000000000",
    "govIdType": "FINAL_CONSUMER",
    "govIdNumber": "00000000000",
    "externalId": null,
    "metadata": {
      "fiscal": null,
      "gender": "",
      "birthdate": ""
    },
    "billingInformation": {
      "email": "",
      "phone": "0000000000",
      "address": "",
      "govIdType": "FINAL_CONSUMER",
      "externalId": "",
      "govIdNumber": "00000000000",
      "businessName": ""
    }
  },
  "payments": {
    "totals": [
      {
        "taxes": [
          { "base": "229000", "name": "ICMS",   "rate": "0.04",   "amount": "9200",
            "metadata": { "cst": "90", "cBenef": "SP040100" } },
          { "base": "229000", "name": "PIS",    "rate": "0.0165", "amount": "3800",
            "metadata": { "cst": "01" } },
          { "base": "229000", "name": "COFINS", "rate": "0.076",  "amount": "17400",
            "metadata": { "cst": "01" } },
          { "base": "198700", "name": "IBS_UF",  "rate": "0.0006", "amount": "100",
            "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0.001, "rateEffective": 0.0006 } },
          { "base": "198700", "name": "IBS_MUN", "rate": "0",      "amount": "0",
            "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0, "rateEffective": 0 } },
          { "base": "198700", "name": "CBS",     "rate": "0.0054", "amount": "1100",
            "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0.009, "rateEffective": 0.0054 } }
        ],
        "total": "229000",
        "subtotal": "197400",
        "discounts": [
          { "priority": "1", "type": "FIXED", "value": "20000", "netPrice": "229000",
            "discountValue": "20000", "netPriceAfterDiscount": "209000" }
        ],
        "taxesValue": "31600",
        "currencyCode": "BRL",
        "discountValue": "20000",
        "subtotalBeforeTaxes": "197400"
      }
    ],
    "shippingCost": [
      { "total": "50000", "taxValue": "0", "currencyCode": "BRL",
        "subtotalWithoutTaxes": "50000", "subtotalIncludeDiscounts": "50000" }
    ],
    "extraCharges": [
      { "total": "15000", "taxValue": "0", "description": "Taxa de serviço", "currencyCode": "BRL",
        "subtotalWithoutTaxes": "15000", "subtotalIncludeDiscounts": "15000" }
    ],
    "discounts": [
      { "priority": "1", "type": "FIXED", "value": "20000", "netPrice": "229000",
        "discountValue": "20000", "netPriceAfterDiscount": "209000" }
    ],
    "paymentMethods": [
      {
        "mid": "",
        "tid": "",
        "processor": "CREDIT_CARD",
        "card": {
          "bin": "MASTERCARD",
          "mask": "",
          "brand": "MASTERCARD",
          "holder": "",
          "cardCountry": "",
          "lastFourDigits": "0000"
        },
        "acquirer": { "code": "00000000000000", "name": "ACQUIRER NAME", "cnpj": "01027058000191" },
        "voucher": "",
        "metadata": {},
        "totalBill": 22.9,
        "currencyCode": "BRL",
        "exactPayment": false,
        "transactionId": "tx_a1b2c3d4",
        "referenceNumber": null,
        "transactionDate": {
          "date": "2026-04-17T15:41:55.000Z",
          "timeZoneName": "America/Sao_Paulo"
        },
        "transactionType": "",
        "authorizationCode": "000000000000",
        "paymentMethodCode": "",
        "transactionStatus": "APPROVED",
        "customerCashAmount": "0",
        "idAuth": "952001",
        "receiptCustomer": "COMPROBANTE CLIENTE\n---------------------------\nMASTERCARD ****0000\nAPROBADO\nAuth: 952001",
        "receiptMerchant": "COMPROBANTE COMERCIO\n---------------------------\nMASTERCARD ****0000\nAPROBADO\nAuth: 952001"
      }
    ],
    "metadata": {
      "fiscal": {
        "vBC":      "229000",
        "vNF":      "229000",
        "vCBS":     "1100",
        "vIBS":     "100",
        "vPIS":     "3800",
        "vDesc":    "0",
        "vICMS":    "9200",
        "vProd":    "229000",
        "vIBSUF":   "100",
        "vCOFINS":  "17400",
        "vIBSMun":  "0",
        "vTotTrib": "31600",
        "vBCIBSCBS":"198700"
      }
    }
  },
  "kds": {
    "metadata": {},
    "orderCode": "OC-br-001",
    "buzzerName": "Leonardo",
    "invoiceEmail": "",
    "invoicePrint": true
  },
  "metadata": {},
  "orderLines": [
    {
      "uid": "b9912637-eb44-4533-a1e5-f5bf2cdd07e8",
      "hash": "0d86fae2b5bc6198",
      "itemId": "39d1bd7fcd45c52f0c824364c2c0cfc402bd72b046cfaaec276a0131908dbf20",
      "itemType": "COMBO",
      "itemDescription": "Crunch Salad + Batata Pequena + 1 Tira + Refri",
      "quantity": "1",
      "selectedCurrency": "BRL",
      "updatedAt": "2026-05-06T01:22:59.663Z",
      "price": {
        "unitPrice": [
          {
            "taxes": [],
            "netPrice": "229000",
            "grossPrice": "229000",
            "currencyCode": "BRL",
            "discounts": [
              { "priority": "1", "type": "FIXED", "value": "15000", "netPrice": "229000",
                "discountValue": "15000", "netPriceAfterDiscount": "214000" }
            ],
            "discountValue": "15000",
            "taxesValue": "0",
            "subtotalBeforeTaxes": "229000"
          }
        ],
        "totalPrice": [
          {
            "taxes": [
              { "base": "229000", "name": "ICMS",   "rate": "0.04",   "amount": "9200",
                "metadata": { "cst": "90", "cBenef": "SP040100" } },
              { "base": "229000", "name": "PIS",    "rate": "0.0165", "amount": "3800",
                "metadata": { "cst": "01" } },
              { "base": "229000", "name": "COFINS", "rate": "0.076",  "amount": "17400",
                "metadata": { "cst": "01" } },
              { "base": "198700", "name": "IBS_UF",  "rate": "0.0006", "amount": "100",
                "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0.001, "rateEffective": 0.0006 } },
              { "base": "198700", "name": "IBS_MUN", "rate": "0",      "amount": "0",
                "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0, "rateEffective": 0 } },
              { "base": "198700", "name": "CBS",     "rate": "0.0054", "amount": "1100",
                "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0.009, "rateEffective": 0.0054 } }
            ],
            "netPrice": "197400",
            "grossPrice": "229000",
            "currencyCode": "BRL",
            "discounts": [
              { "priority": "1", "type": "FIXED", "value": "15000", "netPrice": "229000",
                "discountValue": "15000", "netPriceAfterDiscount": "214000" }
            ],
            "discountValue": "15000",
            "taxesValue": "31600",
            "subtotalBeforeTaxes": "197400"
          }
        ]
      },
      "lineTotals": [
        {
          "taxes": [
            { "base": "229000", "name": "ICMS",   "rate": "0.04",   "amount": "9200",  "metadata": { "cst": "90", "cBenef": "SP040100" } },
            { "base": "229000", "name": "PIS",    "rate": "0.0165", "amount": "3800",  "metadata": { "cst": "01" } },
            { "base": "229000", "name": "COFINS", "rate": "0.076",  "amount": "17400", "metadata": { "cst": "01" } },
            { "base": "198700", "name": "IBS_UF",  "rate": "0.0006", "amount": "100",  "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0.001, "rateEffective": 0.0006 } },
            { "base": "198700", "name": "IBS_MUN", "rate": "0",      "amount": "0",    "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0, "rateEffective": 0 } },
            { "base": "198700", "name": "CBS",     "rate": "0.0054", "amount": "1100", "metadata": { "cst": "200", "reducao": 0.4, "cClassTrib": "200047", "rateNominal": 0.009, "rateEffective": 0.0054 } }
          ],
          "total": "229000",
          "subtotal": "197400",
          "currencyCode": "BRL",
          "discounts": [
            { "priority": "1", "type": "FIXED", "value": "15000", "netPrice": "229000", "discountValue": "15000", "netPriceAfterDiscount": "214000" }
          ],
          "discountValue": "15000",
          "taxesValue": "31600",
          "subtotalBeforeTaxes": "197400"
        }
      ],
      "modifierGroups": [
        {
          "uid": "e247256ad628b0fd9c45453370e247fef1ad173829cc809c9c6930698d62e56b",
          "description": "Selecione: Escolha seus sanduíches!",
          "selectedModifiers": [
            {
              "itemId": "0104fdb2010be9ac14750a950f81c026b35a49a3c32b36281f41ef650d278b72",
              "itemType": "MODIFIER",
              "itemDescription": "CRUNCH SALAD",
              "quantity": "1",
              "selectedCurrency": "BRL",
              "price": { /* unitPrice / totalPrice arrays — same shape */ },
              "modifierGroups": [],
              "metadata": {
                "redeemed": false,
                "externalCode": "80229#900002186#91198"
              }
            }
          ]
        }
      ],
      "metadata": {
        "fiscal": {
          "ncm": "21069090",
          "cfop": "5101",
          "csosn": "500",
          "vTotTrib": 3.16,
          "fiscalCategoryCode": "2106.90.90"
        },
        "redeemed": false,
        "externalCode": "80229",
        "referenceUnitPrice": null,
        "referenceTotalPrice": null
      }
    }
  ],
  "fulfillment": {
    "service": { "uid": "726c4892-48b9-45da-b066-2a8e83d2cb78", "code": "DINE_IN", "metadata": {} },
    "pickup": {
      "prepDate": "2026-05-06T01:22:59.028Z",
      "prepTime": "",
      "pickupDate": "2026-05-06T01:22:59.028Z",
      "propertyId": "a4019cad-bbac-4269-9f8d-f29654e92c45",
      "prepTimeUnit": "minute"
    },
    "delivery": {
      "city": "São Paulo",
      "country": "Brasil",
      "zipCode": "",
      "latitude": "0",
      "nickName": "HOME",
      "longitude": "0",
      "reference": "",
      "mainStreet": "",
      "propertyId": 1,
      "deliveryDate": null,
      "secondaryStreet": "",
      "deliveryConfirmationCode": "IFOOD-7H3K9"
    }
  }
}
Los valores monetarios son strings expresados como unidades menores enteras × 10000 (p. ej. "229000" = 22.9000 BRL). Esto evita drift de punto flotante a través de múltiples integraciones. Parsea con una librería decimal, nunca con parseFloat. La excepción es paymentMethods[].totalBill, que el origen a veces envía como número JSON — maneja ambos.

Referencia de campos

Identificadores top-level

orderId
string
UUID interno de la orden en Fire. Estable a través de entregas; úsalo junto con event.id para trazabilidad.
orderCode
string | null
Código corto legible mostrado en recibos y pantallas KDS (p. ej. 95K, OC-br-001). null cuando el canal no asigna uno.
businessDayDate
string
Día de negocio al que pertenece esta orden, en YYYY-MM-DD. Calculado en hora local de la tienda, así que una orden hecha a las 01:00 puede pertenecer al día de negocio anterior según el corte de fin de día.
externalOrderId
string
El ID de orden tal como lo provee el canal/agregador en la inyección. Úsalo para reconciliar con sistemas upstream (POS, dashboards de agregador).
createdAt
string | null
Timestamp ISO 8601 UTC de cuándo se hizo la orden originalmente. Distinto de event.createdAt, que es cuándo arrancó la ejecución del flow.
status
string
Siempre "COMPLETED" para este evento.
paymentStatus
string
Siempre "SUCCEEDED" para este evento.
redeemPoints
boolean
true si se canjearon puntos de fidelidad en esta orden.
accumulatePoints
boolean
true si el cliente acumuló puntos de fidelidad.
discount
boolean
true si se aplicó algún descuento.
orderComment
string
Nota free-text del cliente para toda la orden. Empty string cuando no se setea.

data.store

store
object
Snapshot de la tienda en el momento que se completó la orden.

data.client

client
object | null
Cliente que hizo la orden. null para órdenes de canal totalmente anónimas. Para órdenes BR de “consumidor final”, client se popula con valores placeholder (govIdType: "FINAL_CONSUMER", govIdNumber: "00000000000").

data.payments

payments
object
Desglose de dinero.

data.fulfillment

fulfillment
object
Cómo se entrega la orden.

data.kds

kds
object
Contexto del kitchen display.

data.device y data.operator

device
object
{ uid, name, platform, metadata.ip } — dispositivo de origen. Los campos pueden ser null para canales no físicos.
operator
object
{ uid, name, session.uid } — staff/cajero que procesó la orden. Todos los campos null para canales self-service (kiosko, web).

data.orderLines

orderLines
object[]
Productos pedidos. Totalmente camelCase (transformado por el builder V4).

data.marketing, data.metadata, data.channel

marketing
object | null
Loyalty + cupones. null en la mayoría de países hoy; reservado para uso futuro.
metadata
object
Bag free-form para extras a nivel de orden. A menudo {}.
channel
object
{ uid, code, metadata }. Ejemplos de code: KIOSK, APP, IFOOD, RAPPI.

Datos fiscales

La data fiscal se incluye solo cuando la tienda tiene emisión fiscal habilitada (store.storeFiscalConfig.enabled === true). Para países sin fiscal o tiendas sin configuración, las tres ubicaciones de abajo están ausentes o en null.
order.completed lleva información fiscal en tres ubicaciones distintas. Cada una sirve un propósito distinto:

1. data.store.storeFiscalConfig — identidad del emisor y config del proveedor

Identifica la entidad legal que emite el documento y cómo autenticar con el proveedor fiscal. Las credenciales NO están aquí intencionalmente — el nodo fiscal las obtiene por proveedor/account.
"storeFiscalConfig": {
  "enabled": true,
  "company": {
    "govIdType":   "CNPJ",
    "govIdNumber": "00000000000000",
    "legalName":   "Sandbox LTDA",
    "tradeName":   "Sandbox"
  },
  "govIdType":            "CNPJ",
  "govIdNumber":          "00000000000000",
  "secondaryGovIdType":   "INSCRICAO_ESTADUAL",
  "secondaryGovIdNumber": "000000000000",
  "metadata": {
    "neverstop":    { "url": "", "enabled": false },
    "storeCode3S":  "50000001",
    "serialNumber": 1
  }
}

2. data.payments.metadata.fiscal — agregados fiscales a nivel de orden

Totales agregados estilo SEFAZ, listos para enviar al proveedor fiscal (tu proveedor fiscal en Brasil). Los valores son strings × 10000.
"metadata": {
  "fiscal": {
    "vBC":      "229000",
    "vNF":      "229000",
    "vICMS":    "9200",
    "vPIS":     "3800",
    "vCOFINS":  "17400",
    "vCBS":     "1100",
    "vIBS":     "100",
    "vIBSUF":   "100",
    "vIBSMun":  "0",
    "vTotTrib": "31600",
    "vBCIBSCBS":"198700",
    "vDesc":    "0",
    "vProd":    "229000"
  }
}

3. data.orderLines[n].metadata.fiscal — clasificación fiscal por línea

Códigos fiscales por producto. Usados por el proveedor fiscal para clasificar cada línea en el documento.
"metadata": {
  "fiscal": {
    "ncm":               "21069090",
    "cfop":              "5101",
    "csosn":             "500",
    "vTotTrib":          3.16,
    "fiscalCategoryCode":"2106.90.90"
  }
}
Además, metadata por impuesto vive dentro de cada taxes[n].metadata (en payments.totals[].taxes[], orderLines[].price.totalPrice[].taxes[] y orderLines[].lineTotals[].taxes[]) con códigos como cst, cBenef, cClassTrib, reducao, rateNominal, rateEffective.

Variaciones por país

El ejemplo de arriba es de una tienda brasileña — el caso más complejo. La forma del V4 es idéntica en todos los países, incluido el desglose granular de taxes[]: cada país popula payments.totals[].taxes[] y los taxes por línea con sus impuestos locales en la misma forma { base, name, rate, amount, metadata }. Lo que cambia por país:
  • Nombres de impuesto — BR usa ICMS, PIS, COFINS, IBS_UF, IBS_MUN, CBS; otros países llevan sus impuestos locales (ej. IVA) con la misma estructura.
  • Códigos de metadata — BR lleva códigos SEFAZ (cst, cBenef, cClassTrib, reducao, rateNominal, rateEffective); otros países sus propios códigos.
  • Emisión fiscal SEFAZ — solo Brasil. payments.metadata.fiscal, orderLines[].metadata.fiscal (ncm/cfop/csosn) y los eventos order.invoiced / order.reversed solo aplican a BR. Los demás países igual llevan su taxes[], pero estos bloques SEFAZ están ausentes.
Las tiendas brasileñas con storeFiscalConfig.enabled === true llevan el payload fiscal completo — ver la sección Datos fiscales arriba. Marcadores de país:
  • store.locationInfo.country.code: "BR" · name: "Brasil" · timezone: "America/Sao_Paulo"
  • store.locationInfo.currencyCode: "BRL"
  • store.storeFiscalConfig.govIdType: "CNPJ" (14 dígitos)
  • store.storeFiscalConfig.secondaryGovIdType: "INSCRICAO_ESTADUAL"
  • payments.totals[].currencyCode: "BRL", paymentMethods[].currencyCode: "BRL", orderLines[].selectedCurrency: "BRL"
  • Populados: payments.metadata.fiscal (vBC / vNF / vICMS / vPIS / vCOFINS / vTotTrib …), orderLines[].metadata.fiscal (ncm / cfop / csosn), lineTotals[].taxes[] (ICMS, PIS, COFINS, IBS_*)

Tabla de referencia rápida

Paíscountry.codeMoneda efectivagovIdType (formato)Agregados fiscales
BrasilBRBRLCNPJ (14 dígitos)Sí — agregados SEFAZ completos
ArgentinaARARSCUIT (XX-XXXXXXXX-X)No — null / vacío
ChileCLCLPRUT (XXXXXXXX-X)No
ColombiaCOCOPNIT (XXXXXXXXX-X)No
EcuadorECUSD (moneda oficial de Ecuador)RUC (13 dígitos)No
VenezuelaVEVESRIF (J-XXXXXXXX-X)No
Otros paísesvaríavaríavaríaNo
A medida que más países tengan un pipeline fiscal dedicado, sus eventos fiscales llegarán como fiscal.*.{cc} (p. ej. fiscal.authorized.co, fiscal.authorized.ec). Hasta entonces, solo order.completed y order.cancelled disparan para tiendas no-BR — los bloques fiscales se quedan en null / vacíos.

Handler de ejemplo

async function onOrderCompleted(data) {
  const {
    orderId,
    externalOrderId,
    store,
    payments,
    orderLines,
    fulfillment,
    createdAt,
  } = data;

  // 1. Persiste para contabilidad / analítica
  await db.orders.upsert({
    where: { fireOrderId: orderId },
    create: {
      fireOrderId: orderId,
      externalOrderId,
      storeCode: store.code,
      country: store.locationInfo.country.code,
      currency: store.locationInfo.currencyCode,
      // payments.totals[0].total es "229000" unidades menores × 10000
      totalMinorUnits: BigInt(payments.totals[0]?.total ?? "0"),
      completedAt: new Date(createdAt ?? Date.now()),
      service: fulfillment.service.code,
    },
    update: {},
  });

  // 2. Si fiscal-enabled, despacha al pipeline fiscal
  if (store.storeFiscalConfig?.enabled) {
    await fiscalPipeline.enqueue({
      orderId,
      country: store.locationInfo.country.code,
      emitter: store.storeFiscalConfig.company,
      aggregates: payments.metadata?.fiscal,
      lines: orderLines.map((l) => ({
        itemId: l.itemId,
        ncm: l.metadata?.fiscal?.ncm,
        cfop: l.metadata?.fiscal?.cfop,
      })),
    });
  }

  // 3. Si delivery, despacha a logística
  if (fulfillment.service.code === "DELIVERY" && fulfillment.delivery) {
    await dispatcher.send({
      orderId,
      address: fulfillment.delivery,
      items: orderLines,
    });
  }
}

Errores comunes

  • Decimales como strings × 10000. payments.totals[0].total === "229000" significa 22.9 BRL. Usa una librería decimal; nunca con parseFloat.
  • El casing es mixto en payments.totals[] y partes de paymentMethods[]. Lee tanto currencyCode como currencyCode defensivamente. El builder V4 transforma la mayor parte del snapshot pero pasa los objetos de payment sin cambios.
  • fulfillment.delivery puede estar presente incluso para servicios non-delivery con ceros placeholder. Ramifica siempre por fulfillment.service.code.
  • client puede ser un placeholder “FINAL_CONSUMER” populado en BR — no es null. Trata govIdType === "FINAL_CONSUMER" como anónimo para analítica.
  • event.id es el ID de ejecución del flow, no el ID de la orden. Usa event.id para idempotencia (cambia por entrega), y orderId como llave de negocio.
  • Routing multi-tenant. Usa store.account.uid, store.vendor.uid y store.code para enrutar al tenant correcto en tu sistema, aunque Fire ya da scope al flow de su lado.

Eventos relacionados

order.cancelled

Dispara cuando esta orden se cancela después.

order.invoiced

Solo Brasil — dispara cuando SEFAZ autoriza el documento fiscal de la orden.