Pricing & Payments (PL-T187)
At a glance
A rule-based pricing stack with a deterministic Quote preview plus a full payments lifecycle. Peak/off-peak multipliers, member-tier discounts via ChainPassport, group and duration brackets, promo codes, gift cards with CSPRNG codes and atomic redemption, deposit-and-pay-at-venue paired rows, online Stripe payments with webhook idempotency, refunds with status recompute to PARTIAL_REFUND/REFUNDED, and per-row audit trails on every transition.
How it works
Pricing is composed from a rule stack on ChainVenueProfile. Each rule has a type (peak_multiplier, tier_discount, group_discount, duration_bracket, promo_code) and parameters; the engine applies them in order and returns a Quote with a line-item breakdown. Dynamic pricing is gated by dynamic_pricing_enabled so a tenant can pin flat rates without removing rules.
POST /chain/pricing-rules/preview is the single deterministic source — both the customer wizard and the operator console call it to render an explainable price before submit. Gift cards use 16-character CSPRNG codes with a unique (tenant_id, code) constraint and atomic redemption — the redemption decrements the balance inside a single update with the audit trail attached. Anonymous balance lookup is available (rate-limited at the gateway) so a customer holding a card can check remaining value without an account.
The deposit flow writes paired BookingPayment rows — a deposit row charged at booking time and a pay_at_venue row settled at check-in. Online payments use Stripe PaymentIntents with metadata round-trip; the /webhooks/stripe/booking-payments endpoint verifies the Stripe-Signature header and uses StripeWebhookEvent.event_id for idempotency, so duplicate webhook deliveries don't double-count payments. Refunds run against succeeded rows with parent_payment_id linkage; the engine recomputes booking status to PARTIAL_REFUND or REFUNDED and writes a new audit_trail entry.
Every transition (created, captured, refunded, voided) records actor, action, and metadata on BookingPayment.audit_trail.
Key capabilities
- Composable pricing-rule stack (peak, tier, group, duration, promo) with Quote preview
- ChainPassport BRONZE/SILVER/GOLD tier discounts
- Gift cards with CSPRNG codes, atomic redemption + anonymous balance lookup
- Deposit + pay-at-venue paired payment rows
- Stripe PaymentIntent with webhook idempotency on event_id
- Refund flow with status recompute (PARTIAL_REFUND / REFUNDED)
- Per-row audit trail on every payment transition
In practice
A customer with a SILVER ChainPassport books two lanes for six people on a Friday at 20:00 with promo code SUMMER20. The wizard calls the preview endpoint, which returns a Quote: base rate 800 SEK + peak multiplier 1.3 → 1040, group discount −10% → 936, SILVER tier −5% → 889, promo SUMMER20 −20% → 711. The customer redeems a 200 SEK gift card (atomic decrement, balance now 0) and pays the remaining 511 SEK via Stripe — the PaymentIntent succeeds, the webhook arrives twice (Stripe retried), but StripeWebhookEvent.event_id deduplicates.
The next morning the customer cancels — within the cancellation window, so the operator triggers a refund. The engine refunds the Stripe charge, the gift-card balance is restored, the booking flips to REFUNDED, and the audit trail records every step.
Features in this subsystem
12| ID | Status | Features |
|---|---|---|
| F22.05.01 | Shipped | Peak / off-peak pricing rules (rule-based stack with peak_multiplier, gated by dynamic_pricing_enabled) ✅ |
| F22.05.02 | Shipped | Member-tier discount (ChainPassport BRONZE/SILVER/GOLD via tier_discount rule) ✅ |
| F22.05.03 | Shipped | Group discount (group_discount rule with min_party_size threshold) ✅ |
| F22.05.04 | Shipped | Duration-bracket discount (duration_bracket rule) and promo-code (promo_code rule) ✅ |
| F22.05.05 | Shipped | Deterministic preview via POST /chain/pricing-rules/preview returning a Quote with line breakdown ✅ |
| F22.05.06 | Shipped | Gift cards: 16-char CSPRNG codes, (tenant_id, code) unique, atomic redemption with audit trail ✅ |
| F22.05.07 | Shipped | Anonymous balance lookup endpoint (rate-limited at gateway) — exposes balance + status only ✅ |
| F22.05.08 | Shipped | Deposit flow — paired deposit + pay_at_venue rows, balance settled at check-in ✅ |
| F22.05.09 | Shipped | Online payment via Stripe PaymentIntent with metadata round-trip ✅ |
| F22.05.10 | Shipped | Stripe webhook /webhooks/stripe/booking-payments — signature verification + StripeWebhookEvent idempotency on event_id ✅ |
| F22.05.11 | Shipped | Refund flow against succeeded rows with parent_payment_id linkage and recompute → PARTIAL_REFUND/REFUNDED ✅ |
| F22.05.12 | Shipped | BookingPayment.audit_trail records every transition (actor, action, metadata) ✅ |
Related subsystems
Stakeholders who need this subsystem
Surfaces in 6 stakeholder analyses