advanced
shop-rentals
pricing
group-discounts

Group / Tiered Pricing

Quantity-based discounts that apply automatically when group bookings hit configured thresholds — JSON-driven, no per-booking work

Levy Fleets TeamMay 7, 20264 min read

Group / tiered pricing applies an automatic percent discount when a single reservation has quantity above a threshold. Useful for encouraging tour groups, schools, or corporate outings to book together instead of as individuals.

How it works

Each pricing tier has a group_pricing_tiers JSONB column. The value is an array of objects:

[
  { "min_quantity": 5, "discount_percent": 10 },
  { "min_quantity": 10, "discount_percent": 20 },
  { "min_quantity": 25, "discount_percent": 30 }
]

When the pricing engine calculates the cost for a reservation:

  1. It computes the per-unit base cost (hourly × hours, or daily × days, etc.)
  2. It looks up the highest applicable threshold (min_quantity ≤ reservation.quantity)
  3. Applies that threshold's discount_percent to the per-unit cost
  4. Multiplies by quantity for the total

Examples (with hourly $10, 2-hour rental):

QuantityThreshold appliedPer-unitTotal
1none$20$20
4none$20$80
510% off$18$90
910% off$18$162
1020% off$16$160
2530% off$14$350

Note quantity=10 is cheaper total than quantity=9 — that's intentional to incentivize larger groups.

Setting it up

Today the dashboard pricing UI doesn't expose the JSON column directly. Set via SQL or the database editor:

UPDATE reservation_pricing_tiers
SET group_pricing_tiers = '[
  {"min_quantity": 5, "discount_percent": 10},
  {"min_quantity": 10, "discount_percent": 20}
]'::jsonb
WHERE id = '<your-tier-id>';

A native "Group discounts" UI on the pricing page is on the roadmap.

Discount stacking rules

  • Highest threshold wins. The 30% threshold replaces the 20% — they don't add.
  • Group discount stacks with promo codes. A 10% group discount plus a 5% promo code = ~14.5% net (multiplicative). Most operators prefer to see the promo code as the smaller of the two and let customers stack.
  • Group discount applies before tax. The discounted subtotal is what tax is calculated on.

Where it shows

On the booking detail

The financial summary shows the per-unit base cost reflecting the discount. There's no explicit "Group discount: -X%" line — the discount is folded into the base rate.

This is a small UX gap. To make the discount visible, add a custom adjustment_reason via SQL or update the pricing snapshot manually.

On the public booking page

The public page shows the estimated total reflecting the group discount automatically. Customers don't see the percent off explicitly, but they see a lower per-bike total.

On the receipt

Same — the receipt shows the discounted base cost, not the original. For "Group discount of $X" callout on receipts, that's a roadmap feature.

Edge cases

Reservation with quantity = 1 but with group thresholds set

The system looks for the highest threshold ≤ quantity. With quantity=1, no threshold applies. Customer pays full per-unit price.

Negative discount values

The schema doesn't validate, but the pricing engine clamps to a max of 100% off. A negative value would error or be ignored — don't enter negatives.

Threshold larger than your max bike inventory

If you have 10 bikes total and set a 25-bike threshold, no one can ever hit it. The threshold is just unreachable — no harm done.

Same threshold appearing twice

Last one wins (JSON object keys / array iteration). Don't do this. Each threshold should be unique.

Reporting

Track how often group discounts apply:

SELECT
  date_trunc('month', pickup_at) AS month,
  count(*) FILTER (WHERE quantity &gt;= 5) AS group_bookings,
  sum(quantity) FILTER (WHERE quantity &gt;= 5) AS total_riders,
  sum(total_cents) FILTER (WHERE quantity &gt;= 5) / 100.0 AS group_revenue
FROM reservations
WHERE subaccount_id = '<your-subaccount-id>'
  AND pickup_at &gt;= '2026-01-01'
  AND status NOT IN ('cancelled', 'no_show', 'expired')
GROUP BY 1
ORDER BY 1 DESC;

If group bookings are growing month-over-month, your pricing strategy is working.

Common configurations

Family-friendly shop

[
  { "min_quantity": 4, "discount_percent": 10 }
]

Encourages families to book together.

Tour shop

[
  { "min_quantity": 6, "discount_percent": 10 },
  { "min_quantity": 12, "discount_percent": 15 },
  { "min_quantity": 20, "discount_percent": 25 }
]

Multiple tiers for different tour group sizes.

School / corporate booking

[
  { "min_quantity": 10, "discount_percent": 20 },
  { "min_quantity": 50, "discount_percent": 35 }
]

Bigger jumps to incentivize large bookings.

What's NOT in group pricing

  • Quantity-based pricing per add-on. Add-ons are flat per-unit; groups don't get a discount on their helmet purchases.
  • Time-based group thresholds. A "weekend group of 10" doesn't get a special tier on top of the existing tiered pricing.
  • Individual rider name discounts. All riders in a group reservation share one rate.

These are workarounds via separate tiers, but not natively in this column.