feat(loyalty): fix Google Wallet integration and improve enrollment flow

- Fix Google Wallet class creation: add required issuerName field (merchant name),
  programLogo with default logo fallback, hexBackgroundColor default
- Add default loyalty logo assets (200px + 512px) for programs without custom logos
- Smart retry: skip retries on 400/401/403/404 client errors (not transient)
- Fix enrollment success page: use sessionStorage for wallet URLs instead of
  authenticated API call (self-enrolled customers have no session)
- Hide wallet section on success page when no wallet URLs available
- Wire up T&C modal on enrollment page with program.terms_text
- Add startup validation for Google/Apple Wallet configs in lifespan
- Add admin wallet status dashboard endpoint and UI (moved to service layer)
- Fix Apple Wallet push notifications with real APNs HTTP/2 implementation
- Fix docs: correct enrollment URLs (port, path segments, /v1 prefix)
- Fix test assertion: !loyalty-enroll! → !enrollment!

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 17:32:55 +01:00
parent f766a72480
commit 8c8975239a
15 changed files with 828 additions and 239 deletions

View File

@@ -62,9 +62,9 @@
---
## Dev URLs (localhost:9999)
## Dev URLs (localhost:8000)
The dev server uses path-based platform routing: `http://localhost:9999/platforms/loyalty/...`
The dev server uses path-based platform routing: `http://localhost:8000/platforms/loyalty/...`
### 1. Platform Admin Pages
@@ -72,14 +72,14 @@ Login as: `admin@orion.lu` or `samir.boulahtit@gmail.com`
| Page | Dev URL |
|------|---------|
| Programs Dashboard | `http://localhost:9999/platforms/loyalty/admin/loyalty/programs` |
| Analytics | `http://localhost:9999/platforms/loyalty/admin/loyalty/analytics` |
| WizaCorp Detail | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1` |
| WizaCorp Settings | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings` |
| Fashion Group Detail | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2` |
| Fashion Group Settings | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2/settings` |
| BookWorld Detail | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/3` |
| BookWorld Settings | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/3/settings` |
| Programs Dashboard | `http://localhost:8000/platforms/loyalty/admin/loyalty/programs` |
| Analytics | `http://localhost:8000/platforms/loyalty/admin/loyalty/analytics` |
| WizaCorp Detail | `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/1` |
| WizaCorp Settings | `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/1/settings` |
| Fashion Group Detail | `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/2` |
| Fashion Group Settings | `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/2/settings` |
| BookWorld Detail | `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/3` |
| BookWorld Settings | `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/3/settings` |
### 2. Merchant Owner / Store Pages
@@ -89,87 +89,87 @@ Login as the store owner, then navigate to any of their stores.
| Page | Dev URL |
|------|---------|
| Terminal | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal` |
| Cards | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards` |
| Settings | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/settings` |
| Stats | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/stats` |
| Enroll Customer | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/enroll` |
| Terminal | `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/terminal` |
| Cards | `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/cards` |
| Settings | `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/settings` |
| Stats | `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/stats` |
| Enroll Customer | `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/enroll` |
**Fashion Group (jane.owner@fashiongroup.com):**
| Page | Dev URL |
|------|---------|
| Terminal | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/terminal` |
| Cards | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/cards` |
| Settings | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/settings` |
| Stats | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/stats` |
| Enroll Customer | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/enroll` |
| Terminal | `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/loyalty/terminal` |
| Cards | `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/loyalty/cards` |
| Settings | `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/loyalty/settings` |
| Stats | `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/loyalty/stats` |
| Enroll Customer | `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/loyalty/enroll` |
**BookWorld (bob.owner@bookworld.com):**
| Page | Dev URL |
|------|---------|
| Terminal | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/terminal` |
| Cards | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/cards` |
| Settings | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/settings` |
| Stats | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/stats` |
| Enroll Customer | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/enroll` |
| Terminal | `http://localhost:8000/platforms/loyalty/store/BOOKSTORE/loyalty/terminal` |
| Cards | `http://localhost:8000/platforms/loyalty/store/BOOKSTORE/loyalty/cards` |
| Settings | `http://localhost:8000/platforms/loyalty/store/BOOKSTORE/loyalty/settings` |
| Stats | `http://localhost:8000/platforms/loyalty/store/BOOKSTORE/loyalty/stats` |
| Enroll Customer | `http://localhost:8000/platforms/loyalty/store/BOOKSTORE/loyalty/enroll` |
### 3. Customer Storefront Pages
Login as a customer (e.g., `customer1@orion.example.com`).
!!! note "Store domain required"
Storefront pages require a store domain context. Only ORION (`orion.shop`)
and FASHIONHUB (`fashionhub.store`) have domains configured. In dev, storefront
routes may need to be accessed through the store's domain or platform path.
!!! note "Store code required in dev"
Storefront pages in dev require the store code in the URL path:
`/platforms/loyalty/storefront/{STORE_CODE}/...`. In production, the store
is resolved from the domain (custom domain, merchant domain, or subdomain).
| Page | Dev URL |
| Page | Dev URL (FASHIONHUB example) |
|------|---------|
| Loyalty Dashboard | `http://localhost:9999/platforms/loyalty/account/loyalty` |
| Transaction History | `http://localhost:9999/platforms/loyalty/account/loyalty/history` |
| Loyalty Dashboard | `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/account/loyalty` |
| Transaction History | `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/account/loyalty/history` |
### 4. Public Pages (No Auth)
| Page | Dev URL |
| Page | Dev URL (FASHIONHUB example) |
|------|---------|
| Self-Enrollment | `http://localhost:9999/platforms/loyalty/loyalty/join` |
| Enrollment Success | `http://localhost:9999/platforms/loyalty/loyalty/join/success` |
| Self-Enrollment | `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/loyalty/join` |
| Enrollment Success | `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/loyalty/join/success` |
### 5. API Endpoints
**Admin API** (prefix: `/platforms/loyalty/api/admin/loyalty/`):
**Admin API** (prefix: `/platforms/loyalty/api/v1/admin/loyalty/`):
| Method | Dev URL |
|--------|---------|
| GET | `http://localhost:9999/platforms/loyalty/api/admin/loyalty/programs` |
| GET | `http://localhost:9999/platforms/loyalty/api/admin/loyalty/stats` |
| GET | `http://localhost:8000/platforms/loyalty/api/v1/admin/loyalty/programs` |
| GET | `http://localhost:8000/platforms/loyalty/api/v1/admin/loyalty/stats` |
**Store API** (prefix: `/platforms/loyalty/api/store/loyalty/`):
**Store API** (prefix: `/platforms/loyalty/api/v1/store/loyalty/`):
| Method | Endpoint | Dev URL |
|--------|----------|---------|
| GET | program | `http://localhost:9999/platforms/loyalty/api/store/loyalty/program` |
| POST | program | `http://localhost:9999/platforms/loyalty/api/store/loyalty/program` |
| POST | stamp | `http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp` |
| POST | points | `http://localhost:9999/platforms/loyalty/api/store/loyalty/points` |
| POST | enroll | `http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/enroll` |
| POST | lookup | `http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup` |
| GET | program | `http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/program` |
| POST | program | `http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/program` |
| POST | stamp | `http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/stamp` |
| POST | points | `http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/points` |
| POST | enroll | `http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/enroll` |
| POST | lookup | `http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/lookup` |
**Storefront API** (prefix: `/platforms/loyalty/api/storefront/`):
**Storefront API** (prefix: `/platforms/loyalty/api/v1/storefront/`):
| Method | Endpoint | Dev URL |
|--------|----------|---------|
| GET | program | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/program` |
| POST | enroll | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/enroll` |
| GET | card | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/card` |
| GET | transactions | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/transactions` |
| GET | program | `http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/program` |
| POST | enroll | `http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/enroll` |
| GET | card | `http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/card` |
| GET | transactions | `http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/transactions` |
**Public API** (prefix: `/platforms/loyalty/api/loyalty/`):
**Public API** (prefix: `/platforms/loyalty/api/v1/loyalty/`):
| Method | Endpoint | Dev URL |
|--------|----------|---------|
| GET | program | `http://localhost:9999/platforms/loyalty/api/loyalty/programs/ORION` |
| GET | program | `http://localhost:8000/platforms/loyalty/api/v1/loyalty/programs/ORION` |
---
@@ -213,10 +213,10 @@ The store has a verified entry in the `store_domains` table. **All** store URLs
| Method | Production URL |
|--------|----------------|
| GET card | `https://orion.shop/api/storefront/loyalty/card` |
| GET transactions | `https://orion.shop/api/storefront/loyalty/transactions` |
| POST enroll | `https://orion.shop/api/storefront/loyalty/enroll` |
| GET program | `https://orion.shop/api/storefront/loyalty/program` |
| GET card | `https://orion.shop/api/v1/storefront/loyalty/card` |
| GET transactions | `https://orion.shop/api/v1/storefront/loyalty/transactions` |
| POST enroll | `https://orion.shop/api/v1/storefront/loyalty/enroll` |
| GET program | `https://orion.shop/api/v1/storefront/loyalty/program` |
**Store backend (staff/owner):**
@@ -234,12 +234,12 @@ The store has a verified entry in the `store_domains` table. **All** store URLs
| Method | Production URL |
|--------|----------------|
| GET program | `https://orion.shop/api/store/loyalty/program` |
| POST program | `https://orion.shop/api/store/loyalty/program` |
| POST stamp | `https://orion.shop/api/store/loyalty/stamp` |
| POST points | `https://orion.shop/api/store/loyalty/points` |
| POST enroll | `https://orion.shop/api/store/loyalty/cards/enroll` |
| POST lookup | `https://orion.shop/api/store/loyalty/cards/lookup` |
| GET program | `https://orion.shop/api/v1/store/loyalty/program` |
| POST program | `https://orion.shop/api/v1/store/loyalty/program` |
| POST stamp | `https://orion.shop/api/v1/store/loyalty/stamp` |
| POST points | `https://orion.shop/api/v1/store/loyalty/points` |
| POST enroll | `https://orion.shop/api/v1/store/loyalty/cards/enroll` |
| POST lookup | `https://orion.shop/api/v1/store/loyalty/cards/lookup` |
### Case 2: Store with merchant domain (e.g., `myloyaltyprogram.lu`)
@@ -261,10 +261,10 @@ store when the URL includes `/store/{store_code}/...`.
| Method | Production URL |
|--------|----------------|
| GET card | `https://myloyaltyprogram.lu/api/storefront/loyalty/card` |
| GET transactions | `https://myloyaltyprogram.lu/api/storefront/loyalty/transactions` |
| POST enroll | `https://myloyaltyprogram.lu/api/storefront/loyalty/enroll` |
| GET program | `https://myloyaltyprogram.lu/api/storefront/loyalty/program` |
| GET card | `https://myloyaltyprogram.lu/api/v1/storefront/loyalty/card` |
| GET transactions | `https://myloyaltyprogram.lu/api/v1/storefront/loyalty/transactions` |
| POST enroll | `https://myloyaltyprogram.lu/api/v1/storefront/loyalty/enroll` |
| GET program | `https://myloyaltyprogram.lu/api/v1/storefront/loyalty/program` |
**Store backend (staff/owner):**
@@ -280,11 +280,11 @@ store when the URL includes `/store/{store_code}/...`.
| Method | Production URL |
|--------|----------------|
| GET program | `https://myloyaltyprogram.lu/api/store/loyalty/program` |
| POST stamp | `https://myloyaltyprogram.lu/api/store/loyalty/stamp` |
| POST points | `https://myloyaltyprogram.lu/api/store/loyalty/points` |
| POST enroll | `https://myloyaltyprogram.lu/api/store/loyalty/cards/enroll` |
| POST lookup | `https://myloyaltyprogram.lu/api/store/loyalty/cards/lookup` |
| GET program | `https://myloyaltyprogram.lu/api/v1/store/loyalty/program` |
| POST stamp | `https://myloyaltyprogram.lu/api/v1/store/loyalty/stamp` |
| POST points | `https://myloyaltyprogram.lu/api/v1/store/loyalty/points` |
| POST enroll | `https://myloyaltyprogram.lu/api/v1/store/loyalty/cards/enroll` |
| POST lookup | `https://myloyaltyprogram.lu/api/v1/store/loyalty/cards/lookup` |
!!! note "Merchant domain resolves to first active store"
When a customer visits `myloyaltyprogram.lu` without a `/store/{code}/...` path,
@@ -310,10 +310,10 @@ The store has no entry in `store_domains` and the merchant has no registered dom
| Method | Production URL |
|--------|----------------|
| 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` |
| GET card | `https://bookstore.rewardflow.lu/api/v1/storefront/loyalty/card` |
| GET transactions | `https://bookstore.rewardflow.lu/api/v1/storefront/loyalty/transactions` |
| POST enroll | `https://bookstore.rewardflow.lu/api/v1/storefront/loyalty/enroll` |
| GET program | `https://bookstore.rewardflow.lu/api/v1/storefront/loyalty/program` |
**Store backend (staff/owner):**
@@ -329,11 +329,11 @@ The store has no entry in `store_domains` and the merchant has no registered dom
| Method | Production URL |
|--------|----------------|
| 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` |
| GET program | `https://bookstore.rewardflow.lu/api/v1/store/loyalty/program` |
| POST stamp | `https://bookstore.rewardflow.lu/api/v1/store/loyalty/stamp` |
| POST points | `https://bookstore.rewardflow.lu/api/v1/store/loyalty/points` |
| POST enroll | `https://bookstore.rewardflow.lu/api/v1/store/loyalty/cards/enroll` |
| POST lookup | `https://bookstore.rewardflow.lu/api/v1/store/loyalty/cards/lookup` |
### Platform Admin & Public API (always on platform domain)
@@ -343,10 +343,10 @@ The store has no entry in `store_domains` and the merchant has no registered dom
| 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` |
| Admin API - Programs | `GET https://rewardflow.lu/api/v1/admin/loyalty/programs` |
| Admin API - Stats | `GET https://rewardflow.lu/api/v1/admin/loyalty/stats` |
| Public API - Program | `GET https://rewardflow.lu/api/v1/loyalty/programs/ORION` |
| Apple Wallet Pass | `GET https://rewardflow.lu/api/v1/loyalty/passes/apple/{serial}.pkpass` |
### Domain configuration per store (current DB state)
@@ -418,19 +418,19 @@ flowchart TD
**Step 1: Subscribe to the platform**
1. Login as `john.owner@wizacorp.com` and navigate to billing:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/billing`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/billing`
- Prod (custom domain): `https://orion.shop/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 Dev: `GET http://localhost:8000/platforms/loyalty/api/v1/store/billing/tiers`
- API Prod: `GET https://{store_domain}/api/v1/store/billing/tiers`
3. Select a tier and initiate Stripe checkout:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/store/billing/checkout`
- API Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/billing/checkout`
- API Prod: `POST https://{store_domain}/api/v1/store/billing/checkout`
4. Complete payment on Stripe checkout page
5. Webhook `checkout.session.completed` activates the subscription
6. Verify subscription is active:
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/store/billing/subscription`
- API Dev: `GET http://localhost:8000/platforms/loyalty/api/v1/store/billing/subscription`
- API Prod: `GET https://{store_domain}/api/v1/store/billing/subscription`
**Step 2: Register merchant domain (admin action)**
@@ -440,19 +440,19 @@ flowchart TD
registers the domain on behalf of the merchant via the admin API.
1. Platform admin registers a merchant domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/{merchant_id}/domains`
- API Dev: `POST http://localhost:8000/platforms/loyalty/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 Dev: `GET http://localhost:8000/platforms/loyalty/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 Dev: `POST http://localhost:8000/platforms/loyalty/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 Dev: `PUT http://localhost:8000/platforms/loyalty/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
@@ -462,7 +462,7 @@ flowchart TD
If a store needs its own domain (e.g., ORION is a major brand and wants `mysuperloyaltyprogram.lu`):
1. Platform admin registers a store domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/stores/{store_id}/domains`
- API Dev: `POST http://localhost:8000/platforms/loyalty/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
@@ -507,25 +507,25 @@ flowchart TD
**Steps:**
1. Login as `john.owner@wizacorp.com` at:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/login`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/login`
- Prod (custom domain): `https://orion.shop/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`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/settings`
- Prod (custom domain): `https://orion.shop/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`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/program`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/program`
4. Choose loyalty type (stamps, points, or hybrid)
5. Configure program parameters (stamp target, points-per-euro, rewards)
6. Set branding (card color, logo, hero image)
7. Configure anti-fraud (cooldown, daily limits, PIN requirements)
8. Create staff PINs:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/pins`
- Prod: `POST https://{store_domain}/api/store/loyalty/pins`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/pins`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/pins`
9. Verify program is live - check from another store (same merchant):
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/settings`
- Dev: `http://localhost:8000/platforms/loyalty/store/WIZAGADGETS/loyalty/settings`
- Prod (subdomain): `https://wizagadgets.rewardflow.lu/store/WIZAGADGETS/loyalty/settings`
**Expected blockers in current state:**
@@ -563,23 +563,23 @@ flowchart TD
**Steps:**
1. Login as `alice.manager@wizacorp.com` and open the terminal:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/terminal`
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
2. Scan customer QR code or enter card number:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/cards/lookup`
3. Enter staff PIN for verification
4. Add stamp:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp`
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/stamp`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/stamp`
5. If target reached, redeem reward:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp/redeem`
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp/redeem`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/stamp/redeem`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/stamp/redeem`
6. View updated card:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards/{card_id}`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/cards/{card_id}`
- Prod: `https://{store_domain}/store/ORION/loyalty/cards/{card_id}`
7. Browse all cards:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/cards`
- Prod: `https://{store_domain}/store/ORION/loyalty/cards`
**Anti-fraud scenarios to test:**
@@ -611,20 +611,20 @@ flowchart TD
**Steps:**
1. Open the terminal:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/terminal`
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
2. Lookup card:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/cards/lookup`
3. Enter purchase amount (e.g., 25.00 EUR)
4. Earn points (auto-calculated at 10 pts/EUR = 250 points):
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/points`
- Prod: `POST https://{store_domain}/api/store/loyalty/points`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/points`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/points`
5. If enough balance, redeem points for reward:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/points/redeem`
- Prod: `POST https://{store_domain}/api/store/loyalty/points/redeem`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/points/redeem`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/points/redeem`
6. Check store-level stats:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/stats`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/stats`
- Prod: `https://{store_domain}/store/ORION/loyalty/stats`
---
@@ -647,21 +647,21 @@ flowchart TD
**Steps:**
1. Visit the public enrollment page:
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join`
- Prod (custom domain): `https://orion.shop/loyalty/join`
- Dev: `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/loyalty/join`
- Prod (custom domain): `https://fashionhub.store/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.rewardflow.lu/api/storefront/loyalty/enroll`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/enroll`
- Prod (custom domain): `POST https://fashionhub.store/api/v1/storefront/loyalty/enroll`
- Prod (subdomain): `POST https://bookstore.rewardflow.lu/api/v1/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`
- Dev: `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (custom domain): `https://fashionhub.store/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://rewardflow.lu/api/loyalty/passes/apple/{serial_number}.pkpass`
- Dev: `GET http://localhost:8000/platforms/loyalty/api/v1/loyalty/passes/apple/{serial_number}.pkpass`
- Prod: `GET https://rewardflow.lu/api/v1/loyalty/passes/apple/{serial_number}.pkpass`
---
@@ -674,17 +674,17 @@ flowchart TD
1. Login as customer at the storefront
2. View loyalty dashboard (card balance, available rewards):
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty`
- Prod (custom domain): `https://orion.shop/account/loyalty`
- Dev: `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/account/loyalty`
- Prod (custom domain): `https://fashionhub.store/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`
- API Dev: `GET http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/card`
- API Prod: `GET https://fashionhub.store/api/v1/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`
- Dev: `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/account/loyalty/history`
- Prod (custom domain): `https://fashionhub.store/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`
- API Dev: `GET http://localhost:8000/platforms/loyalty/api/v1/storefront/loyalty/transactions`
- API Prod: `GET https://fashionhub.store/api/v1/storefront/loyalty/transactions`
---
@@ -697,22 +697,22 @@ flowchart TD
1. Login as admin
2. View all programs:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/programs`
- Dev: `http://localhost:8000/platforms/loyalty/admin/loyalty/programs`
- Prod: `https://rewardflow.lu/admin/loyalty/programs`
3. View platform-wide analytics:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/analytics`
- Dev: `http://localhost:8000/platforms/loyalty/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`
- Dev: `http://localhost:8000/platforms/loyalty/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`
- Dev: `http://localhost:8000/platforms/loyalty/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://rewardflow.lu/api/admin/loyalty/merchants/1/settings`
- API Dev: `PATCH http://localhost:8000/platforms/loyalty/api/v1/admin/loyalty/merchants/1/settings`
- API Prod: `PATCH https://rewardflow.lu/api/v1/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`
- Dev: `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/2`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/2`
---
@@ -725,21 +725,21 @@ flowchart TD
**Steps:**
1. Open terminal and lookup card:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/terminal`
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/cards/lookup`
2. View the card's transaction history to find the transaction to void:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards/{card_id}`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/cards/{card_id}`
- Prod: `https://{store_domain}/store/ORION/loyalty/cards/{card_id}`
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/{card_id}/transactions`
- API Prod: `GET https://{store_domain}/api/store/loyalty/cards/{card_id}/transactions`
- API Dev: `GET http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/{card_id}/transactions`
- API Prod: `GET https://{store_domain}/api/v1/store/loyalty/cards/{card_id}/transactions`
3. Void a stamp transaction:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp/void`
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp/void`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/stamp/void`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/stamp/void`
4. Or void a points transaction:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/points/void`
- Prod: `POST https://{store_domain}/api/store/loyalty/points/void`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/points/void`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/points/void`
5. Verify: original and void transactions are linked in the audit log
---
@@ -751,28 +751,28 @@ flowchart TD
**Precondition:** Cross-location redemption must be enabled in merchant settings:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
- Dev: `http://localhost:8000/platforms/loyalty/admin/loyalty/merchants/1/settings`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
**Steps:**
1. Staff at ORION adds stamps to customer's card:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
- Dev: `http://localhost:8000/platforms/loyalty/store/ORION/loyalty/terminal`
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp`
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/stamp`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/stamp`
2. Customer visits WIZAGADGETS
3. Staff at WIZAGADGETS looks up the same card:
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/terminal`
- Dev: `http://localhost:8000/platforms/loyalty/store/WIZAGADGETS/loyalty/terminal`
- Prod: `https://{store_domain}/store/WIZAGADGETS/loyalty/terminal`
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/cards/lookup`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/cards/lookup`
4. Card is found (same merchant) with accumulated stamps
5. Staff at WIZAGADGETS redeems the reward:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp/redeem`
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp/redeem`
- Dev: `POST http://localhost:8000/platforms/loyalty/api/v1/store/loyalty/stamp/redeem`
- Prod: `POST https://{store_domain}/api/v1/store/loyalty/stamp/redeem`
6. Verify transaction history shows both stores:
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/cards/{card_id}`
- Dev: `http://localhost:8000/platforms/loyalty/store/WIZAGADGETS/loyalty/cards/{card_id}`
- Prod: `https://{store_domain}/store/WIZAGADGETS/loyalty/cards/{card_id}`
---