Skip to content

Order lines and tax

What Kustom validates on the order_lines array — the rules and limits on each field, tax amounts that have to reconcile across every line and against the order totals, and how to anonymize product names that would reveal special category data about the customer.

order_lines is the part of a Kustom order that fails most often, because it’s the part Kustom checks arithmetic on. Tax has to reconcile across every line and against the order totals, and product names travel further than merchants expect — onto the confirmation page and into payment method communications. This page collects the rules for both.

Every line is validated on its own before the order’s totals are checked. These are the rules and limits for each field:

FieldRequiredRules
typeNophysical (the default), digital, discount, shipping_fee, sales_tax, gift_card, store_credit or surcharge
referenceNoYour own product id. Up to 255 characters
nameYes1 to 255 characters. Shown to the customer, so see anonymizing
quantityYesA whole number, 0 or more
quantity_unitNo1 to 8 characters, such as pcs or kg
unit_priceYesMinor units, tax included, before any discount. At most 100000000
total_discount_amountNoMinor units, tax included. 0 or more
total_amountYesquantity × unit_price − total_discount_amount. At most 100000000
tax_rateYesTwo implicit decimals, from 0 to 10000. 2500 is 25%
total_tax_amountYesWithin ±1 of total_amount − total_amount × 10000 / (10000 + tax_rate). Negative on a discount line
merchant_dataNoUp to 1024 characters of your own, returned on the order
product_url, image_urlNoUp to 1024 characters each

Kustom checks the tax on every order it receives and rejects the order if your numbers don’t add up. Three fields are checked against each other:

FieldLevelMust satisfy
tax_rateOrder lineThe rate applied to that line, as a percentage with two implicit decimals. 2500 is 25%
total_tax_amountOrder lineThe tax contained in that line’s total_amount
order_tax_amountOrderThe sum of every line’s total_tax_amount, plus the total_amount of any sales_tax line

A mismatch comes back as 400 BAD_VALUE naming the exact field — order_lines[2].total_tax_amount, order_tax_amount, order_lines[0].tax_rate. See Troubleshooting Checkout for how to read those messages. When the numbers were right a moment ago and are wrong now, compare the last successful update against the failing request: discounts, coupons, shipping fees and gift cards are the usual culprits, because each of them adds or changes a line.

Amounts are always in minor units. 25000 is 250.00 SEK, never 25000 SEK.

Field-by-field definitions live in the Checkout API reference.

Recalculating tax when the shipping address changes

Section titled “Recalculating tax when the shipping address changes”

Sales tax often depends on where the goods are going, and at the moment you create the order you may not know. So don’t guess: create the order with a tax figure you can defend — frequently zero, or a default sales_tax line — and correct it once the customer supplies an address.

That correction happens through the address_update callback. Register it in merchant_urls, and Kustom will POST you the current order_amount, order_tax_amount, order_lines, billing_address and shipping_address whenever the customer enters or edits an address. Reply within 10 seconds with the recalculated cart, the valid shipping options or both. A late reply, a non-200 or an invalid cart stops the purchase and shows the customer an error.

The mechanics are on Server-side callbacks. What matters here is what you change in the response:

  • Recompute sales tax for the new address.
  • Add or update the sales_tax line, if you use one.
  • Add or update the shipping_fee line for the cost of delivery to that address.
  • Re-derive order_amount and order_tax_amount so they still reconcile.

If the customer enters only one address, it’s used as both the billing and the shipping address. If your shipping selector lives inside the checkout iframe, Kustom adds the shipping_fee line and moves the totals for you — unless you register shipping_option_update, which hands that line to you. If it lives on your own page, Kustom knows nothing about the choice — put the line and the new totals into the cart yourself.

SmartPost needs two things from the checkout: the pickup point address and the phone number the PIN is sent to. Send the SmartPost address to Kustom as a shipping option before the checkout loads, so Kustom can pass it to the payment method for risk assessment, and use the phone number the customer enters in the checkout for the PIN.

Turn off separate shipping and billing addresses for these transactions. Otherwise the customer is asked for a shipping address they have already chosen as a pickup point.

This is the tax-exclusive pattern US sales tax needs, not the European default. Everywhere Kustom operates in Europe, tax is included in each line’s total_amount and carried on the line as total_tax_amount — the table above. Where you need control over rounding, jurisdiction rules or how tax is presented, put the tax in its own order line of type sales_tax and set tax_rate to 0 on the goods lines.

A sales_tax line is constrained: quantity must be 1, and tax_rate, total_tax_amount and total_discount_amount must all be 0. Its total_amount is what counts toward order_tax_amount, which is why the example below balances with every total_tax_amount at zero.

{
"order_amount": 11900,
"order_tax_amount": 1900,
"order_lines": [
{
"type": "physical",
"name": "Product 1",
"quantity": 1,
"unit_price": 10000,
"tax_rate": 0,
"total_amount": 10000,
"total_tax_amount": 0
},
{
"type": "sales_tax",
"reference": "SALES_TAX",
"name": "Sales Tax 19%",
"quantity": 1,
"unit_price": 1900,
"tax_rate": 0,
"total_amount": 1900,
"total_tax_amount": 0
}
]
}

The pattern is deliberate: the tax is a line item priced at the tax amount, and every line’s total_tax_amount is zero, so the only place tax appears is that one line. Mixing the two styles in the same order is what produces order_tax_amount mismatches.

Anonymizing product names that reveal special category data

Section titled “Anonymizing product names that reveal special category data”

order_lines[].name reaches Kustom, appears on the customer’s confirmation page and turns up in post-purchase communications from payment methods. For most catalogues that’s harmless. If your catalogue can reveal something about the customer that GDPR Article 9 treats as a special category, sending the real product name is a compliance problem — and you’re the data controller for what you send.

Article 9 categoryProduct names that can reveal itTypical merchants
Health dataPrescription medication, diagnostic tests, mobility aids, incontinence products, hearing aidsPharmacies, medical supply, health and wellness
Sex life or sexual orientationIntimacy products, sexual health items, LGBTQ+ themed merchandiseSexual wellness, adult retail
Religious or philosophical beliefsReligious texts, prayer items, explicitly halal or kosher labelled supplements, faith-based coursesReligious bookshops, specialty food, online courses
Racial or ethnic originProducts whose name alone reveals a specific heritageCultural goods, heritage food, specialist cosmetics
Political opinionsMemberships, merchandise or publications tied to a named party or movementPolitical organizations, media, advocacy groups
Trade union membershipUnion membership packages, union-branded merchandiseUnions, professional associations
Genetic dataDNA testing kits, genetic screening servicesHealth tech, consumer diagnostics
Biometric dataFingerprint or facial recognition enrollment servicesSecurity tech, access control

If you’re unsure whether a product qualifies, anonymize it. Over-anonymizing costs you a little operational clarity. Under-anonymizing is a compliance failure.

Replace name with a generic label and keep your own product id in reference, so you lose nothing you need for reconciliation, fulfillment or disputes.

Avoid — this exposes health data:

{
"name": "Sertraline 50mg Tablets - 28 pack",
"reference": "100482",
"quantity": 1,
"unit_price": 18900,
"total_amount": 18900,
"tax_rate": 0,
"total_tax_amount": 0
}

Send this instead:

{
"name": "Health product (anonymized)",
"reference": "100482",
"quantity": 1,
"unit_price": 18900,
"total_amount": 18900,
"tax_rate": 0,
"total_tax_amount": 0
}

In a mixed cart, anonymize only the sensitive lines. Ordinary products keep their real names.

Product categoryRecommended name
Pharmacy, prescription or OTC medicationHealth product (anonymized)
Mental healthHealth product (anonymized)
Addiction supportHealth product (anonymized)
Genetic testing or diagnosticsHealth product (anonymized)
Sexual wellnessWellness product (anonymized)
Disability or care aidsCare product (anonymized)
Religious or faith-based goodsProduct (anonymized)
Ethnicity or cultural identity goodsProduct (anonymized)
Political membership or publicationsProduct (anonymized)
Trade union membership or branded goodsProduct (anonymized)

Keep the word (anonymized) in the label. It tells your own operations team, and anyone reviewing the order, that the label was chosen deliberately rather than being a data error. A bare “product” or “item” reads as an integration bug.

Flag sensitive products in your catalogue — a boolean such as is_sensitive — and swap the name in the code that builds the Kustom payload, not in your database. Keep the real name in your own systems. You need it for fulfillment, returns and disputes. Then place a test order in Playground and confirm the anonymized label is what appears on the confirmation page and in Developer Logs.

Two consequences to plan for. Customers will see the generic label on their payment confirmation, so say so somewhere they will read it — a line in your order confirmation email such as “For your privacy, sensitive product names are not shown in payment confirmations” is usually enough. And if a dispute arises, the evidence you submit through the Kustom Portal is separate from the API payload: reference your own records, including the real product name, in the evidence itself. Your product id in reference is what lets you look it up.

This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.