Skip to Content
Products

Bundle JSON

The JSON structure of a bundle (version 1), for imports and for anyone who builds a bundle outside the screen.

Last updated on

The Extra invoice lines card saves a bundle as JSON on the product, under the key invoiceBundle. For imports you use the same structure. For normal use you do not need this page: the fields and their explanation are on Bundle steps.

Structure

FieldMeaning
invoiceBundle.versionAlways 1
invoiceBundle.stepsList of steps, at most 40. The order is the order of the child lines on the invoice

Every step is either a product step or an info step.

Product step

FieldMeaning
productIdAn existing product in your organisation. Required
quantityA number greater than 0, default 1. The quantity of the line without a formula, or when the formula returns true
quantityScaleOptional. "anchor_line_quantity" multiplies every calculated quantity by the quantity of the anchor line
descriptionOptional. Text for the invoice line instead of the product name; a formula can give each line its own description
styleOptional. DEFAULT or INFO
billOncePerInvoiceOptional. true keeps the step with the first anchor line when an invoice has several anchor lines
extendAccessMethodOptional. { "daysAfterPeriodEnd": <integer, 0 to 3660> }, see NFC extension
formulaOptional. TypeScript that decides which lines the step produces, see Formula

Info step

{
  "stepKind": "info",
  "description": "Inclusief toeristen-, gemeente- en provincietaksen"
}

description is required and an info step has no productId. With style (DEFAULT or INFO) you choose the line style; the default is INFO. A formula that returns false leaves out the info line.

Formula

formula is TypeScript that, when an invoice is saved, returns true (one line with quantity), false (no line), a number (the quantity) or a list of lines { "quantity", "description", "unitPrice", "accessMethodValue" }. Which fields and lists you can read and what each form does is described under Formula per step. A formula that does not compile is rejected when the product is saved. In JSON the formula is a single string with \n for line breaks.

"formula": "return {customer.customFields.exemptFromSurcharges} !== true;"

NFC extension

Only useful if the main product has Extend Occupation Period as an action.

"extendAccessMethod": { "daysAfterPeriodEnd": 20 }

When a line from such a step is paid, Tillor sets the validity of the customer's access methods to the end date of the anchor line plus daysAfterPeriodEnd calendar days. By default this runs until 23:59 in the organisation's time zone, or until the weekday and time of Extend access method on the main product. Which types and statuses count is also set there. Without an end date on the anchor line, nothing happens. With Do not shorten on extend switched on, Tillor skips methods whose validity is already on or after the new date. An accessMethodValue on the line names the method of that line; the extension itself follows the settings of the main product.

After a successful extension, the invoice timeline shows a line that refers to the payment.

Example: explanation, NFC surcharge, meter rental and dogs

{
  "invoiceBundle": {
    "version": 1,
    "steps": [
      {
        "stepKind": "info",
        "description": "Inclusief toeristen-, gemeente- en provincietaksen"
      },
      {
        "productId": "prd_nfc_toeslag",
        "quantity": 1,
        "quantityScale": "anchor_line_quantity",
        "extendAccessMethod": { "daysAfterPeriodEnd": 20 },
        "style": "INFO",
        "billOncePerInvoice": true,
        "formula": "const nfc = {accessMethods}.filter((a) => a.type === \"NFC\" && a.status === \"ACTIVE\");\nreturn nfc.slice(0, 1).map((a) => ({ quantity: 0, unitPrice: 0, accessMethodValue: a.value, description: `Toegangsmethode inclusief: ${a.label}` }));"
      },
      {
        "productId": "prd_nfc_toeslag",
        "quantity": 1,
        "quantityScale": "anchor_line_quantity",
        "extendAccessMethod": { "daysAfterPeriodEnd": 20 },
        "billOncePerInvoice": true,
        "formula": "const nfc = {accessMethods}.filter((a) => a.type === \"NFC\" && a.status === \"ACTIVE\");\nreturn nfc.slice(1).map((a) => ({ quantity: 1, accessMethodValue: a.value, description: `Toeslag extra toegangsmethode: ${a.label}` }));"
      },
      {
        "productId": "prd_meterhuur",
        "quantity": 1,
        "quantityScale": "anchor_line_quantity",
        "formula": "return {meters}.filter((m) => m.provider === \"ICY\" && m.type === \"ELECTRICITY\" && m.icyMetadata?.switchState === true).map((m) => ({ quantity: 1, description: `Huur digitale elektriciteitsmeter ${m.name}` }));"
      },
      {
        "productId": "prd_hond",
        "quantity": 1,
        "quantityScale": "anchor_line_quantity",
        "billOncePerInvoice": true,
        "formula": "const dogs = Math.max(0, Math.floor(Number({customer.customFields.animals.dogs}) || 0));\nreturn dogs === 0 ? false : [{ quantity: dogs, description: `Honden (${dogs})` }];"
      }
    ]
  }
}

Note

Check that all productId values on product steps exist in your catalogue. Tillor skips steps with an unknown product.

See also

Was this page helpful?

On this page