Pricing & promotions
Dynamic Pricing by Season (Hotels)
Indicative price, not an offer: €39/mo; seasonal rules, demand-based pricing, blackout dates
Generated from docs/plan/paid/dynamic-pricing-by-season/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
A paid module for hotels and seasonal businesses that sets different prices for different date ranges (winter €80/night, summer €150/night, shoulder season €110/night). Merchants define seasonal pricing rules in a calendar UI, and the storefront automatically selects the correct price based on checkout dates. Supports booking windows (e.g., advance bookings get 10% discount) and blackout dates.
The problem
A hotel’s revenue per night varies wildly by season: €80 in winter, €150 in summer, but they can only set one price in the system. Currently, they manually update prices monthly or lose margin (underpricing in season, overpricing off-season). Competitors with dynamic pricing capture 20–30% more revenue from the same inventory.
What it does
- Create seasonal pricing rules: date range (start_date, end_date), base_price, optional modifiers (advance_booking %, walk_in %, group_booking %)
- Calendar UI in admin: visual timeline showing seasons (color-coded bars: winter blue, summer red, shoulder gray)
- Set blackout dates: selected dates are unavailable for booking (e.g., maintenance, private event)
- Storefront checkout: customer selects check-in and check-out dates → system calculates price per night using rule matching the date range
- Booking window discount: if booked 30+ days in advance, apply advance_booking % discount to base_price
- Group booking rule: if 10+ rooms booked, apply group % discount (customer provides group size at checkout)
- Minimum stay rule: some seasons require 3-night minimum; enforce at checkout
- Audit log: every pricing rule change (created, edited, deleted) with timestamp and merchant who changed it
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.
- Real-time yield optimization (pricing algorithm that adjusts price per remaining inventory; too complex, no ML model)
- Occupancy forecasting or inventory syncing (merchant owns the inventory system; we only set prices)
- Payment plans or deposit requirements (payment flow is merchant/processor concern)
Data model
Add seasonal_pricing_rules table: { id, shop_id, season_name, start_date, end_date, base_price_minor_units, advance_booking_pct, walk_in_pct, group_size_threshold, group_pct, min_stay_nights }. Add blackout_dates table: { id, shop_id, date, reason }. Schema migration: new tables, no risk.
API
- POST /admin/seasonal-pricing — create seasonal rule
- GET /admin/seasonal-pricing — list all rules for the season
- PUT /admin/seasonal-pricing/:id — edit rule (date, price, modifiers)
- DELETE /admin/seasonal-pricing/:id — delete rule
- POST /checkout/calculate-price — { check_in_date, check_out_date, booking_days_advance, group_size } → returns nightly_price, total_price, rule_applied
- GET /admin/calendar/blackout-dates — list blackout dates
- POST /admin/calendar/blackout-dates — add blackout date
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Add ‘Seasonal Pricing’ section in Commerce > Pricing. Calendar view (month/year selector): shows seasons as colored bars, blackout dates as red X. Click date range to create/edit rule. Form: season name, start/end dates, base price, advance_booking % (e.g., 10 = 10% discount), group_size_threshold (e.g., 10 rooms), group % discount (e.g., 15%). Click single date to add blackout.
The seam — why this is paid
Paid module owns the seasonal pricing rules engine and the complex calendar/date-range logic. Core owns the checkout flow and price calculation. Merchants without this module can update prices manually via product schema.
Support commitment: pricing algorithm, revenue-management SLA, occupancy forecasting
Dependencies
- Checkout flow (must call pricing calculator based on dates selected)
- Product schema (nightly rate or booking product must exist)
- Audit log (to track pricing rule changes)
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.
- A rule ‘Summer’ (Jun 1 – Aug 31) with base_price = 15000 (€150) applies to bookings starting Jun 1
- A booking from Jun 15–Jun 18 (3 nights) at base price €150/night = €450 total
- If booked on May 10 (36 days in advance) with advance_booking_pct = 10, nightly price = €135 (10% discount), total = €405
- A blackout date on Jul 4 prevents checkout for check-in/out spanning Jul 4 (error: ‘date unavailable’)
- If min_stay_nights = 3 for summer, a 2-night booking returns error: ‘minimum 3 nights required’
- Editing a rule (e.g., changing summer price €150 → €160) does not retroactively change past bookings, only future
Risks
If date range matching is not exclusive (two overlapping rules both match), price is ambiguous (always apply earliest-defined rule, document this). If advance_booking discount is applied as absolute subtraction instead of percentage, calculation is wrong. If blackout dates are stored as strings instead of dates, comparison fails (store as ISO 8601).
Commercial context
| Suggested price | €39/mo; seasonal rules, demand-based pricing, blackout dates |
| Rival anchor | Airbnb built-in; custom: €2000+; Magento: no app |
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.