Sommer auf der GoldenPass-Linie — Bergseen, Wanderungen & der Train du Fromage

MOB · GoldenPass — Capability walkthrough

Test cases

One card per requirement. API-backed cases run the live mock and show the real request/response; flip one env var and the same call hits the real system. Data-driven cases show the example record + a backoffice link. 49 cases — 34 MUST · 14 SHOULD · 1 NICE.

Verkauf & Sortiment

VS-01MUST

Verkauf des kompletten Sortiments via NOVA

Offer search returns point-to-point, supersaver (train-bound, with quota) and day-pass, priced in cents.

Contract: NovaSalesPort.offers(req)

Go live: INTEGRATION_NOVA=b2p + NOVA_*

Run mock · raw request/response
POST /api/offers
{
  "from": "Montreux",
  "to": "Zweisimmen",
  "date": "2026-07-01",
  "passengers": [
    {
      "type": "adult",
      "count": 1
    }
  ],
  "travelClass": "second"
}
VS-02MUST

Verkauf von Sitzplatzreservationen

Get a seat map, then hold a seat with a TTL — a second hold on the same seat is rejected.

Contract: SeatInventoryPort.getSeatMap / hold

Go live: (Payload-backed; real adapter = NOVA seat svc)

Coach · SBB-standard plan
AvailableTakenHeldN/A
Run mock · raw request/response
GET /api/seat-map?schedule=demo&date=2026-07-01&class=second
VS-03MUST

Haustarif mit KoServ QR-Code

Issue a UIC-918.3 (KoServ-scannable) e-ticket — returns the Aztec barcode + encoded payload.

Contract: TicketIssuancePort.issue(input)

Go live: UIC_RICS + UIC_KEY_ID + UIC_SIGN_SECRET

Run mock · raw request/response
POST /api/tickets/preview
{
  "bookingNumber": "MOB-DEMO123",
  "passengerName": "John Doe",
  "from": "Montreux",
  "to": "Zweisimmen",
  "travelDate": "2026-07-01",
  "travelClass": "second",
  "priceCents": 5000
}
VS-04MUST

Hybride Artikel mit Constraints (Train du Fromage)

Composite article (transport + gastro) with constraints: max party size, operating days, advance days.

Contract: products.articleType="hybrid" + components[] + constraints[]

Im Preis inbegriffen

  • Round-trip journey
  • 7-course cheese & wine menu · max. 30
Max. 9 Pers.7 Tage im VorausDi·Fr·Sa·SoNur Sommer
Contract data (JSON)
{
  "articleType": "hybrid",
  "components": [
    {
      "kind": "transport",
      "label": "Round-trip journey"
    },
    {
      "kind": "gastro",
      "label": "7-course cheese & wine menu",
      "capacityPerSlot": 30
    }
  ],
  "constraints": {
    "maxPartySize": 9,
    "dateLocked": false,
    "minAdvanceDays": 7,
    "operatingDays": [
      "tue",
      "fri",
      "sat",
      "sun"
    ]
  },
  "validity": {
    "season": "summer"
  }
}
VS-05MUST

Dynamisches Upselling je Verbindung

Rules trigger by line/route/class and surface gastro offers with a discount in the funnel.

Contract: upsell-rules

Backoffice record· VS-05

What you’re seeing: An upsell rule: when a booking matches the trigger (route/class), these offers appear in the funnel at the given discount.

Name
Gastro on scenic routes
Active
● Yes
Trigger
Route
montreux-zweisimmen
Class
any
Offers
ProductLabelDiscount Pct
dining-deluxeChef's 3-course15
wine-pairingWine pairing10
Contract data (JSON)
{
  "name": "Gastro on scenic routes",
  "active": true,
  "trigger": {
    "route": "montreux-zweisimmen",
    "class": "any"
  },
  "offers": [
    {
      "product": "dining-deluxe",
      "label": "Chef's 3-course",
      "discountPct": 15
    },
    {
      "product": "wine-pairing",
      "label": "Wine pairing",
      "discountPct": 10
    }
  ]
}
VS-06SHOULD

Optimales / intelligentes Ticket

The cheapest valid offer is flagged BEST PRICE with the saving vs point-to-point (tick Half-Fare).

Contract: optimize(offers) over NovaSalesPort

Go live: automatic once NOVA is real

Run mock · raw request/response
POST /api/offers
{
  "from": "Montreux",
  "to": "Zweisimmen",
  "date": "2026-07-01",
  "passengers": [
    {
      "type": "adult",
      "count": 1
    }
  ],
  "travelClass": "second",
  "reductions": [
    "half_fare"
  ]
}
VS-07SHOULD

Regionalzüge ohne Reservation

Schedules flagged reservationRequired=false skip the seat-selection step (free seating).

Contract: schedules.reservationRequired=false

Backoffice record· VS-07

What you’re seeing: A schedule with reservationRequired = false → the booking flow skips the seat step (free seating on regional trains).

Route
regional-pleiades
Departure Time
09:15
Arrival Time
11:45
Bookable
● Yes
Reservation Required
○ No
Notes
Regional — free seating
Contract data (JSON)
{
  "route": "regional-pleiades",
  "departureTime": "09:15",
  "arrivalTime": "11:45",
  "bookable": true,
  "reservationRequired": false,
  "notes": "Regional — free seating"
}
VS-08MUST

Artikel-Gültigkeit (zeitlich / saisonal)

Products carry a validity window + season; shown as a badge and enforced at purchase.

Contract: products.validity { validFrom, validUntil, season }

SaisonartikelNur Sommer
Contract data (JSON)
{
  "validity": {
    "season": "summer"
  }
}

Customer Experience (UX)

UX-01MUST

Interaktiver Sitzplan (SBB-Standard)

Coach plan with window/aisle/table/quiet seats + live availability (x/y SVG coords).

Contract: SeatInventoryPort.getSeatMap + seat-maps

Coach · SBB-standard plan
AvailableTakenHeldN/A
Run mock · raw request/response
GET /api/seat-map?schedule=demo&date=2026-07-01&class=first
UX-02MUST

Sehr schnelle Navigation

Each purchase step responds in a few hundred ms (server components / route handlers).

Contract: Next.js server components

Each purchase step responds in a few hundred ms (server components / route handlers).

Open planner
UX-03SHOULD

Strecke auf interaktiver Karte

The planner draws the route line from route waypoints + station pins on a Leaflet map.

Contract: routes.waypoints + /fahrplan

The planner draws the route line from route waypoints + station pins on a Leaflet map.

Open planner (Montreux→Zweisimmen)
UX-04MUST

Responsives Rendering

Layouts reflow mobile ↔ desktop (toggle mobile devtools on the planner).

Contract: Tailwind responsive layouts

Layouts reflow mobile ↔ desktop (toggle mobile devtools on the planner).

Open planner
UX-05MUST

Saisonales Content-Management (global)

One switch (auto from date ranges, or forced) flips seasonal imagery/copy + banner site-wide.

Contract: seasons global

Backoffice record· UX-05

What you’re seeing: The Seasons global: activeSeason resolves from the date ranges (or is forced); the matching banner + season-variant images then drive the whole site.

Active Season
auto
Ranges
SeasonFromTo
summer2026-06-212026-09-22
winter2026-12-212027-03-20
Banners
SeasonHeadline
summerSommerabenteuer warten
Contract data (JSON)
{
  "activeSeason": "auto",
  "ranges": [
    {
      "season": "summer",
      "from": "2026-06-21",
      "to": "2026-09-22"
    },
    {
      "season": "winter",
      "from": "2026-12-21",
      "to": "2027-03-20"
    }
  ],
  "banners": [
    {
      "season": "summer",
      "headline": "Sommerabenteuer warten"
    }
  ]
}
UX-06MUST

Bilder je Saisonvariante

Media tagged per season; the active season selects the matching variant.

Contract: media.seasonVariant

Backoffice record· UX-06

What you’re seeing: Each media asset can carry a seasonVariant; the active season picks the matching image automatically.

Alt
GPX viaduct
Season Variant
summer
Filename
gpx-summer.jpg
Width
1920
Height
1080
Contract data (JSON)
{
  "alt": "GPX viaduct",
  "seasonVariant": "summer",
  "filename": "gpx-summer.jpg",
  "width": 1920,
  "height": 1080
}
UX-07MUST

Live-Informationen (SBB / SKI)

Departures board (delay/platform) + disruptions; plus the editor-managed orange ticker.

Contract: LivePort.departures / disruptions

Go live: OPENTRANSPORT_TOKEN

Live departures · Montreux ·
Run mock · raw request/response
GET /api/live?stop=Montreux

Accounts & Payments

AP-01MUST

Kauf als Gast oder via SwissPass Login

Checkout as guest or log in with SwissPass; customers stored with accountType.

Contract: AuthPort + customers (auth)

Continue as guest, or log in with SwissPass (OIDC + PKCE).
AP-02MUST

SwissPass Login (OIDC / PKCE)

Auth-Code + PKCE round-trip → profile {sub,email,…}; callback upserts a customer.

Contract: AuthPort.getAuthUrl / exchangeCode

Go live: INTEGRATION_AUTH=swisspass + SWISSPASS_*

Continue as guest, or log in with SwissPass (OIDC + PKCE).
AP-03SHOULD

Referenzierung des Tickets auf SwissPass

A SwissPass purchase records a swissPassRef on the booking.

Contract: EbAtSpPort + bookings.swissPassRef

Backoffice record· AP-03

What you’re seeing: A booking made by a SwissPass user stores the SwissPass reference, so the ticket can be tied to their card.

Booking Number
MOB-00012345
Customer Email
user@example.com
Channel
web
Swiss Pass Ref
SP-REF-9f2a3b
Status
paid
Contract data (JSON)
{
  "bookingNumber": "MOB-00012345",
  "customerEmail": "user@example.com",
  "channel": "web",
  "swissPassRef": "SP-REF-9f2a3b",
  "status": "paid"
}
AP-04MUST

B2B-Login mit Rechnungszahlung

Corporate account books on invoice up to a credit limit; group discount.

Contract: b2b-accounts + customers + InvoicePort

Backoffice record· AP-04

What you’re seeing: A B2B account: members book on account up to the credit limit and settle by invoice on the payment terms, with a group discount.

Company Name
Acme Tours AG
Vat Id
CHE-123.456.789
Credit Limit (CHF)
CHF 5'000.00
Payment Terms Days
45
Group Discount Pct
12
Status
active
Contract data (JSON)
{
  "companyName": "Acme Tours AG",
  "vatId": "CHE-123.456.789",
  "creditLimitCents": 500000,
  "paymentTermsDays": 45,
  "groupDiscountPct": 12,
  "status": "active"
}
AP-05MUST

Zustellung als E-Ticket (Mail / Download)

On payment success: PDF e-ticket + confirmation email (Resend).

Contract: TicketIssuancePort + Resend

Run mock · raw request/response
POST /api/tickets/preview
{
  "bookingNumber": "MOB-DEMO123",
  "passengerName": "Jane Doe",
  "from": "Montreux",
  "to": "Gstaad",
  "travelDate": "2026-07-01",
  "travelClass": "first",
  "priceCents": 7800
}
AP-06MUST

Standard-Zahlungsmittel

Payment means: card / TWINT / Apple Pay / MatchPay / voucher / B2B invoice.

Contract: PaymentPort.means

Go live: STRIPE_SECRET_KEY (card live on prod)

Credit / debit card
via stripe
TWINT
via stripe
Apple Pay
via stripe
MatchPay (SBB)
via payment_ov
Gift card / Voucher
via eguma
B2B invoice
via cembrapay · B2B only
Contract data (JSON)
[
  {
    "code": "card",
    "label": "Credit / debit card",
    "provider": "stripe"
  },
  {
    "code": "twint",
    "label": "TWINT",
    "provider": "stripe"
  },
  {
    "code": "apple_pay",
    "label": "Apple Pay",
    "provider": "stripe"
  },
  {
    "code": "matchpay",
    "label": "MatchPay (SBB)",
    "provider": "payment_ov"
  },
  {
    "code": "voucher",
    "label": "Gift card / Voucher",
    "provider": "eguma"
  },
  {
    "code": "invoice",
    "label": "B2B invoice",
    "provider": "cembrapay",
    "b2bOnly": true
  }
]
AP-07MUST

MatchPay (SBB)

MatchPay settles server-side (no client secret) and reconciles on its channel.

Contract: PaymentPort (means=matchpay, provider=payment_ov)

Go live: PAYMENT_OV_MERCHANT_ID

Credit / debit card
via stripe
MatchPay (SBB)
via payment_ov
selected
B2B invoice
via cembrapay · B2B only
Contract data (JSON)
[
  {
    "code": "card",
    "label": "Credit / debit card",
    "provider": "stripe"
  },
  {
    "code": "matchpay",
    "label": "MatchPay (SBB)",
    "provider": "payment_ov"
  },
  {
    "code": "invoice",
    "label": "B2B invoice",
    "provider": "cembrapay",
    "b2bOnly": true
  }
]
AP-08MUST

B2B-Rechnung (CembraPay)

Create a B2B invoice (subtotal + VAT 8.1% + due date) + PDF from line items.

Contract: InvoicePort.createInvoice

Go live: CEMBRAPAY_MERCHANT_ID

Rechnung
INV-7K4MN9PQ
issued
fällig 2026-07-29
PositionMengeTotal
GPX 4068 · 3 pax · Montreux → Zweisimmen · 2026-07-013CHF 135.00
Vegan meal add-ons3CHF 75.00
ZwischensummeCHF 210.00
MwSt (8.1%)CHF 17.01
TotalCHF 227.01
Contract data (JSON)
{
  "invoiceNumber": "INV-7K4MN9PQ",
  "companyName": "Acme Tours AG",
  "lineItems": [
    {
      "description": "GPX 4068 · 3 pax · Montreux → Zweisimmen · 2026-07-01",
      "quantity": 3,
      "unitPriceCents": 4500,
      "lineTotalCents": 13500
    },
    {
      "description": "Vegan meal add-ons",
      "quantity": 3,
      "unitPriceCents": 2500,
      "lineTotalCents": 7500
    }
  ],
  "subtotalCents": 21000,
  "vatCents": 1701,
  "totalCents": 22701,
  "status": "issued",
  "dueAt": "2026-07-29"
}
AP-09MUST

E-Guma Geschenkkarten

Check a gift-card balance, then redeem against a booking; remaining balance decremented.

Contract: VoucherPort.balance / redeem

Go live: INTEGRATION_VOUCHER=eguma + EGUMA_API_KEY

Voucher
Guthaben
EGUMA001

Redeemable at checkout; balance decremented per use (partial redemptions supported).

Run mock · raw request/response
GET /api/voucher/balance?code=EGUMA001
AP-10SHOULD

NGW-Gutscheine

Same VoucherPort, type=ngw (öV gift card). Demo code NGW002.

Contract: VoucherPort (type=ngw)

Voucher
Guthaben
NGW002

Redeemable at checkout; balance decremented per use (partial redemptions supported).

Run mock · raw request/response
GET /api/voucher/balance?code=NGW002
AP-11SHOULD

Ticket in der SwissPass App (EB@SP)

On a paid SwissPass booking, EB@SP delivery is attempted + tracked (none→pending→delivered).

Contract: EbAtSpPort.deliver + bookings.ebAtSp

Backoffice record· AP-11

What you’re seeing: The booking tracks EB@SP delivery status as the ticket is loaded onto the customer’s SwissPass app.

Booking Number
MOB-00012345
Swiss Pass Sub
spy333344
Eb At Sp
delivered
Delivered At
2026-07-01T08:12:00Z
Contract data (JSON)
{
  "bookingNumber": "MOB-00012345",
  "swissPassSub": "spy333344",
  "ebAtSp": "delivered",
  "deliveredAt": "2026-07-01T08:12:00Z"
}

Self-Service & Customer Care

SS-01SHOULD

Self-Service Storno/Rückerstattung (regelbasiert)

Tiered refund rules by days-before with fee + refund percentage.

Contract: refund-rules

Backoffice record· SS-01

What you’re seeing: A refund-rule: the tier matching how many days before travel sets the fee and the refunded percentage.

Name
Standard cancellation policy
Active
● Yes
Tiers
Days Before FromRefund PctFee ValueDays Before ToFee Type
141000
7901013percent
00502percent
Contract data (JSON)
{
  "name": "Standard cancellation policy",
  "active": true,
  "tiers": [
    {
      "daysBeforeFrom": 14,
      "refundPct": 100,
      "feeValue": 0
    },
    {
      "daysBeforeFrom": 7,
      "daysBeforeTo": 13,
      "feeType": "percent",
      "feeValue": 10,
      "refundPct": 90
    },
    {
      "daysBeforeFrom": 0,
      "daysBeforeTo": 2,
      "feeType": "percent",
      "feeValue": 50,
      "refundPct": 0
    }
  ]
}
SS-02MUST

Self-Service-Storno im Kundenprofil

Customer cancels → refund record with computed fee/refund, status requested→completed.

Contract: refunds (self_service) + PaymentPort.refund

Backoffice record· SS-02

What you’re seeing: A self-service refund: the rule computed the fee and refunded amount; the record moves requested → completed.

Booking
MOB-00012345
Reason
request
Channel
self_service
Policy Applied
7-13 days
Fee (CHF)
CHF 20.00
Refunded (CHF)
CHF 180.00
Status
completed
Contract data (JSON)
{
  "booking": "MOB-00012345",
  "reason": "request",
  "channel": "self_service",
  "policyApplied": "7-13 days",
  "feeCents": 2000,
  "refundedCents": 18000,
  "status": "completed"
}
SS-03SHOULD

Massenstornierung (Zugausfall)

One cancellation event finds affected bookings and issues bulk refunds.

Contract: cancellation-events → refunds

Backoffice record· SS-03

What you’re seeing: A Zugausfall event: processing it finds all affected bookings and auto-issues refunds in bulk.

Title
Zugausfall GPX 4068 — 2026-06-15
Reason
infrastructure
Auto Refund
● Yes
Affected Bookings
47
Status
closed
Contract data (JSON)
{
  "title": "Zugausfall GPX 4068 — 2026-06-15",
  "reason": "infrastructure",
  "autoRefund": true,
  "affectedBookings": 47,
  "status": "closed"
}
SS-04MUST

Backoffice-Rückerstattungen

Agent approves → status requested→approved→processing→completed with processor ref.

Contract: refunds (backoffice)

Backoffice record· SS-04

What you’re seeing: An agent-initiated refund: status advances requested → approved → processing → completed, with a payment-processor reference.

Booking
MOB-00067890
Reason
goodwill
Channel
backoffice
Status
processing
Refunded (CHF)
CHF 90.00
Processor Ref
ch_3Pxy…
Contract data (JSON)
{
  "booking": "MOB-00067890",
  "reason": "goodwill",
  "channel": "backoffice",
  "status": "processing",
  "refundedCents": 9000,
  "processorRef": "ch_3Pxy…"
}

Backoffice & CMS

BO-01MUST

Kontingentverwaltung + Verkaufsfenster

Capacity per date/train/class with per-channel allocations + a sales window.

Contract: quotas

Backoffice record· BO-01

What you’re seeing: A quota: total capacity for a date/train/class, split into per-channel allocations, with a sales window. Overselling is blocked.

Display Name
GPX 4068 · 2026-06-15 · second
Date
2026-06-15
Class
second
Total Capacity
100
Sold
47
Allocations
ChannelAllocatedSold
web6038
nova309
retained100
Sales Window Until
2026-06-15T23:59:59Z
Contract data (JSON)
{
  "displayName": "GPX 4068 · 2026-06-15 · second",
  "date": "2026-06-15",
  "class": "second",
  "totalCapacity": 100,
  "sold": 47,
  "allocations": [
    {
      "channel": "web",
      "allocated": 60,
      "sold": 38
    },
    {
      "channel": "nova",
      "allocated": 30,
      "sold": 9
    },
    {
      "channel": "retained",
      "allocated": 10,
      "sold": 0
    }
  ],
  "salesWindowUntil": "2026-06-15T23:59:59Z"
}
BO-02MUST

Headless-CMS-Blöcke

Pages built from editable blocks (hero, content, media, FAQ, CTA, map…), shown in our design.

Contract: pages.layout blocks

Pages built from editable blocks (hero, content, media, FAQ, CTA, map…), shown in our design.

Open a migrated page
BO-03MUST

Warenkorb mit Ablaufzeit

Cart + seat hold carry an expiry (mirrors the 30-min NOVA hold); expiry releases the seat.

Contract: carts.expiresAt + reservations.holdExpiresAt

Backoffice record· BO-03

What you’re seeing: A cart with a TTL: a visible countdown runs; on expiry the held seats are released back to inventory.

Session Id
sess_abc123
Status
active
Items
Title SnapshotQuantityUnit Price (CHF)
GPX 4068 Montreux → Zweisimmen1CHF 45.00
Total (CHF)
CHF 70.00
Expires At
2026-06-14T16:45:00Z
Contract data (JSON)
{
  "sessionId": "sess_abc123",
  "status": "active",
  "items": [
    {
      "titleSnapshot": "GPX 4068 Montreux → Zweisimmen",
      "quantity": 1,
      "unitPriceCents": 4500
    }
  ],
  "totalCents": 7000,
  "expiresAt": "2026-06-14T16:45:00Z"
}
BO-04MUST

Promo-Codes nach Produkt

Generate a batch of codes scoped to a product.

Contract: promo-codes.scope.products

Run mock · raw request/response
POST /api/promos
{
  "count": 5,
  "discountType": "percent",
  "discountValue": 25,
  "prefix": "FROMAGE"
}
BO-05MUST

Promo-Codes nach Bahnhof

Codes scoped to an origin/destination pair.

Contract: promo-codes.scope.fromStation/toStation

Run mock · raw request/response
POST /api/promos
{
  "count": 5,
  "discountType": "amount",
  "discountValue": 1000,
  "prefix": "MOZ"
}
BO-06MUST

Promo-Codes nach Reisedatum

Codes scoped to a travel-date range.

Contract: promo-codes.scope.travelDateFrom/To

Run mock · raw request/response
POST /api/promos
{
  "count": 5,
  "discountType": "percent",
  "discountValue": 15,
  "prefix": "JULY",
  "travelDateFrom": "2026-07-01",
  "travelDateTo": "2026-07-31"
}
BO-07SHOULD

Vorschau der Ticket-Layouts

Render the ticket layout + Aztec without issuing a real ticket.

Contract: TicketIssuancePort.issue (preview)

Run mock · raw request/response
POST /api/tickets/preview
{
  "bookingNumber": "MOB-PREVIEW",
  "passengerName": "Preview",
  "from": "Montreux",
  "to": "Zweisimmen",
  "travelDate": "2026-07-01",
  "travelClass": "second",
  "priceCents": 4500
}
BO-08NICE

Promo-Code-API (Automatisierung)

Generate codes via API (e.g. birthday automation) — returns a batchId + N codes.

Contract: POST /api/promos

Run mock · raw request/response
POST /api/promos
{
  "count": 10,
  "discountType": "percent",
  "discountValue": 15,
  "prefix": "BDAY",
  "travelDateFrom": "2026-07-01",
  "travelDateTo": "2026-07-31"
}

Finanzen & Daten

FD-01MUST

Automatische Reconciliation NOVA (Multi-Channel)

Per-channel gross / commission / refunds / net (nova 30%, matchpay 1.5%, haustarif 2.9%).

Contract: ReconciliationPort.run

Loading report…
Run mock · raw request/response
GET /api/reconciliation
FD-02SHOULD

Reconciliation MatchPay (SBB)

The settlement table includes a MatchPay channel row.

Contract: ReconciliationPort.run (matchpay)

Loading report…
Run mock · raw request/response
GET /api/reconciliation
FD-03MUST

Finanzdaten & Reportings

Revenue by channel/period with totals (reconciliations collection + dashboard).

Contract: reconciliations

Loading report…
FD-04SHOULD

CRM-Übergabe nach Kauf/SAV

On purchase/SAV: upsert CRM contact + deal with an audit trail.

Contract: hubspot.ts (gated)

Go live: HUBSPOT_TOKEN

Backoffice record· FD-04

What you’re seeing: After a purchase or SAV action, the customer is upserted into the CRM as a contact + deal; the booking keeps an audit-trail event.

Event
purchase
Booking Ref
MOB-00012345
Crm Contact
upserted
Crm Deal
created · closed-won
Amount (CHF)
CHF 96.00
Contract data (JSON)
{
  "event": "purchase",
  "bookingRef": "MOB-00012345",
  "crmContact": "upserted",
  "crmDeal": "created · closed-won",
  "amountCents": 9600
}
FD-05MUST

Google Analytics

GA4 ecommerce events fire once a measurement id is set.

Contract: site-settings.analytics.ga4MeasurementId

Backoffice record· FD-05

What you’re seeing: Set a GA4 measurement id in Site Settings → GA4 ecommerce events (view_item, add_to_cart, purchase) start firing.

Analytics
Ga4 Measurement Id
G-XXXXXXX
Enable Ecommerce
● Yes
Require Consent
● Yes
Contract data (JSON)
{
  "analytics": {
    "ga4MeasurementId": "G-XXXXXXX",
    "enableEcommerce": true,
    "requireConsent": true
  }
}

Architektur & Sicherheit

AS-01MUST

White-Label-Architektur

Documented target — tenant discriminator + theming tokens (per-env build args today).

Contract: documented (plan §5.5)

Documented capability: The design-token system + per-environment build args are the basis; a tenant discriminator + theme resolver make it multi-tenant.

AS-02MUST

Gesicherte API (WAF / DDoS / BFF)

BFF = Next route handlers (only public surface); WAF/DDoS/cert = Cloud Armor + Cloud Run.

Contract: Next route handlers + Cloud Armor (infra)

IntegrationAdapterModeKeys to go live
Run mock · raw request/response
GET /api/integrations/status
AS-03MUST

Vollständiges technisches Monitoring

Per-integration mode (mock/real) + readiness + required env keys; plus uptime checks.

Contract: GET /api/integrations/status

IntegrationAdapterModeKeys to go live
Run mock · raw request/response
GET /api/integrations/status
AS-04SHOULD

API für Drittsysteme (Chatbot / agentisch)

Payload API keys + the AI Concierge (search / offer / draft booking tools).

Contract: Payload API keys + AI Concierge

Payload API keys + the AI Concierge (search / offer / draft booking tools).

Open site (concierge)
AS-05SHOULD

Fraud Detection via Log-Analyse

Score a context → severity + action + reasons (velocity, value, email, refund abuse, missing IP).

Contract: FraudPort.evaluate

Run mock · raw request/response
POST /api/fraud/evaluate
{
  "recentAttempts": 5,
  "amountCents": 200000,
  "email": "temp@mailinator.com",
  "isRefund": true
}
AS-06SHOULD

Aktive Fraud-Prevention

Fraud rules + blocklist (email/ip/card/device) consulted at checkout → block/challenge.

Contract: fraud-rules + blocklist

Backoffice record· AS-06

What you’re seeing: A blocklist entry: checkout consults the blocklist + rules and blocks/challenges matching patterns (e.g. SAV abuse).

Value
temp@mailinator.com
Kind
email
Reason
SAV abuse
Active
● Yes
Expires At
Contract data (JSON)
{
  "value": "temp@mailinator.com",
  "kind": "email",
  "reason": "SAV abuse",
  "active": true,
  "expiresAt": null
}