refactor: remove all backward compatibility code across 70 files
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running

Clean up 28 backward compatibility instances identified in the codebase.
The app is not live, so all shims are replaced with the target architecture:

- Remove legacy Inventory.location column (use bin_location exclusively)
- Remove dashboard _extract_metric_value helper (use flat metrics dict)
- Remove legacy stat field duplicates (total_stores, total_imports, etc.)
- Remove 13 re-export shims and class aliases across modules
- Remove module-enabling JSON fallback (use PlatformModule junction table)
- Remove menu_to_legacy_format() conversion (return dataclasses directly)
- Remove title/description from MarketplaceProductBase schema
- Clean billing convenience method docstrings
- Clean test fixtures and backward-compat comments
- Add PlatformModule seeding to init_production.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 13:20:29 +01:00
parent b0db8133a0
commit aad18c27ab
70 changed files with 501 additions and 841 deletions

View File

@@ -8,9 +8,9 @@ The application serves multiple frontends from a single codebase:
| Frontend | Description | Example URLs |
|----------|-------------|--------------|
| **ADMIN** | Platform administration | `/admin/*`, `/api/v1/admin/*`, `admin.oms.lu/*` |
| **ADMIN** | Platform administration | `/admin/*`, `/api/v1/admin/*`, `admin.omsflow.lu/*` |
| **STORE** | Store dashboard | `/store/*`, `/api/v1/store/*` |
| **STOREFRONT** | Customer-facing shop | `/storefront/*`, `/stores/*`, `orion.oms.lu/*` |
| **STOREFRONT** | Customer-facing shop | `/storefront/*`, `/stores/*`, `orion.omsflow.lu/*` |
| **PLATFORM** | Marketing pages | `/`, `/pricing`, `/about` |
The `FrontendDetector` class provides centralized, consistent detection of which frontend a request targets.
@@ -64,13 +64,13 @@ class FrontendType(str, Enum):
The `FrontendDetector` uses the following priority order:
```
1. Admin subdomain (admin.oms.lu) → ADMIN
1. Admin subdomain (admin.omsflow.lu) → ADMIN
2. Path-based detection:
- /admin/* or /api/v1/admin/* → ADMIN
- /store/* or /api/v1/store/* → STORE
- /storefront/*, /shop/*, /stores/* → STOREFRONT
- /api/v1/platform/* → PLATFORM
3. Store subdomain (orion.oms.lu) → STOREFRONT
3. Store subdomain (orion.omsflow.lu) → STOREFRONT
4. Store context set by middleware → STOREFRONT
5. Default → PLATFORM
```
@@ -133,7 +133,7 @@ from app.modules.enums import FrontendType
# Full detection
frontend_type = FrontendDetector.detect(
host="orion.oms.lu",
host="orion.omsflow.lu",
path="/products",
has_store_context=True
)
@@ -167,10 +167,10 @@ if FrontendDetector.is_storefront(host, path, has_store_context=True):
| Request | Host | Path | Frontend |
|---------|------|------|----------|
| Admin subdomain | admin.oms.lu | /dashboard | ADMIN |
| Store subdomain | orion.oms.lu | /products | STOREFRONT |
| Admin subdomain | admin.omsflow.lu | /dashboard | ADMIN |
| Store subdomain | orion.omsflow.lu | /products | STOREFRONT |
| Custom domain | mybakery.lu | /products | STOREFRONT |
| Platform root | oms.lu | /pricing | PLATFORM |
| Platform root | omsflow.lu | /pricing | PLATFORM |
## Request State

View File

@@ -20,7 +20,7 @@ This middleware layer is **system-wide** and enables the multi-tenant architectu
**What it does**:
- Detects platform from:
- Custom domain (e.g., `oms.lu`, `loyalty.lu`)
- Custom domain (e.g., `omsflow.lu`, `rewardflow.lu`)
- Path prefix in development (e.g., `/platforms/oms/`, `/platforms/loyalty/`)
- Default to `main` platform for localhost without prefix
- Rewrites path for platform-prefixed requests (strips `/platforms/{code}/`)
@@ -33,7 +33,7 @@ Request arrives
┌─────────────────────────────────────┐
│ Production domain? (oms.lu, etc.) │
│ Production domain? (omsflow.lu, etc.) │
└─────────────────────────────────────┘
│ YES → Use that platform
@@ -135,13 +135,13 @@ Injects: request.state.store = <Store object>
**Detection Priority** (handled by `FrontendDetector`):
```python
1. Admin subdomain (admin.oms.lu) ADMIN
1. Admin subdomain (admin.omsflow.lu) ADMIN
2. Path-based detection:
- /admin/* or /api/v1/admin/* ADMIN
- /store/* or /api/v1/store/* STORE
- /storefront/*, /shop/*, /stores/* STOREFRONT
- /api/v1/platform/* PLATFORM
3. Store subdomain (orion.oms.lu) STOREFRONT
3. Store subdomain (orion.omsflow.lu) STOREFRONT
4. Store context set by middleware STOREFRONT
5. Default PLATFORM
```

View File

@@ -123,7 +123,7 @@ The system uses different URL patterns for development vs production:
**Production (custom domains):**
- Main marketing site: `orion.lu/``main` platform
- Platform sites: `oms.lu/`, `loyalty.lu/` → specific platform
- Platform sites: `omsflow.lu/`, `rewardflow.lu/` → specific platform
### Request Processing
@@ -259,7 +259,7 @@ Request: GET /about
1. Insert platform record:
```sql
INSERT INTO platforms (code, name, description, domain, path_prefix)
VALUES ('loyalty', 'Loyalty Program', 'Customer loyalty and rewards', 'loyalty.lu', 'loyalty');
VALUES ('loyalty', 'Loyalty Program', 'Customer loyalty and rewards', 'rewardflow.lu', 'loyalty');
```
2. Create platform-specific content pages:
@@ -270,7 +270,7 @@ Request: GET /about
3. Configure routing:
- **Development:** Access at `localhost:9999/platforms/loyalty/`
- **Production:** Access at `loyalty.lu/`
- **Production:** Access at `rewardflow.lu/`
- Platform detected automatically by `PlatformContextMiddleware`
- No additional route configuration needed
@@ -285,5 +285,5 @@ Request: GET /about
| Platform | Code | Dev URL | Prod URL |
|----------|------|---------|----------|
| Main Marketing | `main` | `localhost:9999/` | `orion.lu/` |
| OMS | `oms` | `localhost:9999/platforms/oms/` | `oms.lu/` |
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |
| OMS | `oms` | `localhost:9999/platforms/oms/` | `omsflow.lu/` |
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `rewardflow.lu/` |

View File

@@ -63,14 +63,14 @@ Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its ow
|-----|----------------|
| `orion.lu/` | Main marketing site homepage |
| `orion.lu/about` | Main marketing site about page |
| `oms.lu/` | OMS platform homepage |
| `oms.lu/pricing` | OMS platform pricing page |
| `oms.lu/admin/` | Admin panel for OMS platform |
| `oms.lu/store/{code}/` | Store dashboard on OMS |
| `omsflow.lu/` | OMS platform homepage |
| `omsflow.lu/pricing` | OMS platform pricing page |
| `omsflow.lu/admin/` | Admin panel for OMS platform |
| `omsflow.lu/store/{code}/` | Store dashboard on OMS |
| `https://mybakery.lu/storefront/` | Store storefront (store's custom domain) |
| `loyalty.lu/` | Loyalty platform homepage |
| `rewardflow.lu/` | Loyalty platform homepage |
**Note:** In production, stores configure their own custom domains for storefronts. The platform domain (e.g., `oms.lu`) is used for admin and store dashboards, while storefronts use store-owned domains.
**Note:** In production, stores configure their own custom domains for storefronts. The platform domain (e.g., `omsflow.lu`) is used for admin and store dashboards, while storefronts use store-owned domains.
### Quick Reference by Platform
@@ -83,9 +83,9 @@ Dev:
Storefront: http://localhost:8000/platforms/oms/stores/{store_code}/storefront/
Prod:
Platform: https://oms.lu/
Admin: https://oms.lu/admin/
Store: https://oms.lu/store/{store_code}/
Platform: https://omsflow.lu/
Admin: https://omsflow.lu/admin/
Store: https://omsflow.lu/store/{store_code}/
Storefront: https://mybakery.lu/storefront/ (store's custom domain)
```
@@ -98,9 +98,9 @@ Dev:
Storefront: http://localhost:8000/platforms/loyalty/stores/{store_code}/storefront/
Prod:
Platform: https://loyalty.lu/
Admin: https://loyalty.lu/admin/
Store: https://loyalty.lu/store/{store_code}/
Platform: https://rewardflow.lu/
Admin: https://rewardflow.lu/admin/
Store: https://rewardflow.lu/store/{store_code}/
Storefront: https://myrewards.lu/storefront/ (store's custom domain)
```
@@ -112,7 +112,7 @@ Request arrives
┌─────────────────────────────────────┐
│ Check: Is this production domain? │
│ (oms.lu, loyalty.lu, etc.) │
│ (omsflow.lu, rewardflow.lu, etc.) │
└─────────────────────────────────────┘
├── YES → Route to that platform
@@ -139,8 +139,8 @@ Request arrives
| Platform | Code | Dev URL | Prod Domain |
|----------|------|---------|-------------|
| Main Marketing | `main` | `localhost:8000/` | `orion.lu` |
| OMS | `oms` | `localhost:8000/platforms/oms/` | `oms.lu` |
| Loyalty | `loyalty` | `localhost:8000/platforms/loyalty/` | `loyalty.lu` |
| OMS | `oms` | `localhost:8000/platforms/oms/` | `omsflow.lu` |
| Loyalty | `loyalty` | `localhost:8000/platforms/loyalty/` | `rewardflow.lu` |
| Site Builder | `site-builder` | `localhost:8000/platforms/site-builder/` | `sitebuilder.lu` |
**See:** [Multi-Platform CMS Architecture](../multi-platform-cms.md) for content management details.

View File

@@ -204,7 +204,7 @@ Migration: `alembic/versions/z5f6g7h8i9j0_add_loyalty_platform.py`
Inserts Loyalty platform with:
- code: `loyalty`
- name: `Loyalty+`
- domain: `loyalty.lu`
- domain: `rewardflow.lu`
- path_prefix: `loyalty`
- theme_config: purple color scheme
@@ -256,8 +256,8 @@ Inserts Loyalty platform with:
| Platform | Code | Dev URL | Prod URL |
|----------|------|---------|----------|
| Main Marketing | `main` | `localhost:9999/` | `orion.lu/` |
| OMS | `oms` | `localhost:9999/platforms/oms/` | `oms.lu/` |
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |
| OMS | `oms` | `localhost:9999/platforms/oms/` | `omsflow.lu/` |
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `rewardflow.lu/` |
### 7.4 Middleware Detection Logic
@@ -266,7 +266,7 @@ Request arrives
┌─────────────────────────────────────┐
│ Production domain? (oms.lu, etc.) │
│ Production domain? (omsflow.lu, etc.) │
└─────────────────────────────────────┘
│ YES → Route to that platform
@@ -324,7 +324,7 @@ Included in `docs/architecture/multi-platform-cms.md`:
### Three-Tier Content Resolution
```
Customer visits: oms.lu/stores/orion/about
Customer visits: omsflow.lu/stores/orion/about
┌─────────────────────────────────────────────────────────────┐

View File

@@ -54,7 +54,7 @@ Complete step-by-step guide for deploying Orion on a Hetzner Cloud VPS.
**Deferred (not urgent, do when all platforms ready):**
- [ ] DNS A + AAAA records for platform domains (`oms.lu`, `rewardflow.lu`)
- [ ] DNS A + AAAA records for platform domains (`omsflow.lu`, `rewardflow.lu`)
- [ ] Uncomment platform domains in Caddyfile after DNS propagation
!!! success "Progress — 2026-02-14"
@@ -63,7 +63,7 @@ Complete step-by-step guide for deploying Orion on a Hetzner Cloud VPS.
- **Wizamart → Orion rename** — 1,086 occurrences replaced across 184 files (database identifiers, email addresses, domains, config, templates, docs, seed data)
- Template renamed: `homepage-wizamart.html``homepage-orion.html`
- **Production DB rebuilt from scratch** with Orion naming (`orion_db`, `orion_user`)
- Platform domains configured in seed data: wizard.lu (main), oms.lu, rewardflow.lu (loyalty)
- Platform domains configured in seed data: wizard.lu (main), omsflow.lu, rewardflow.lu (loyalty)
- Docker volume explicitly named `orion_postgres_data`
- `.dockerignore` added — prevents `.env` from being baked into Docker images
- `env_file: .env` added to `docker-compose.yml` — containers load host env vars properly
@@ -438,7 +438,7 @@ Before setting up Caddy, point your domain's DNS to the server.
| A | `git` | `91.99.65.229` | 300 |
| A | `flower` | `91.99.65.229` | 300 |
### oms.lu (OMS Platform) — TODO
### omsflow.lu (OMS Platform) — TODO
| Type | Name | Value | TTL |
|---|---|---|---|
@@ -470,7 +470,7 @@ It should match the value in the Hetzner Cloud Console (Networking tab). Then cr
| AAAA | `git` | `2a01:4f8:1c1a:b39c::1` | 300 |
| AAAA | `flower` | `2a01:4f8:1c1a:b39c::1` | 300 |
Repeat for `oms.lu` and `rewardflow.lu`.
Repeat for `omsflow.lu` and `rewardflow.lu`.
!!! tip "DNS propagation"
Set TTL to 300 (5 minutes) initially. DNS changes can take up to 24 hours to propagate globally, but usually complete within 30 minutes. Verify with: `dig api.wizard.lu +short`
@@ -502,14 +502,14 @@ www.wizard.lu {
redir https://wizard.lu{uri} permanent
}
# ─── Platform 2: OMS (oms.lu) ───────────────────────────────
# Uncomment after DNS is configured for oms.lu
# oms.lu {
# ─── Platform 2: OMS (omsflow.lu) ───────────────────────────────
# Uncomment after DNS is configured for omsflow.lu
# omsflow.lu {
# reverse_proxy localhost:8001
# }
#
# www.oms.lu {
# redir https://oms.lu{uri} permanent
# www.omsflow.lu {
# redir https://omsflow.lu{uri} permanent
# }
# ─── Platform 3: Loyalty+ (rewardflow.lu) ──────────────────
@@ -537,14 +537,14 @@ flower.wizard.lu {
```
!!! info "How multi-platform routing works"
All platform domains (`wizard.lu`, `oms.lu`, `rewardflow.lu`) point to the **same FastAPI backend** on port 8001. The `PlatformContextMiddleware` reads the `Host` header to detect which platform the request is for. Caddy preserves the Host header by default, so no extra configuration is needed.
All platform domains (`wizard.lu`, `omsflow.lu`, `rewardflow.lu`) point to the **same FastAPI backend** on port 8001. The `PlatformContextMiddleware` reads the `Host` header to detect which platform the request is for. Caddy preserves the Host header by default, so no extra configuration is needed.
The `domain` column in the `platforms` database table must match:
| Platform | code | domain |
|---|---|---|
| Main | `main` | `wizard.lu` |
| OMS | `oms` | `oms.lu` |
| OMS | `oms` | `omsflow.lu` |
| Loyalty+ | `loyalty` | `rewardflow.lu` |
Start Caddy:
@@ -588,17 +588,17 @@ cd ~/gitea && docker compose up -d gitea
Stores on each platform use two routing modes:
- **Standard (subdomain)**: `acme.oms.lu` — included in the base subscription
- **Standard (subdomain)**: `acme.omsflow.lu` — included in the base subscription
- **Premium (custom domain)**: `acme.lu` — available with premium subscription tiers
Both modes are handled by the `StoreContextMiddleware` which reads the `Host` header, so Caddy just needs to forward requests and preserve the header.
#### Wildcard Subdomains (for store subdomains)
When stores start using subdomains like `acme.oms.lu`, add wildcard blocks:
When stores start using subdomains like `acme.omsflow.lu`, add wildcard blocks:
```caddy
*.oms.lu {
*.omsflow.lu {
reverse_proxy localhost:8001
}
@@ -1099,7 +1099,7 @@ docker stats --no-stream
|---|---|---|---|
| Orion API | 8000 | 8001 | `api.wizard.lu` |
| Main Platform | 8000 | 8001 | `wizard.lu` |
| OMS Platform | 8000 | 8001 | `oms.lu` (TODO) |
| OMS Platform | 8000 | 8001 | `omsflow.lu` (TODO) |
| Loyalty+ Platform | 8000 | 8001 | `rewardflow.lu` (TODO) |
| PostgreSQL | 5432 | 5432 | (internal only) |
| Redis | 6379 | 6380 | (internal only) |
@@ -1250,7 +1250,7 @@ After Caddy is configured:
| Gitea | `https://git.wizard.lu` |
| Flower | `https://flower.wizard.lu` |
| Grafana | `https://grafana.wizard.lu` |
| OMS Platform | `https://oms.lu` (after DNS) |
| OMS Platform | `https://omsflow.lu` (after DNS) |
| Loyalty+ Platform | `https://rewardflow.lu` (after DNS) |
Direct IP access (temporary, until firewall rules are removed):

View File

@@ -173,20 +173,20 @@ Login as a customer (e.g., `customer1@orion.example.com`).
---
## Production URLs (loyalty.lu)
## Production URLs (rewardflow.lu)
In production, the platform uses **domain-based routing** instead of the `/platforms/loyalty/` path prefix.
Store context is detected via **custom domains** (registered in `store_domains` table)
or **subdomains** of `loyalty.lu` (from `Store.subdomain`).
or **subdomains** of `rewardflow.lu` (from `Store.subdomain`).
### URL Routing Summary
| Routing mode | Priority | Pattern | Example |
|-------------|----------|---------|---------|
| Platform domain | — | `loyalty.lu/...` | Admin pages, public API |
| Platform domain | — | `rewardflow.lu/...` | Admin pages, public API |
| Store custom domain | 1 (highest) | `{custom_domain}/...` | Store with its own domain (overrides merchant domain) |
| Merchant domain | 2 | `{merchant_domain}/...` | All stores inherit merchant's domain |
| Store subdomain | 3 (fallback) | `{store_code}.loyalty.lu/...` | Default when no custom/merchant domain |
| Store subdomain | 3 (fallback) | `{store_code}.rewardflow.lu/...` | Default when no custom/merchant domain |
!!! info "Domain Resolution Priority"
When a request arrives, the middleware resolves the store in this order:
@@ -295,58 +295,58 @@ store when the URL includes `/store/{store_code}/...`.
### Case 3: Store without custom domain (uses platform subdomain)
The store has no entry in `store_domains` and the merchant has no registered domain.
**All** store URLs are served via a subdomain of the platform domain: `{store_code}.loyalty.lu`.
**All** store URLs are served via a subdomain of the platform domain: `{store_code}.rewardflow.lu`.
**Storefront (customer-facing):**
| Page | Production URL |
|------|----------------|
| Loyalty Dashboard | `https://bookstore.loyalty.lu/account/loyalty` |
| Transaction History | `https://bookstore.loyalty.lu/account/loyalty/history` |
| Self-Enrollment | `https://bookstore.loyalty.lu/loyalty/join` |
| Enrollment Success | `https://bookstore.loyalty.lu/loyalty/join/success` |
| Loyalty Dashboard | `https://bookstore.rewardflow.lu/account/loyalty` |
| Transaction History | `https://bookstore.rewardflow.lu/account/loyalty/history` |
| Self-Enrollment | `https://bookstore.rewardflow.lu/loyalty/join` |
| Enrollment Success | `https://bookstore.rewardflow.lu/loyalty/join/success` |
**Storefront API:**
| Method | Production URL |
|--------|----------------|
| GET card | `https://bookstore.loyalty.lu/api/storefront/loyalty/card` |
| GET transactions | `https://bookstore.loyalty.lu/api/storefront/loyalty/transactions` |
| POST enroll | `https://bookstore.loyalty.lu/api/storefront/loyalty/enroll` |
| GET program | `https://bookstore.loyalty.lu/api/storefront/loyalty/program` |
| GET card | `https://bookstore.rewardflow.lu/api/storefront/loyalty/card` |
| GET transactions | `https://bookstore.rewardflow.lu/api/storefront/loyalty/transactions` |
| POST enroll | `https://bookstore.rewardflow.lu/api/storefront/loyalty/enroll` |
| GET program | `https://bookstore.rewardflow.lu/api/storefront/loyalty/program` |
**Store backend (staff/owner):**
| Page | Production URL |
|------|----------------|
| Store Login | `https://bookstore.loyalty.lu/store/BOOKSTORE/login` |
| Terminal | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/terminal` |
| Cards | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/cards` |
| Settings | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/settings` |
| Stats | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/stats` |
| Store Login | `https://bookstore.rewardflow.lu/store/BOOKSTORE/login` |
| Terminal | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/terminal` |
| Cards | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/cards` |
| Settings | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/settings` |
| Stats | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/stats` |
**Store API:**
| Method | Production URL |
|--------|----------------|
| GET program | `https://bookstore.loyalty.lu/api/store/loyalty/program` |
| POST stamp | `https://bookstore.loyalty.lu/api/store/loyalty/stamp` |
| POST points | `https://bookstore.loyalty.lu/api/store/loyalty/points` |
| POST enroll | `https://bookstore.loyalty.lu/api/store/loyalty/cards/enroll` |
| POST lookup | `https://bookstore.loyalty.lu/api/store/loyalty/cards/lookup` |
| GET program | `https://bookstore.rewardflow.lu/api/store/loyalty/program` |
| POST stamp | `https://bookstore.rewardflow.lu/api/store/loyalty/stamp` |
| POST points | `https://bookstore.rewardflow.lu/api/store/loyalty/points` |
| POST enroll | `https://bookstore.rewardflow.lu/api/store/loyalty/cards/enroll` |
| POST lookup | `https://bookstore.rewardflow.lu/api/store/loyalty/cards/lookup` |
### Platform Admin & Public API (always on platform domain)
| Page / Endpoint | Production URL |
|-----------------|----------------|
| Admin Programs | `https://loyalty.lu/admin/loyalty/programs` |
| Admin Analytics | `https://loyalty.lu/admin/loyalty/analytics` |
| Admin Merchant Detail | `https://loyalty.lu/admin/loyalty/merchants/{id}` |
| Admin Merchant Settings | `https://loyalty.lu/admin/loyalty/merchants/{id}/settings` |
| Admin API - Programs | `GET https://loyalty.lu/api/admin/loyalty/programs` |
| Admin API - Stats | `GET https://loyalty.lu/api/admin/loyalty/stats` |
| Public API - Program | `GET https://loyalty.lu/api/loyalty/programs/ORION` |
| Apple Wallet Pass | `GET https://loyalty.lu/api/loyalty/passes/apple/{serial}.pkpass` |
| Admin Programs | `https://rewardflow.lu/admin/loyalty/programs` |
| Admin Analytics | `https://rewardflow.lu/admin/loyalty/analytics` |
| Admin Merchant Detail | `https://rewardflow.lu/admin/loyalty/merchants/{id}` |
| Admin Merchant Settings | `https://rewardflow.lu/admin/loyalty/merchants/{id}/settings` |
| Admin API - Programs | `GET https://rewardflow.lu/api/admin/loyalty/programs` |
| Admin API - Stats | `GET https://rewardflow.lu/api/admin/loyalty/stats` |
| Public API - Program | `GET https://rewardflow.lu/api/loyalty/programs/ORION` |
| Apple Wallet Pass | `GET https://rewardflow.lu/api/loyalty/passes/apple/{serial}.pkpass` |
### Domain configuration per store (current DB state)
@@ -364,11 +364,11 @@ The store has no entry in `store_domains` and the merchant has no registered dom
|-------|----------|---------------------|------------------|
| ORION | WizaCorp | `orion.shop` | `orion.shop` (store override) |
| FASHIONHUB | Fashion Group | `fashionhub.store` | `fashionhub.store` (store override) |
| WIZAGADGETS | WizaCorp | _(none)_ | `wizagadgets.loyalty.lu` (subdomain fallback) |
| WIZAHOME | WizaCorp | _(none)_ | `wizahome.loyalty.lu` (subdomain fallback) |
| FASHIONOUTLET | Fashion Group | _(none)_ | `fashionoutlet.loyalty.lu` (subdomain fallback) |
| BOOKSTORE | BookWorld | _(none)_ | `bookstore.loyalty.lu` (subdomain fallback) |
| BOOKDIGITAL | BookWorld | _(none)_ | `bookdigital.loyalty.lu` (subdomain fallback) |
| WIZAGADGETS | WizaCorp | _(none)_ | `wizagadgets.rewardflow.lu` (subdomain fallback) |
| WIZAHOME | WizaCorp | _(none)_ | `wizahome.rewardflow.lu` (subdomain fallback) |
| FASHIONOUTLET | Fashion Group | _(none)_ | `fashionoutlet.rewardflow.lu` (subdomain fallback) |
| BOOKSTORE | BookWorld | _(none)_ | `bookstore.rewardflow.lu` (subdomain fallback) |
| BOOKDIGITAL | BookWorld | _(none)_ | `bookdigital.rewardflow.lu` (subdomain fallback) |
!!! example "After merchant domain registration"
If WizaCorp registers `myloyaltyprogram.lu` as their merchant domain, the table becomes:
@@ -384,7 +384,7 @@ The store has no entry in `store_domains` and the merchant has no registered dom
1. **Store custom domain**: `orion.shop` (from `store_domains` table) — highest priority
2. **Merchant domain**: `myloyaltyprogram.lu` (from `merchant_domains` table) — inherited default
3. **Subdomain fallback**: `orion.loyalty.lu` (from `Store.subdomain` + platform domain)
3. **Subdomain fallback**: `orion.rewardflow.lu` (from `Store.subdomain` + platform domain)
---
@@ -420,7 +420,7 @@ flowchart TD
1. Login as `john.owner@wizacorp.com` and navigate to billing:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/billing`
- Prod (custom domain): `https://orion.shop/store/ORION/billing`
- Prod (subdomain): `https://orion.loyalty.lu/store/ORION/billing`
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/billing`
2. View available subscription tiers:
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/store/billing/tiers`
- API Prod: `GET https://{store_domain}/api/v1/store/billing/tiers`
@@ -441,19 +441,19 @@ flowchart TD
1. Platform admin registers a merchant domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/{merchant_id}/domains`
- API Prod: `POST https://loyalty.lu/api/v1/admin/merchants/{merchant_id}/domains`
- API Prod: `POST https://rewardflow.lu/api/v1/admin/merchants/{merchant_id}/domains`
- Body: `{"domain": "myloyaltyprogram.lu", "is_primary": true}`
2. The API returns a `verification_token` for DNS verification
3. Get DNS verification instructions:
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
- API Prod: `GET https://loyalty.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
- API Prod: `GET https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
4. Merchant adds a DNS TXT record: `_orion-verify.myloyaltyprogram.lu TXT {verification_token}`
5. Verify the domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
- API Prod: `POST https://loyalty.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
- API Prod: `POST https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
6. Activate the domain:
- API Dev: `PUT http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}`
- API Prod: `PUT https://loyalty.lu/api/v1/admin/merchants/domains/merchant/{domain_id}`
- API Prod: `PUT https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}`
- Body: `{"is_active": true}`
7. All merchant stores now inherit `myloyaltyprogram.lu` as their effective domain
@@ -463,7 +463,7 @@ If a store needs its own domain (e.g., ORION is a major brand and wants `mysuper
1. Platform admin registers a store domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/stores/{store_id}/domains`
- API Prod: `POST https://loyalty.lu/api/v1/admin/stores/{store_id}/domains`
- API Prod: `POST https://rewardflow.lu/api/v1/admin/stores/{store_id}/domains`
- Body: `{"domain": "mysuperloyaltyprogram.lu", "is_primary": true}`
2. Follow the same DNS verification and activation flow as merchant domains
3. Once active, this store's effective domain becomes `mysuperloyaltyprogram.lu` (overrides merchant domain)
@@ -509,11 +509,11 @@ flowchart TD
1. Login as `john.owner@wizacorp.com` at:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/login`
- Prod (custom domain): `https://orion.shop/store/ORION/login`
- Prod (subdomain): `https://orion.loyalty.lu/store/ORION/login`
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/login`
2. Navigate to loyalty settings:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/settings`
- Prod (custom domain): `https://orion.shop/store/ORION/loyalty/settings`
- Prod (subdomain): `https://orion.loyalty.lu/store/ORION/loyalty/settings`
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/loyalty/settings`
3. Create a new loyalty program:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/program`
- Prod: `POST https://{store_domain}/api/store/loyalty/program`
@@ -526,7 +526,7 @@ flowchart TD
- Prod: `POST https://{store_domain}/api/store/loyalty/pins`
9. Verify program is live - check from another store (same merchant):
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/settings`
- Prod (subdomain): `https://wizagadgets.loyalty.lu/store/WIZAGADGETS/loyalty/settings`
- Prod (subdomain): `https://wizagadgets.rewardflow.lu/store/WIZAGADGETS/loyalty/settings`
**Expected blockers in current state:**
@@ -649,19 +649,19 @@ flowchart TD
1. Visit the public enrollment page:
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join`
- Prod (custom domain): `https://orion.shop/loyalty/join`
- Prod (subdomain): `https://bookstore.loyalty.lu/loyalty/join`
- Prod (subdomain): `https://bookstore.rewardflow.lu/loyalty/join`
2. Fill in enrollment form (email, name)
3. Submit enrollment:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/storefront/loyalty/enroll`
- Prod (custom domain): `POST https://orion.shop/api/storefront/loyalty/enroll`
- Prod (subdomain): `POST https://bookstore.loyalty.lu/api/storefront/loyalty/enroll`
- Prod (subdomain): `POST https://bookstore.rewardflow.lu/api/storefront/loyalty/enroll`
4. Redirected to success page:
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (custom domain): `https://orion.shop/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (subdomain): `https://bookstore.loyalty.lu/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (subdomain): `https://bookstore.rewardflow.lu/loyalty/join/success?card=XXXX-XXXX-XXXX`
5. Optionally download Apple Wallet pass:
- Dev: `GET http://localhost:9999/platforms/loyalty/api/loyalty/passes/apple/{serial_number}.pkpass`
- Prod: `GET https://loyalty.lu/api/loyalty/passes/apple/{serial_number}.pkpass`
- Prod: `GET https://rewardflow.lu/api/loyalty/passes/apple/{serial_number}.pkpass`
---
@@ -676,13 +676,13 @@ flowchart TD
2. View loyalty dashboard (card balance, available rewards):
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty`
- Prod (custom domain): `https://orion.shop/account/loyalty`
- Prod (subdomain): `https://bookstore.loyalty.lu/account/loyalty`
- Prod (subdomain): `https://bookstore.rewardflow.lu/account/loyalty`
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/storefront/loyalty/card`
- API Prod: `GET https://orion.shop/api/storefront/loyalty/card`
3. View full transaction history:
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty/history`
- Prod (custom domain): `https://orion.shop/account/loyalty/history`
- Prod (subdomain): `https://bookstore.loyalty.lu/account/loyalty/history`
- Prod (subdomain): `https://bookstore.rewardflow.lu/account/loyalty/history`
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/storefront/loyalty/transactions`
- API Prod: `GET https://orion.shop/api/storefront/loyalty/transactions`
@@ -698,22 +698,22 @@ flowchart TD
1. Login as admin
2. View all programs:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/programs`
- Prod: `https://loyalty.lu/admin/loyalty/programs`
- Prod: `https://rewardflow.lu/admin/loyalty/programs`
3. View platform-wide analytics:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/analytics`
- Prod: `https://loyalty.lu/admin/loyalty/analytics`
- Prod: `https://rewardflow.lu/admin/loyalty/analytics`
4. Drill into WizaCorp's program:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/1`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1`
5. Manage WizaCorp's merchant-level settings:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/1/settings`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
- API Dev: `PATCH http://localhost:9999/platforms/loyalty/api/admin/loyalty/merchants/1/settings`
- API Prod: `PATCH https://loyalty.lu/api/admin/loyalty/merchants/1/settings`
- API Prod: `PATCH https://rewardflow.lu/api/admin/loyalty/merchants/1/settings`
6. Adjust settings: PIN policy, self-enrollment toggle, void permissions
7. Check other merchants:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/2`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/2`
---
@@ -752,7 +752,7 @@ flowchart TD
**Precondition:** Cross-location redemption must be enabled in merchant settings:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/1/settings`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
**Steps:**