PAY-9Required EU law
Show the lowest earlier price beside a discount
When you announce a price cut on goods to EU consumers, show as prior price the lowest price of the 30 days before. Compute every percentage or saving from that price.
Region
European Union (EU). The duty comes from the law there. The rule applies only when the project serves people there, and its Applies when also holds.
How to do it
Article 6a of the Price Indication Directive, added by the Omnibus Directive 2019/2161, sets this rule. A struck-through list price that nobody paid breaks it. Member states may set other periods for goods that spoil fast or are new on the market. Keep a history of every price with its start and end time, so the prior price comes from data. Compute it on the server when the discount starts, and store it with the discount. The EU Court of Justice ruled in Aldi Süd (C-330/23, 2024) that a "-40%" badge or a saving must also start from this price. A badge counted from the list price misleads. Log every price change as Log every staff action on money says.
Examples
SQL example
SELECT min(amount_minor) AS prior_price_minor
FROM price_history
WHERE product_id = $1
AND valid_from < $2
AND coalesce(valid_to, $2) > $2 - interval '30 days';
JavaScript example
const PRIOR_PRICE_WINDOW_MS = 30 * 24 * 60 * 60 * 1000
function priorPrice(history, saleStartsAt) {
const windowStart = saleStartsAt - PRIOR_PRICE_WINDOW_MS
const inWindow = history.filter(p => p.validFrom < saleStartsAt && (p.validTo ?? saleStartsAt) > windowStart)
return Math.min(...inWindow.map(p => p.amountMinor))
}
const reductionPercent = (priorMinor, saleMinor) => Math.round((1 - saleMinor / priorMinor) * 100)
Python example
from datetime import timedelta
PRIOR_PRICE_WINDOW = timedelta(days=30)
def prior_price(history, sale_starts_at):
window_start = sale_starts_at - PRIOR_PRICE_WINDOW
return min(p.amount_minor for p in history
if p.valid_from < sale_starts_at and (p.valid_to or sale_starts_at) > window_start)
def reduction_percent(prior_minor, sale_minor):
return round((1 - sale_minor / prior_minor) * 100)
How to check
A unit test sets a price of 200, then 150 for 10 days, then 200 again, then a sale at 120. The page shows 150 as the prior price and a reduction of 20%, not 40%. Every badge and saving on the page comes from the stored prior price.
Sources
Related rules
- PAY-57 Log every staff action on money in Payments and billing
- ECM-36 Apply discounts on the server in one fixed order in E-commerce and fulfilment
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/payments/price-display-and-consumer-law/price-reduction-prior-price">Agent Wranglers payments and billing playbook</a>. In code you write, add a comment next to it with the same link.