Skip to main content

C-MERCH — Hospitality Merchandising Patterns

Scope: A pattern library for surfacing offers, urgency signals, trust cues, and loyalty affordances in the consumer booking funnel, property detail pages, and guest portal. Reusable across meta-web, tenant-booking-web, and guest-portal-web. All patterns include ethical guardrails — no dark patterns; accuracy is required.

Ethics statement: Melmastoon does not implement manipulative scarcity (fabricated "only 2 left"), fake social proof, or deceptive urgency timers. Every pattern below includes an accuracy requirement. Violation is grounds for removing the pattern from a tenant's configuration.


Pattern index

PatternCode nameSurfaces
Rate plan cardRatePlanCardTenant booking web (room selection)
Upsell stripUpsellStripTenant booking web (room selection, checkout), Guest portal
Cross-sell carouselCrossSellCarouselTenant booking web (confirmation), Meta web
Promo / discount surfacingPromoEntryTenant booking web (guest details, payment)
Loyalty cardLoyaltyCardGuest portal, consumer mobile
Trust badgesTrustBadgesAll booking surfaces
Social proofSocialProofMeta web (property detail), tenant booking web
Urgency indicatorUrgencyIndicatorMeta web (search results), tenant booking web
Reviews displayReviewsDisplayMeta web (property detail), tenant booking web

1. Rate plan card (RatePlanCard)

Context: Displayed in the room selection step of the booking funnel. One card per available rate plan (e.g., "Flexible", "Non-refundable", "Breakfast included").

Anatomy

┌────────────────────────────────────────────────┐
│ [RatePlanName] [Price/night]│
│ [CancellationPolicyBadge] [Total] │
│ [InclusionsList] │
│ ✓ Free cancellation until [date] │
│ ✓ Breakfast included │
│ ✗ Non-refundable (greyed, strikethrough) │
│ [Select rate] │
└────────────────────────────────────────────────┘

Variants

VariantWhen
highlightedBest-value rate (platform-computed by room-availability-service)
dimmedSold-out or restricted rate
member-rateLower price visible only to loyalty members

Token fields

FieldTypeSource
rate_plan_idstringroom-availability-service
nameLocaleStringTenant-configured
price_per_nightMoneyBFF; live pricing
total_priceMoneyBFF; includes taxes + fees
cancellation_policy'free' | 'partial' | 'non-refundable'Rate plan config
free_cancellation_untilISO8601 | nullRate plan config
inclusionsLocaleString[]Rate plan config
is_member_ratebooleanBFF; requires auth

Ethics: cancellation badge

  • 'free' → Green badge "Free cancellation until [date]"
  • 'partial' → Amber badge "Partial refund — see policy"
  • 'non-refundable' → Red badge "Non-refundable"

Badge must be visible without scrolling within the card. Never hide the cancellation policy behind a tooltip-only affordance.


2. Upsell strip (UpsellStrip)

Context: Displayed after room selection (pre-payment) and in the guest portal (mid-stay). Offers add-ons relevant to the guest's stay.

Anatomy

Enhance your stay
──────────────────────────────────────────────
[UpsellItem] [UpsellItem] [UpsellItem] [→]
🌅 Late 🍳 Breakfast 🚘 Airport
Checkout + $12/pp Transfer
+ $30 [Add] + $45 [Add]
──────────────────────────────────────────────

Upsell item types (hospitality-standard)

ItemIconPrice model
Room upgradebed glyphPer stay
Late checkoutclockPer stay
Early check-insunrisePer stay
Breakfastfork-knifePer person per night
Airport transfercarPer direction
Spa treatmentleafPer session
ParkingparkingPer night
F&B room creditwineFixed credit
Welcome amenitygiftPer stay

Rules

  • Maximum 5 upsell items shown at once (pagination if more available)
  • Items shown are personalised by ai-gateway-serviceupsell-ranking-model (Phase 2 — R1 shows all available items sorted by margin)
  • Prices are live from folio-service; not cached (staleness = incorrect charges)
  • "Added" state: item card flips to a confirmation state with "Remove" option
  • No auto-add: guest must explicitly tap "Add"; pre-checked checkboxes are prohibited

Guest portal mid-stay context

Only show upsells relevant to remaining stay duration (e.g., don't show "late checkout" if guest checks out today).


Context: Shown on the booking confirmation page ("You might also like") and on the meta-web property detail page.

Anatomy

Other properties you might enjoy
─────────────────────────────────────────────────────
[PropertyCard] [PropertyCard] [PropertyCard] [→]

Each PropertyCard in a cross-sell context shows: thumbnail, property name, city, lowest-available rate, overall rating.

Rules

  • Cross-sell properties are served by search-aggregation-service using guest's stay dates and destination
  • Never show the same property the guest just booked
  • "Partner properties" label if any cross-sell is a commercial placement (paid ranking boost)
  • Maximum 6 items; horizontal scroll on mobile

4. Promo / discount surfacing (PromoEntry)

Context: In the booking funnel at the guest-details or payment step.

Anatomy

Have a promo code?
[_________________] [Apply]

✓ Code SUMMER25 applied — 25% off your stay
New total: $270 (was $360)

Variants

VariantDescription
coupon-entryFree-text promo code input
member-rate-reveal"Sign in to see your member rate" CTA
mobile-exclusiveBanner: "You're getting our mobile-only rate"
partner-rate"Booked via [OTA name] — contracted rate applied"

Rules

  • Discount amount shown in absolute value AND percentage (e.g., "You save $90 (25%)")
  • If a promo code is invalid: inline error "This code is invalid or expired" (not a modal)
  • If a better rate (member) is available, surface a non-intrusive banner: "Log in to save [X%]" — not an obstruction
  • Never auto-apply a discount without confirmation (ghost discounts confuse folios)

5. Loyalty card (LoyaltyCard)

Context: Consumer mobile (account section), guest portal (home).

Anatomy

┌──────────────────────────────────────────────┐
│ 🌟 Melmastoon Member │
│ [TierBadge: Silver] │
│ │
│ 1,240 pts Next tier: Gold at 2,000 pts │
│ ████████░░░░░░ 62% │
│ │
│ Perks: │
│ ✓ Free breakfast from Gold tier │
│ ✓ Late checkout (on request) │
│ ✓ 10% off spa │
│ [View all] │
└──────────────────────────────────────────────┘

Rules

  • Points balance is real-time from loyalty-service (not cached)
  • Progress bar is accessible: role="progressbar" aria-valuenow={62} aria-valuemin={0} aria-valuemax={100} aria-label="62% to Gold tier"
  • Tier benefits list: max 3 shown; "View all" expands
  • Phase 2: animated confetti on tier upgrade

6. Trust badges (TrustBadges)

Context: All booking surfaces — typically near the "Book now" / "Pay" CTA.

Badge set (standard)

BadgeIconCondition for display
Verified propertyshield-checkProperty passed platform verification
Free cancellationarrow-counter-clockwiseRate plan has cancellation: 'free'
Secure paymentlock-simpleAlways shown on payment step
Best-price guaranteetagTenant opts in (platform must back with price-match policy)
Instant confirmationlightningRate plan has confirmation: 'instant'
Travel insuranceumbrellaIf insurance upsell available

Rules

  • Maximum 4 badges shown horizontally (overflow → hide, not carousel)
  • Each badge has a title / aria-label explaining it (e.g., "Secure payment: Your card details are encrypted")
  • "Best-price guarantee" badge requires an ADR note in the tenant's config confirming the price-match policy is active
  • No fabricated badges ("Award winner 2026" without evidence)

7. Social proof (SocialProof)

Context: Meta-web property detail page, tenant booking web home block.

Sub-patterns

7a. Recent bookings count

🔥 47 people booked this property in the last 7 days

Accuracy requirement: Real data from analytics-service. Minimum threshold: only display if count ≥ 5 in the time window. If count < 5, hide the element entirely (don't show "2 people booked...").

7b. Currently viewing count

👁️ 12 people are looking at this property right now

Accuracy requirement: Real-time presence count from channel-service. Minimum threshold: ≥ 3 concurrent viewers. Count updates every 60 s.

7c. Review snippet

A single highlighted review (highest-rated, most-recent, or most-helpful — configurable):

"The breakfast was amazing and the staff were so helpful."
— Ahmad K., verified stay, March 2026 ★★★★★

Rules:

  • verified stay badge only for reviews from guests who completed a stay via the platform
  • Review text truncated at 140 chars with "Read more" expansion
  • "Helpful" vote count shown if ≥ 5 votes

8. Urgency indicator (UrgencyIndicator)

Context: Meta-web search results (property card), tenant booking web (rate plan card).

Sub-patterns

8a. Room scarcity

⚡ Only 2 rooms left at this rate

Accuracy requirement: Real-time from room-availability-service. Only display when available_rooms ≤ 3 for the selected dates. If availability changes between render and display (> 5 min), the badge auto-refreshes. Never hardcode.

8b. Price trend

📈 Price increased 15% since yesterday

Accuracy requirement: analytics-service provides yesterday's average rate. Only show if difference ≥ 10%.
Direction rule: Only show "price increased" — never show "price decreased" as urgency (it's not urgent; it's a benefit, and it belongs in a discount badge).

8c. High-demand badge

🔥 Popular this weekend

Accuracy requirement: Shown when occupancy forecast (from analytics-service) for the searched dates is > 80% for comparable properties in the same destination.

Ethical guardrails (all urgency patterns)

  1. All urgency data must be real-time or sourced from actual historical data
  2. Fabricating urgency signals is a platform policy violation (tenant config suspended)
  3. Urgency signals must be removable per tenant if they opt out
  4. Never pair multiple urgency signals on the same element simultaneously (max one)

9. Reviews display (ReviewsDisplay)

Context: Meta-web property detail, tenant booking web.

Anatomy

Overall rating: ★★★★☆ 4.2 / 5 (138 reviews)

Rating breakdown:
Cleanliness ████████░░ 4.5
Staff █████████░ 4.8
Location ██████░░░░ 3.9
Value ███████░░░ 4.1

[Filter by: Most recent ▾] [Sort by: Most helpful ▾]

── Review card ──────────────────────────────────────
Ahmad K. ★★★★★ March 2026 [Verified stay]
"Excellent hospitality..."
[Response from owner: "Thank you Ahmad! ...]
[Helpful (12)] [Not helpful (1)]
─────────────────────────────────────────────────────
[Load 10 more reviews]

Rules

  • Rating breakdown bars: role="meter" with aria-valuenow, aria-valuemin, aria-valuemax, aria-label
  • Verified stay badge: only for platform-verified bookings (same rule as 7c)
  • "Response from owner" shown when present (expands inline, not modal)
  • "Helpful" vote: one vote per user per review; requires auth (magic link for guest portal; full auth for others)
  • Minimum 3 reviews to show the aggregate rating; below 3, show "No reviews yet — be the first"
  • No review suppression: tenants cannot hide individual negative reviews (platform policy)

RTL notes (all patterns)

  • Scarcity count: "Only 2 left" → in Pashto: يوازې ۲ پاتې دي — Latin numerals per F4 invariant? Exception: Room counts and days remaining use Latin numerals (quantities, not prices) per 09-NFR.md §3 — Latin for quantities in financial context; locale numerals permissible for non-financial counts in body copy per tenant locale-pack. Implement with <NumericDisplay context="count"> which reads the tenant's numeral_variant setting.
  • Layout: all patterns mirror in RTL (flex-row-reverse, icon positions swap)
  • Trust badges: text and icons align to inline-start

Accessibility notes (all patterns)

  • Urgency indicators: not conveyed by color alone (include icon + text)
  • Rating stars: aria-label="4.2 out of 5 stars" on the star visual; do not use ★ characters as the only rating cue
  • Progress bars (loyalty, rating breakdown): always role="meter" with full aria-value* attributes
  • Upsell strip: horizontal scroll region has role="region" aria-label="Add-on options"; keyboard accessible (Tab through items, arrow keys scroll)

Telemetry events

EventTriggerProperties
rate_plan.viewedRate plan card rendered above foldrate_plan_id, price, cancellation_type
rate_plan.selectedGuest selects a raterate_plan_id
upsell.impressionUpsell strip rendereditems_shown: string[]
upsell.item_addedGuest adds an upsellitem_type, price
promo.appliedPromo code successfully applieddiscount_pct
social_proof.booking_count.shownBooking count renderedcount, window_days
urgency.scarcity.shownScarcity badge renderedrooms_remaining
reviews.loadedReview list renderedcount, average_rating
reviews.helpful_voteUser votes helpfulreview_id

References