Skip to main content

Pricing Strategies

A VAS row is priced by a (price, pricing_type, pricing_config) triple on vas_cost (catalog baseline), channel_value_added_service (channel override) or listing_channel_value_added_service (listing override).

  • price — the simple-case numeric. Used as the unit price for per-unit strategies, or the flat fee for FIXED.
  • pricing_type — one of FIXED · PER_PERSON · PER_ITEM · PER_QUANTITY · PER_HOUR · PER_KM · BASE_PLUS_OVERAGE · TIERED · ON_ACTUALS.
  • pricing_config — JSONB. Optional for simple strategies, required for the complex ones. Deserialized into a sealed PricingConfig subtype keyed on type.

The Sealed Hierarchy

PricingConfig (sealed)
├─ FixedPricing → just price
├─ PerUnitPricing → price × units (chosen unit)
├─ BasePlusOveragePricing → base envelope + overage rates
├─ TieredPricing → slab pricing, ordered tiers
└─ OnActualsPricing → booked at zero, reconciled later

When pricing_config can be omitted

For FIXED and the four PER_* types the price column alone is enough — leave pricing_config NULL. Storing the structured config explicitly is still useful when you want UI to show the strategy alongside the row.


FIXED — FixedPricing

One flat price per booking, regardless of guests/quantity.

Schema

{ "type": "FIXED" }

Example: Bonfire at ₹2,500 flat

POST /api/v1/admin/vas-costs
Content-Type: application/json
{
"vasId": "BONFIRE",
"tagName": "goa-peak",
"price": 2500.00,
"pricingType": "FIXED"
}

pricing_config is left NULL. The total is ₹2,500 whether guests order one or ten bonfires (treat multiples as separate cart items).


PER_UNIT — PerUnitPricing

price × unit_count where the unit is one of PER_PERSON, PER_ITEM, PER_QUANTITY, PER_HOUR, PER_KM.

Schema

{ "type": "PER_UNIT", "unit": "PER_PERSON" }

The unit field on the config must match the row's pricing_type. If you store pricing_type = PER_PERSON you can omit pricing_config entirely — the row already declares the unit.

Example: BBQ at ₹800 per person

{
"vasId": "BBQ_2V_2NV",
"tagName": "goa-peak",
"price": 800.00,
"pricingType": "PER_PERSON"
}

Eight guests = ₹6,400.

Example: High Tea at ₹400 per person, with explicit config

{
"vasId": "HIGH_TEA",
"tagName": "goa-peak",
"price": 400.00,
"pricingType": "PER_PERSON",
"pricingConfig": {
"type": "PER_UNIT",
"unit": "PER_PERSON"
}
}

BASE_PLUS_OVERAGE — BasePlusOveragePricing

A base fee for an included envelope (hours + km), then per-unit overage rates for anything beyond it. The textbook case is car rentals.

Schema

{
"type": "BASE_PLUS_OVERAGE",
"baseHours": 4,
"baseKm": 40,
"perExtraHour": 200.00,
"perExtraKm": 12.00
}

baseHours / baseKm here are informational — the actual envelope is also captured on TransportAttributes for display. Keep them aligned.

Example: "Premium Sedan – Swift Dzire, 4 Hours / 40 KMs" at ₹1,800 base + overage

{
"vasId": "PREMIUM_SEDAN",
"variantId": "SWIFT_DZIRE_4H_40KM",
"tagName": "goa-peak",
"price": 1800.00,
"pricingType": "BASE_PLUS_OVERAGE",
"pricingConfig": {
"type": "BASE_PLUS_OVERAGE",
"baseHours": 4,
"baseKm": 40,
"perExtraHour": 200.00,
"perExtraKm": 12.00
}
}

A 6-hour, 55-km trip costs:

₹1,800            (base envelope)
+ 2 × ₹200 (2 hours over baseHours)
+ 15 × ₹12 (15 km over baseKm)
= ₹2,380

Example: 8-hour variant at higher base

{
"vasId": "PREMIUM_SEDAN",
"variantId": "SWIFT_DZIRE_8H_80KM",
"tagName": "goa-peak",
"price": 3200.00,
"pricingType": "BASE_PLUS_OVERAGE",
"pricingConfig": {
"type": "BASE_PLUS_OVERAGE",
"baseHours": 8,
"baseKm": 80,
"perExtraHour": 180.00,
"perExtraKm": 10.00
}
}

TIERED — TieredPricing

Slab pricing. Units are evaluated against ordered tiers and the matching tier's pricePerUnit is applied. The last tier can leave toUnitsInclusive null to mean "and beyond".

Schema

{
"type": "TIERED",
"tiers": [
{ "fromUnits": 1, "toUnitsInclusive": 4, "pricePerUnit": 800.00 },
{ "fromUnits": 5, "toUnitsInclusive": 10, "pricePerUnit": 700.00 },
{ "fromUnits": 11, "toUnitsInclusive": null, "pricePerUnit": 600.00 }
]
}

The pricing_type for tiered should be TIERED. The row-level price is informational (typically set to the first tier's pricePerUnit).

Example: BBQ with group discounts

{
"vasId": "BBQ_2V_2NV",
"tagName": "goa-peak",
"price": 800.00,
"pricingType": "TIERED",
"pricingConfig": {
"type": "TIERED",
"tiers": [
{ "fromUnits": 1, "toUnitsInclusive": 4, "pricePerUnit": 800.00 },
{ "fromUnits": 5, "toUnitsInclusive": 10, "pricePerUnit": 700.00 },
{ "fromUnits": 11, "toUnitsInclusive": null, "pricePerUnit": 600.00 }
]
}
}

Three guests = 3 × ₹800 = ₹2,400. Twelve guests = 12 × ₹600 = ₹7,200.

Slab semantics

The current model applies one tier's rate to all units (the tier the count lands in). To support marginal "first 4 at ₹800, next 6 at ₹700" semantics, sum per-tier slices client-side or extend the resolver.


ON_ACTUALS — OnActualsPricing

For pass-through costs like "Chef (Grocery on Actual)". The booking is accepted at zero (or a refundable deposit) and the real amount is reconciled from receipts after the stay. Optionally apply a markup percent at reconciliation.

Schema

{
"type": "ON_ACTUALS",
"deposit": 0.00,
"markupPercent": 10
}

Example: Chef (Grocery on Actual) with 10% markup

{
"vasId": "CHEF_GROCERY_ACTUAL",
"tagName": "goa-peak",
"price": 0.00,
"pricingType": "ON_ACTUALS",
"pricingConfig": {
"type": "ON_ACTUALS",
"deposit": 0.00,
"markupPercent": 10
}
}

The cart shows the chef service at ₹0; ops uploads receipts post-stay and the guest is charged actualGrocery × 1.10.


Pricing Resolution Order

When the pricing engine needs the price for a VAS on a booking:

1. listing_channel_value_added_service  (most specific)
(listingId, channelId, vasId)
2. channel_value_added_service
(channelId, vasId, tagName) – walks all tags on the listing
3. vas_cost (baseline)
(vasId, variantId?, tagName)

Each level can override:

  • price
  • pricing_type
  • pricing_config
  • is_enabled (only at channel/listing levels — kills the option entirely)

If pricing_config is NULL at a level, the next level's config bubbles up. This lets a channel toggle a flat-fee item to tiered without touching the catalog.

Choosing the Right Strategy — Cheatsheet

You want…Use
One flat price per bookingFIXED
Price × headcountPER_PERSON (+ optional PerUnitPricing)
Price × item countPER_ITEM
Price × hours / kmPER_HOUR / PER_KM
Base envelope + overageBASE_PLUS_OVERAGE
Volume discount slabsTIERED
Pass-through reconciled laterON_ACTUALS