Skip to main content

Value-Added Services Pricing

VAS pricing follows the same three-layer model as Mealsvas_costchannel_value_added_servicelisting_channel_value_added_service — but supports a richer pricing surface because VAS items vary in shape.

This page is a thin bridge. The deep guide lives under Value-Added Services.

The Catalogue

LayerTableKeyed by
Catalog costvas_cost(vas_id, variant_id?, tag_name)
Channel overridechannel_value_added_service(channel_id, vas_id, tag_name)
Listing overridelisting_channel_value_added_service(listing_id, channel_id, vas_id)

vas_cost is unique among the three pricing surfaces in that it carries a nullable variant_id. Two partial unique indexes keep the keys clean:

UNIQUE (vas_id, tag_name)                WHERE variant_id IS NULL    -- non-variant VAS
UNIQUE (vas_id, variant_id, tag_name) WHERE variant_id IS NOT NULL -- per-variant rows

Five Pricing Strategies

Unlike meals (always per-adult + per-child), VAS has five pricing_type shapes. The pricing_config JSONB column captures the parameters for the more complex variants:

pricing_typeWhen to usepricing_config shape
FIXEDFlat fee per booking — e.g. Bonfire ₹2,500not required
PER_PERSON / PER_ITEM / PER_QUANTITY / PER_HOUR / PER_KMprice × unit_countoptional PerUnitPricing { unit }
BASE_PLUS_OVERAGESedan: ₹X for 4h/40km, then per-hour + per-km afterBasePlusOveragePricing { baseHours, baseKm, perExtraHour, perExtraKm }
TIEREDSlab pricing — group discountsTieredPricing { tiers: [{ fromUnits, toUnitsInclusive, pricePerUnit }] }
ON_ACTUALSChef grocery; booked at 0, reconciled post-stayOnActualsPricing { deposit, markupPercent }

Full walkthrough with worked examples: VAS Pricing Strategies.

Variants

vas carries kind = VARIANT_PARENT when the same service ships in multiple SKUs. Each variant gets its own row in vas_variant and its own per-tag price row in vas_cost.

// vas: Premium Sedan
{ "id": "PREMIUM_SEDAN", "kind": "VARIANT_PARENT", "category": "TRANSPORT" }

// vas_variant: Swift Dzire 4h/40km
{ "id": "SWIFT_DZIRE_4H_40KM", "vasId": "PREMIUM_SEDAN", "attributes": { "baseHours": 4, "baseKm": 40 } }

// vas_cost: per-variant pricing
{
"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
}
}

Channel and listing layers do not carry variant_id. Per-variant pricing lives only in vas_cost; channel and listing rows apply across the whole VAS (all its variants). When listing_channel_value_added_service references a specific vas_cost_id, the variant context is implicit through that FK.

Bundles

kind = BUNDLE aggregates multiple child VAS entries via vas_bundle_item. The bundle can either roll up to one fixed price (bundle_pricing_mode = ROLLUP, stored in vas_cost like any other VAS) or sum its children at runtime (SUM_CHILDREN). Full walkthrough: Variants, Bundles & Choices.

Choice Groups

Some VAS items expose pick-N-of-M customisations (BBQ "pick 2 veg + 2 non-veg"). These live in vas_choice_group + vas_choice_option and are configuration-only — they don't change the pricing-layer model.

What Differs from Meals

MealsVAS
Catalog rowmealvas (with category, kind, attributes, constraints)
Cost row keys(meal_id, tag_name)(vas_id, variant_id?, tag_name)
Pricing axesadult cost + child costone price + a sealed pricing_config
Variant SKUsnoyes
Bundlesnoyes
Choice groupsnoyes
Channel pricing_config overridenoyes

What's the Same

  • Same three layers, same propagation rules, same onboarding flow.
  • Same listing_tag segmentation.
  • Same runtime resolution path — the website reads only the listing layer.

See Propagation and Resolution for the shared flow.