- Checkout
- Features
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.
The fields on an order line
Section titled “The fields on an order line”Every line is validated on its own before the order’s totals are checked. These are the rules and limits for each field:
| Field | Required | Rules |
|---|---|---|
type | No | physical (the default), digital, discount, shipping_fee, sales_tax, gift_card, store_credit or surcharge |
reference | No | Your own product id. Up to 255 characters |
name | Yes | 1 to 255 characters. Shown to the customer, so see anonymizing |
quantity | Yes | A whole number, 0 or more |
quantity_unit | No | 1 to 8 characters, such as pcs or kg |
unit_price | Yes | Minor units, tax included, before any discount. At most 100000000 |
total_discount_amount | No | Minor units, tax included. 0 or more |
total_amount | Yes | quantity × unit_price − total_discount_amount. At most 100000000 |
tax_rate | Yes | Two implicit decimals, from 0 to 10000. 2500 is 25% |
total_tax_amount | Yes | Within ±1 of total_amount − total_amount × 10000 / (10000 + tax_rate). Negative on a discount line |
merchant_data | No | Up to 1024 characters of your own, returned on the order |
product_url, image_url | No | Up to 1024 characters each |
How Kustom validates tax on an order
Section titled “How Kustom validates tax on an order”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:
| Field | Level | Must satisfy |
|---|---|---|
tax_rate | Order line | The rate applied to that line, as a percentage with two implicit decimals. 2500 is 25% |
total_tax_amount | Order line | The tax contained in that line’s total_amount |
order_tax_amount | Order | The 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_taxline, if you use one. - Add or update the
shipping_feeline for the cost of delivery to that address. - Re-derive
order_amountandorder_tax_amountso 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.
Using SmartPost as a shipping option
Section titled “Using SmartPost as a shipping option”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.
Using a sales_tax order line
Section titled “Using a sales_tax order line”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 category | Product names that can reveal it | Typical merchants |
|---|---|---|
| Health data | Prescription medication, diagnostic tests, mobility aids, incontinence products, hearing aids | Pharmacies, medical supply, health and wellness |
| Sex life or sexual orientation | Intimacy products, sexual health items, LGBTQ+ themed merchandise | Sexual wellness, adult retail |
| Religious or philosophical beliefs | Religious texts, prayer items, explicitly halal or kosher labelled supplements, faith-based courses | Religious bookshops, specialty food, online courses |
| Racial or ethnic origin | Products whose name alone reveals a specific heritage | Cultural goods, heritage food, specialist cosmetics |
| Political opinions | Memberships, merchandise or publications tied to a named party or movement | Political organizations, media, advocacy groups |
| Trade union membership | Union membership packages, union-branded merchandise | Unions, professional associations |
| Genetic data | DNA testing kits, genetic screening services | Health tech, consumer diagnostics |
| Biometric data | Fingerprint or facial recognition enrollment services | Security 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.
How to anonymize a line
Section titled “How to anonymize a line”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 category | Recommended name |
|---|---|
| Pharmacy, prescription or OTC medication | Health product (anonymized) |
| Mental health | Health product (anonymized) |
| Addiction support | Health product (anonymized) |
| Genetic testing or diagnostics | Health product (anonymized) |
| Sexual wellness | Wellness product (anonymized) |
| Disability or care aids | Care product (anonymized) |
| Religious or faith-based goods | Product (anonymized) |
| Ethnicity or cultural identity goods | Product (anonymized) |
| Political membership or publications | Product (anonymized) |
| Trade union membership or branded goods | Product (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.
Implementing it, and living with it
Section titled “Implementing it, and living with it”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.
Related
Section titled “Related”- Server-side callbacks — recalculate the cart when the shipping address changes.
- Troubleshooting Checkout — decode a
BAD_VALUEnaming an order line field. - Saved payment methods — the token a recurring order is charged against.
- Create an order — where
order_linesgoes on the request. - Checkout API reference — every order line field and its constraints.
This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.