commercetools
The webshop platform, and the only place a discount's reason survives, because the middleware drops it before NetSuite.
The webshop platform. CCC takes discount information from it: the cart discounts that exist, and which of them came off each sales order.
Why CCC reads it at all
NetSuite already carries the money. What it does not carry is why an order was discounted: the middleware between the webshop and NetSuite drops the discount reason, so NetSuite never receives it.
commercetools is the only place that reason exists. That is the whole justification for this source. Without it a discounted order shows a smaller amount with no way to attribute it to a campaign, a code or a rule.
How CCC reaches it
A GraphQL API with its own client model, authenticated by a token CCC caches in the tokens table under commercetools. The token lives 48 hours and is refreshed at 24, half its life, so a refresh failure has a day of slack before anything breaks.
Its credentials use the CTP_* prefix in .env, which is the platform's own convention rather than the source-name pattern the other groups follow. The scopes requested are read-only: viewing orders, discount codes and cart discounts.
What it delivers
| Feed | Grain | Notes |
|---|---|---|
| Cart discounts | One per configured discount | The definitions, with their localised names |
| Order cart discounts | One row per order and cart discount | Summed from line-item discounted quantities |
The second is more detailed than it first looks. A discount is resolved down to the discounted price per quantity on each line item, and the discount codes that triggered each cart discount are collected per order. So one order can carry several discounts, and one discount can be reached through several codes.
Cadence and window
The definitions refresh less often than the applications, which follow orders closely.
It re-reads a trailing window rather than each day once. The order discount feed is one of the two sources ingest.trailingqueuedays re-queues, currently 14 days, because the webshop keeps adjusting recent days. A discount figure from last week can therefore still move. See configuration and boundaries.
The client requests orders by createdAt for the batch date. Earlier days can be queued manually; this does not guarantee that the upstream platform retains every historic order.
Quirks and limits
- A discount code is a voucher, not an identity. It does not identify who used it, and reading it as though it does is wrong in a way that looks plausible.
- A cart discount can have no name, and the import counts those separately rather than discarding them. A nameless discount is real data, not a fault.
- Names are localised, so a single discount carries several and one has to be chosen for display.
- The discount CCC records is what the webshop applied. It is not the same number as the revenue reduction booked in the ledger, which arrives through NetSuite. Do not expect the two to reconcile line for line: CCC does not reconcile them.
Where it lands
ct_-prefixed tables. ct_cartdiscounts is refreshed by upsert rather than by the swap pattern the other reference imports use, which is why it is defined in the installer like an ordinary table rather than by its own job.
Classification reads the imported applications and sets discountcode when a code is present, or discountcart when it is absent. Both can be set on one order. These flags reach rule filters; the detailed promotion amounts and definitions are not themselves rule-builder fields. An ambiguous code stored as NULL therefore contributes to discountcart. Transaction discount lines remain the monetary input, separate from these promotion flags.
Related
- Discounts
- NetSuite, which carries the money but not the reason
- Configuration and boundaries
Evidence
Client, token lifetime and scopes from app/models/commercetools.php. Feed shapes and the per-quantity resolution from app/jobs/commercetools/getcartdiscounts.php and getordercartdiscounts.php. The dropped discount reason, the CTP_* prefix, the token half-life and the upsert-not-swap decision from AGENTS.md. Trailing window from config/settings.json. Basis: code-checked against the current implementation.
Only the trailing window is re-queued, so a day older than that is refreshed by queueing it by hand. The webshop discount and ledger revenue are not reconciled anywhere in CCC: the discount reason is carried for analysis, and the amounts that reach margin are the transaction's own discount lines.
- Type
source - Status
active - Updated
2026-09-14 - Created
2026-09-13