Pricing & promotions
Omnibus 30-day lowest-price display
Generated from docs/plan/core/omnibus-30-day-lowest-price-display/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Tiering note. STORING price history is core — one table, one write on save. The maintained per-country Omnibus display and audit pack is the paid half. A merchant must never be unable to comply with the law because they did not pay.
EU Omnibus regulation requires merchants to display the lowest price from the past 30 days when advertising a discount. This free, core feature adds price-history tracking, 30-day lowest-price lookup, compliance reporting, and storefront badge display.
The problem
No price-history table. EU Omnibus (2023/2373) requires merchants to show the lowest price offered in the past 30 days when advertising any discount. Without price history, merchants cannot prove compliance; regulators can fine non-compliant shops. This is a legal obligation across EU, not optional.
What it does
- Track daily product prices in PriceHistory table: product_id, price (integer minor units), recorded_at (timestamp)
- Automatically record price snapshot on every product price change (on update event, not time-based polling)
- Retrieve 30-day lowest price for a product via query API
- Calculate discount % and display on storefront when current price < lowest 30d price
- Admin compliance validator: ‘Current €X, lowest 30d €Y—discount is %Z. Claim valid? Y/N’
- Compliance report (downloadable): products currently on discount, current price vs. 30d lowest, discount %, status (valid/warning/invalid)
- Price-change audit log: show which prices were active on which dates
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Does not enforce maximum discount size (that’s merchant policy; system shows the data only)
- Does not auto-revert prices that look suspicious (e.g., artificially low baseline)—merchant responsibility; system can flag with warning
- Does not build a price-claim audit/approval workflow (e.g., merchant must submit claim for review)—that’s a compliance module, separate
- Does not integrate external price monitors (e.g., Keepa for Amazon)—merchant loads prices manually or via their own sync
- Does not handle regional price differences (e.g., DE vs. FR)—each region is a separate shop/currency; merchants manage regions separately
- Does not generate Omnibus compliance certificates or legal disclaimers—those are merchant’s responsibility with counsel
Data model
New entity: PriceHistory with product_id, price (integer minor units), recorded_at (timestamp). Add composite index on (product_id, recorded_at DESC) for efficient 30-day range queries. No migration data: history starts from schema creation forward. Archive/retention policy: decide whether to keep history indefinitely or archive records older than 13 months (52 weeks + buffer for claim disputes).
API
- GET /admin/api/products/:id/price-history?days=30 — list price changes in past N days (default 30), ordered by recorded_at DESC
- GET /admin/api/products/:id/lowest-price?days=30 — get lowest price and date within past N days
- GET /store/api/products/:id/price-compliance — show current price, lowest 30d price, discount %, compliance status (for storefront display)
- POST /admin/api/products/:id/record-price — manually record price snapshot (triggers automatically on price update; manual call for edge cases)
- GET /admin/api/compliance/report — export compliance report: products on discount vs. 30d history
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Product detail view: 30-day price chart (sparkline or line graph). Discount claim validator: input current price and claimed discount%; system shows lowest 30d price and displays ‘Valid’/‘Warning’/‘Invalid’ badge. Compliance report: download CSV listing all products currently on discount, current price, lowest 30d, discount %, compliance status. Report includes timestamp of report generation and merchant name (for audits).
The seam — why this is core
Core owns: product entity, price field. Core owns: PriceHistory tracking and 30d-lowest-price queries (this is a statutory obligation, not optional). Core owns: compliance reporting (merchants must be able to prove compliance to regulators). Why: Omnibus is a hard legal requirement across EU; merchants must comply or face fines; no merchant can opt out. No paid pack should offer ‘compliance as a service’—that’s core.
EU compliance obligation. Merchants need legal review and price-hold enforcement.
Dependencies
- Product entity and price field must exist
- Price-change event hook must exist (or be added): system must capture and store price change immediately
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Product price is €100; on price change, PriceHistory record created automatically with price=100 and current timestamp
- Query lowest-price for past 30 days returns €100 (single price in history)
- Product price drops €75 (25% off); PriceHistory record created with price=75
- Query lowest-price in past 30d now returns €75 (most recent lowest)
- Storefront shows ‘Was €100, now €75’ with ‘25% off’ badge; lowest-price call returns €100 (from prior period), so display is: ‘Lowest 30d: €100’
- Lowest 30d is €85 (recorded on day 15); current is €75 (recorded today); compliance claim must show €85 as reference, not €100
- Admin compliance report shows: Product A (current €75, lowest €100, discount 25%) with ‘Valid’ badge
- Admin compliance report shows: Product B (current €50, lowest €51, discount 2%) with yellow ‘Warning’ badge (very small discount, possible artificial baseline)
- PriceHistory query filtered to 30 days returns only records with recorded_at >= (now - 30 days)
- Price recorded 40 days ago does not appear in 30d query
- Admin manually records price snapshot for product; new PriceHistory row created with manual_flag=true (optional, for auditing)
Risks
If price-history records are not created on EVERY price change (bulk updates, API, direct DB), merchants get false compliance claims; audit all price-update paths (product detail edit, bulk CSV upload, API calls, direct SQL). Daily snapshot strategy (midnight cron) leaves gaps between updates; instead use event-based recording (on price-change hook)—far more reliable. PriceHistory growth: over 5 years, a popular product with frequent price changes could have 100K+ records; add retention policy (e.g., archive records >24 months) or implement data summarization (daily low/high instead of every change). Soft-delete: if merchant deletes product, decide whether to cascade-delete PriceHistory or keep for audit trail; recommend keep (soft delete product, preserve history for compliance audits).
Commercial context
| Suggested price | €49–99/month |
| Rival anchor | Magento: not native; Ninebits Omnibus module used in production. |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.