Recomputing is not answering
The tempting implementation is to re-run the pricing engine against the order
and show the result. It does not work, and the reason is worth stating plainly.
Prices change. Campaigns start and end. Rules get edited. Recomputing an order
from March against today's configuration produces a number that is correct for
today and has nothing to do with what the customer was charged. If it happens to
match, you have learned nothing; if it does not, you cannot tell whether the
original was wrong or the world moved.
So the price has to be explained from a record made at the time, not
reconstructed. That single requirement determines most of the design.
Record the resolution, not just the result
What gets stored with the order is not a total and a discount. It is the
sequence of decisions that produced them.
Order ORD-77120, priced 2026-03-14T10:22:41Z, engine config v148
line 1 SKU-2201 qty 3 list 20.00 each = 60.00
rule PROMO-BUY3PAY2 (multi-buy) -20.00 applied to line
rule CODE-SPRING10 (10% basket) n/a excluded by PROMO-BUY3PAY2
line 2 SKU-8890 qty 1 list 45.00 = 45.00
rule CODE-SPRING10 (10% basket) -4.50 applied to line
basket subtotal 105.00 discount 24.50 total 80.50
coupon SPRING10 consumed, 1 of 1 uses for customer C-4471
Three things are being captured that people leave out. Which rules were
considered and rejected, with the reason — "excluded by" is the answer to
"why didn't my code work", which is the most common contact of all. The engine
configuration version, so the rule definitions themselves are recoverable.
And the discount attributed per line, which is the next section and the one
with real money attached.
Line-level attribution, because of returns
Recording a single basket-level discount is easier and is wrong, and the reason
is not tidiness — it is that returns become uncomputable.
Basket: 3 shirts at 20.00, promotion "buy three, pay for two"
Charged: 40.00
Customer returns one shirt. What is the refund?
Naive refund 20.00, leaving 2 shirts for 20.00 total.
The promotion is still applied to a basket that no longer
qualifies, and the customer has two shirts for the price of one.
Correct the remaining 2 shirts do not qualify, so they cost 40.00.
The customer already paid 40.00, so the refund is 0.00.
Neither number is obtainable from a basket-level total. You need to know how
much discount each line carried and whether the promotion's condition still
holds after the return, which means the promotion's qualifying condition has
to be stored alongside the attribution rather than inferred.
The generalisation is that a return does not reverse a payment, it unwinds a
priced basket — and unwinding requires the pricing to have been recorded at the
granularity the return operates on.
Order and exclusivity are configuration, and they are contested
Once there is more than one campaign, the outcome depends on decisions nobody
made explicitly.
Does a percentage off a category apply before or after a basket-threshold
amount? Can a multi-buy and a coupon both apply to the same line? If two
campaigns are eligible and mutually exclusive, which wins? Is the customer
entitled to the best available combination, or to the first that matched?
These have real money attached and no default is neutral. They belong in
configuration with an owner and an effective date, not in the order the rules
happened to be evaluated, and the resolution order should appear in the record
above so that a dispute about the outcome is a dispute about a stated rule.
The answer to "who decides" is a pricing or trading function, not engineering.
What engineering owes them is that the decision is expressible and visible.
A simulator is what makes it safe
Once a promotion engine has grown teeth — stacking, priority, per-customer
limits, exclusions, date windows — nobody can predict what a configuration will
do, including the person who wrote it. Rules interact, and the interactions are
where the expensive mistakes live: the combination that takes a basket to zero,
the code traded on a deals forum faster than you can revoke it.
The mitigation is a simulator: run a proposed configuration against a corpus
of real historical baskets and report what would change. It answers the
question a merchandiser cannot otherwise answer — what does this actually do —
before it is live rather than after, and it catches the interaction nobody
imagined because the corpus contains baskets nobody imagined.
The same corpus doubles as a regression suite, in the way a rating engine's
replay corpus does in insurance. A change aimed at one campaign that moves the
price of an unrelated one is caught by a failing comparison rather than by a
customer.
Why this is not optional
Worth naming the driver, because it explains why the effort is funded. An
unexplainable price is a problem in three separate directions: a customer
service agent who cannot tell someone why they were charged, a finance team that
cannot reconcile discount spend to campaigns, and — where a promotion was
advertised on particular terms — a conduct question about whether the customer
got what was offered.
Being able to produce the block at the top of this page, for any order, from a
support screen, is the deliverable.
Prices and campaigns move, so an order's price can only be explained from what
was recorded when it was placed. Store the rules that fired, the ones that were
excluded and why, and attribute every discount to a line.
© 2026 Preptima. Originally published at preptima.com (opens in a new tab).