Skip to main content

Pricing / Line Items

Pricing mappings calculate prices for line items on contract and order entities as part of inbound ERP event processing. The system:

  1. Extracts line item data from the ERP payload
  2. Resolves product and price entity references by unique keys (similar to relations)
  3. Calls the epilot Pricing API (calculatePricingDetails) to compute totals, taxes, and breakdowns
  4. Persists the calculated line_items, totals, and total_details on the target entity

Line Item Typesโ€‹

Two types of line items are supported:

  • Product/price reference items -- Reference existing product and price entities by unique keys. The Pricing API fetches the full product/price data and computes amounts.
  • Custom items -- Provide a unit_amount directly without referencing a product or price entity. Still sent through the Pricing API for tax and total computation.

Configuration Structureโ€‹

The pricing property is an optional configuration on an entity mapping, at the same level as fields:

{
"entity_schema": "contract",
"unique_ids": ["external_id"],
"fields": [...],
"pricing": {
"jsonataExpression": "lineItems",
"items": [...],
"result_mapping": {...},
"on_error": "fail"
}
}
PropertyTypeRequiredDescription
jsonataExpressionstringNoJSONata expression to extract the line items array from the entity data. If omitted, the entity data itself is used.
itemsarrayYes*Array of line item configurations. Acts as a template when a single entry is provided. *Required unless items_jsonata is used.
items_jsonatastringNoJSONata expression that returns the full pricing API input array. Use for complex transformations. Mutually exclusive with items.
result_mappingobjectNoControls which entity attributes receive the pricing output. See Result Mapping.
on_errorstringNoError handling strategy: fail (default), skip, or warn. See Error Handling.

Product/Price Reference Itemsโ€‹

Reference existing product and price entities by unique keys. The system resolves entity IDs at runtime and passes them to the Pricing API.

{
"product": {
"entity_schema": "product",
"unique_ids": [
{ "attribute": "external_id", "field": "productCode" }
]
},
"price": {
"entity_schema": "price",
"unique_ids": [
{ "attribute": "external_id", "field": "tariffId" }
]
},
"quantity": { "field": "qty" }
}
PropertyTypeRequiredDescription
productPricingEntityRefYesReference to the product entity
pricePricingEntityRefNoReference to the price entity. If omitted, the product must have exactly one active price
quantityFieldValueYesNumber of units
unit_amount_overrideFieldValueNoOverride the price's unit_amount for this line item
description_overrideFieldValueNoOverride the line item description

PricingEntityRef follows the same pattern as relation unique IDs:

{
"entity_schema": "product",
"unique_ids": [
{ "attribute": "external_id", "field": "productCode" }
]
}

Each unique_ids entry supports the standard value sources:

  • { "attribute": "...", "field": "..." } -- extract from payload field
  • { "attribute": "...", "jsonataExpression": "..." } -- compute with JSONata
  • { "attribute": "...", "constant": "..." } -- fixed value

Custom Itemsโ€‹

Custom items define a unit_amount directly without referencing product/price entities. They are still sent through the Pricing API for consistent tax and total computation.

{
"_custom": true,
"description": { "field": "itemName" },
"unit_amount": { "field": "unitPriceCents" },
"unit_amount_currency": { "constant": "EUR" },
"quantity": { "field": "qty" },
"tax_rate": { "constant": 19 }
}
PropertyTypeRequiredDescription
_customtrueYesDiscriminator flag. Must be true
descriptionFieldValueYesLine item description
unit_amountFieldValueYesPrice per unit (integer, in cents)
unit_amount_decimalFieldValueNoPrice per unit as decimal string (e.g., "12.50"). Alternative to unit_amount
unit_amount_currencyFieldValueNoISO 4217 currency code. Defaults to EUR
quantityFieldValueYesNumber of units
typeFieldValueNoone_time or recurring. Defaults to one_time
billing_periodFieldValueNoRequired if type is recurring: weekly, monthly, every_quarter, every_6_months, yearly
tax_rateFieldValueNoTax rate percentage (e.g., 19 for 19%)

Items Configuration Modesโ€‹

Template Modeโ€‹

Provide one or more item configurations in the items array. When the jsonataExpression extracts an array of N data items:

  • Single config in items: The config acts as a template applied to every extracted data item.
  • N configs in items: Each config maps to the corresponding data item by index.

This is the simplest mode for homogeneous ERP arrays where every line item follows the same structure.

{
"pricing": {
"jsonataExpression": "positions",
"items": [
{
"product": {
"entity_schema": "product",
"unique_ids": [{ "attribute": "external_id", "field": "articleNumber" }]
},
"price": {
"entity_schema": "price",
"unique_ids": [{ "attribute": "external_id", "field": "priceKey" }]
},
"quantity": { "field": "quantity" }
}
]
}
}

With input:

{
"positions": [
{ "articleNumber": "PROD-A", "priceKey": "TARIFF-1", "quantity": 2 },
{ "articleNumber": "PROD-B", "priceKey": "TARIFF-2", "quantity": 1 }
]
}

The single template is applied to each position, producing two line items for the pricing API.

JSONata Modeโ€‹

Use items_jsonata for full control over the Pricing API input. The expression receives the extracted entity data and must return an array of objects matching the Pricing API's PriceItemsDto format.

{
"pricing": {
"items_jsonata": "$map(positions, function($v) { $v.type = 'product' ? { 'product_id': $v.epilotProductId, 'price_id': $v.epilotPriceId, 'quantity': $number($v.qty) } : { 'description': $v.label, 'unit_amount': $v.priceInCents, 'unit_amount_currency': 'EUR', 'quantity': $number($v.qty), 'is_composite_price': false } })"
}
}

This mode is useful when:

  • Line items need complex transformations that don't fit the template pattern
  • Product/price IDs are already resolved in the payload (no entity lookup needed)
  • You need conditional logic per item (e.g., different handling based on item type)
note

In JSONata mode, product/price entity resolution by unique keys is not performed automatically. If you need entity lookup, use template mode instead.

Value Extraction Patternโ€‹

All value fields in the pricing configuration (quantity, unit_amount, description, etc.) use the same extraction pattern as the rest of the mapping system:

ModeSyntaxDescription
Field reference{ "field": "qty" }Extract value from payload field. Supports JSONPath with $ prefix
JSONata expression{ "jsonataExpression": "$number(quantity) * factor" }Compute value with a JSONata expression
Constant{ "constant": "EUR" }Fixed value

Result Mappingโ€‹

By default, the Pricing API response is mapped to standard entity attributes. Customize the target attribute names using result_mapping:

{
"pricing": {
"items": [...],
"result_mapping": {
"line_items_attribute": "line_items",
"amount_subtotal_attribute": "amount_subtotal",
"amount_total_attribute": "amount_total",
"total_details_attribute": "total_details",
"currency_attribute": "currency"
}
}
}
PropertyDefaultDescription
line_items_attributeline_itemsEntity attribute for the calculated line items array
amount_subtotal_attributeamount_subtotalEntity attribute for the subtotal (before taxes). Set to null to skip
amount_total_attributeamount_totalEntity attribute for the total (after taxes). Set to null to skip
total_details_attributetotal_detailsEntity attribute for the full tax/recurrence breakdown. Set to null to skip
currency_attributecurrencyEntity attribute for the currency code. Set to null to skip

The pricing result attributes are merged with other field-mapped attributes. If a field mapping and the pricing result both set the same attribute, the pricing result takes precedence.

Error Handlingโ€‹

The on_error property controls what happens when pricing calculation fails:

StrategyBehavior
failDefault. The entity update fails and is retried via SQS dead-letter queue. Use when pricing data is critical
skipLog a warning and proceed with the entity update without pricing attributes
warnLog a warning, publish a monitoring event, and proceed without pricing attributes

Error scenarios:

  • Product/price not found -- A referenced product or price entity cannot be found by its unique keys. Unlike relations, missing products/prices are not auto-created (they are master data).
  • Pricing API failure -- The calculatePricingDetails call returns an error or times out.
  • Partial resolution failure -- If any line item's product/price cannot be resolved, the entire pricing calculation fails (all-or-nothing).

Processing Flowโ€‹

Pricing calculation happens during entity update processing (asynchronous, after extraction):

ERP Event Payload
โ”‚
Stage 1: Extraction
โ”œโ”€โ”€ Apply entity-level JSONata
โ”œโ”€โ”€ Map fields, relations, relation_refs
โ”œโ”€โ”€ Extract pricing data via pricing.jsonataExpression
โ””โ”€โ”€ Pass pricing config + extracted data in EntityUpdate message
โ”‚
Stage 2: Entity Update Processing
โ”œโ”€โ”€ Fetch existing entity
โ”œโ”€โ”€ Resolve relations
โ”œโ”€โ”€ Process pricing:
โ”‚ โ”œโ”€โ”€ Resolve product/price entity refs โ†’ product_id/price_id
โ”‚ โ”œโ”€โ”€ Build PriceItemsDto input
โ”‚ โ”œโ”€โ”€ Call calculatePricingDetails API
โ”‚ โ””โ”€โ”€ Merge result into entity attributes
โ”œโ”€โ”€ Create/update entity (with pricing attributes merged)
โ””โ”€โ”€ Queue post_actions if needed

Pricing resolution is asynchronous -- it does not block extraction of other entities. Products/prices are resolved at the same stage as relations, using the same entity lookup mechanism.

Examplesโ€‹

Contract with Product/Price Referencesโ€‹

Map ERP line items to a contract by looking up products and prices by external_id.

Configuration:

{
"entities": [
{
"entity_schema": "contract",
"unique_ids": ["external_id"],
"fields": [
{ "attribute": "external_id", "field": "contractId" },
{ "attribute": "contract_name", "field": "name" },
{
"attribute": "billing_contact",
"relations": {
"operation": "_set",
"items": [{
"entity_schema": "contact",
"unique_ids": [{ "attribute": "customer_number", "field": "customerId" }]
}]
}
}
],
"pricing": {
"jsonataExpression": "lineItems",
"items": [
{
"product": {
"entity_schema": "product",
"unique_ids": [{ "attribute": "external_id", "field": "productCode" }]
},
"price": {
"entity_schema": "price",
"unique_ids": [{ "attribute": "external_id", "field": "tariffId" }]
},
"quantity": { "field": "quantity" }
}
]
}
}
]
}

Input:

{
"contractId": "V-2024-001",
"name": "Stromvertrag Haushalt",
"customerId": "CUST-123",
"lineItems": [
{ "productCode": "STROM-HH", "tariffId": "BASIC-2024", "quantity": 1 },
{ "productCode": "NETZ-HH", "tariffId": "NETZ-STD", "quantity": 1 }
]
}

Processing:

  1. System extracts lineItems array via JSONata
  2. For each item, resolves the product and price by external_id
  3. Calls calculatePricingDetails with the resolved product/price IDs and quantities
  4. Sets line_items, amount_subtotal, amount_total, total_details, and currency on the contract entity

Custom Items (No Product/Price)โ€‹

Use custom items when the ERP provides pre-calculated prices that don't correspond to existing product/price entities.

Configuration:

{
"entities": [
{
"entity_schema": "order",
"unique_ids": ["external_id"],
"fields": [
{ "attribute": "external_id", "field": "orderId" },
{ "attribute": "status", "constant": "placed" }
],
"pricing": {
"jsonataExpression": "items",
"items": [
{
"_custom": true,
"description": { "field": "itemName" },
"unit_amount": { "field": "unitPriceCents" },
"unit_amount_currency": { "constant": "EUR" },
"quantity": { "field": "qty" },
"tax_rate": { "constant": 19 }
}
]
}
}
]
}

Input:

{
"orderId": "ORD-5678",
"items": [
{ "itemName": "Installation Fee", "unitPriceCents": 9900, "qty": 1 },
{ "itemName": "Monthly Service", "unitPriceCents": 2500, "qty": 12 }
]
}

The Pricing API computes taxes and totals for each custom item, and the order is updated with calculated line_items and totals.

Unit Amount Overrideโ€‹

Override the price entity's unit_amount with a value from the ERP payload. Useful when the ERP provides negotiated or custom prices.

{
"pricing": {
"jsonataExpression": "lineItems",
"items": [
{
"product": {
"entity_schema": "product",
"unique_ids": [{ "attribute": "external_id", "field": "productCode" }]
},
"price": {
"entity_schema": "price",
"unique_ids": [{ "attribute": "external_id", "field": "tariffId" }]
},
"quantity": { "field": "qty" },
"unit_amount_override": { "field": "negotiatedPriceCents" }
}
]
}
}

JSONata Items Expressionโ€‹

Use items_jsonata when you need full control over the Pricing API input.

Configuration:

{
"entities": [
{
"entity_schema": "order",
"unique_ids": ["external_id"],
"fields": [
{ "attribute": "external_id", "field": "orderId" }
],
"pricing": {
"items_jsonata": "$map(positions, function($v) { $v.type = 'product' ? { 'product_id': $v.epilotProductId, 'price_id': $v.epilotPriceId, 'quantity': $number($v.qty) } : { 'description': $v.label, 'unit_amount': $v.priceInCents, 'unit_amount_currency': 'EUR', 'quantity': $number($v.qty), 'is_composite_price': false } })"
}
}
]
}

Input:

{
"orderId": "ORD-9999",
"positions": [
{ "type": "product", "epilotProductId": "uuid-1", "epilotPriceId": "uuid-2", "qty": "2" },
{ "type": "fee", "label": "Shipping", "priceInCents": 499, "qty": "1" }
]
}

The JSONata expression maps each position to the Pricing API format, handling both product-referenced and custom items with full flexibility.

Validationโ€‹

  • pricing.items is required when items_jsonata is not provided, and vice versa. They are mutually exclusive.
  • Each product/price reference item must have a product with at least one unique_ids entry.
  • Each custom item (_custom: true) must have description, unit_amount, and quantity.
  • The pricing property is only meaningful on entities with pricing capability (typically contract or order), but this is not enforced at the mapping level.

Next Stepsโ€‹