fix(loyalty): cross-store enrollment, card scoping, i18n flicker
Some checks failed
Some checks failed
Fix duplicate card creation when the same email enrolls at different stores under the same merchant, and implement cross-location-aware enrollment behavior. - Cross-location enabled (default): one card per customer per merchant. Re-enrolling at another store returns the existing card with a "works at all our locations" message + store list. - Cross-location disabled: one card per customer per store. Enrolling at a different store creates a separate card for that store. Changes: - Migration loyalty_004: replace (merchant_id, customer_id) unique index with (enrolled_at_store_id, customer_id). Per-merchant uniqueness enforced at application layer when cross-location enabled. - card_service.resolve_customer_id: cross-store email lookup via merchant_id param to find existing cardholders at other stores. - card_service.enroll_customer: branch duplicate check on allow_cross_location_redemption setting. - card_service.search_card_for_store: cross-store email search when cross-location enabled so staff at store2 can find cards from store1. - card_service.get_card_by_customer_and_store: new service method. - storefront enrollment: catch LoyaltyCardAlreadyExistsException, return existing card with already_enrolled flag, locations, and cross-location context. Server-rendered i18n via Jinja2 tojson. - enroll-success.html: conditional cross-store/single-store messaging, server-rendered translations and context, i18n_modules block added. - dashboard.html, history.html: replace $t() with server-side _() to fix i18n flicker across all storefront templates. - Fix device-mobile icon → phone icon. - 4 new i18n keys in 4 locales (en, fr, de, lb). - Docs: updated data-model, business-logic, production-launch-plan, user-journeys with cross-location behavior and E2E test checklist. - 12 new unit tests + 3 new integration tests (334 total pass). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -214,14 +214,25 @@ Uses PKCS#7 signed `.pkpass` files and APNs push notifications.
|
||||
|
||||
## Cross-Store Redemption
|
||||
|
||||
When `allow_cross_location_redemption` is enabled in merchant settings:
|
||||
The `allow_cross_location_redemption` merchant setting controls both card scoping and enrollment behavior:
|
||||
|
||||
- Cards are scoped to the **merchant** (not individual stores)
|
||||
### When enabled (default)
|
||||
|
||||
- **One card per customer per merchant** — enforced at the application layer
|
||||
- Customer can earn stamps at Store A and redeem at Store B
|
||||
- Each transaction records which `store_id` it occurred at
|
||||
- The `enrolled_at_store_id` field tracks where the customer first enrolled
|
||||
- If a customer tries to enroll at a second store, the system returns their existing card with a message showing all available locations
|
||||
|
||||
When disabled, stamp/point operations are restricted to the enrollment store.
|
||||
### When disabled
|
||||
|
||||
- **One card per customer per store** — each store under the merchant issues its own card
|
||||
- Stamp/point operations are restricted to the card's enrollment store
|
||||
- A customer can hold separate cards at different stores under the same merchant
|
||||
- Re-enrolling at the **same** store returns the existing card
|
||||
- Enrolling at a **different** store creates a new card scoped to that store
|
||||
|
||||
**Database constraint:** Unique index on `(enrolled_at_store_id, customer_id)` prevents duplicate cards at the same store regardless of the cross-location setting.
|
||||
|
||||
## Enrollment Flow
|
||||
|
||||
@@ -229,21 +240,25 @@ When disabled, stamp/point operations are restricted to the enrollment store.
|
||||
|
||||
Staff enrolls customer via terminal:
|
||||
1. Enter customer email (and optional name)
|
||||
2. System resolves or creates customer record
|
||||
3. Creates loyalty card with unique card number and QR code
|
||||
4. Creates `CARD_CREATED` transaction
|
||||
5. Awards welcome bonus points (if configured) via `WELCOME_BONUS` transaction
|
||||
6. Creates Google Wallet object and Apple Wallet serial
|
||||
7. Returns card details with "Add to Wallet" URLs
|
||||
2. System resolves customer — checks the current store first, then searches across all stores under the merchant for an existing cardholder with the same email
|
||||
3. If the customer already has a card (per-merchant or per-store, depending on the cross-location setting), raises `LoyaltyCardAlreadyExistsException`
|
||||
4. Otherwise creates loyalty card with unique card number and QR code
|
||||
5. Creates `CARD_CREATED` transaction
|
||||
6. Awards welcome bonus points (if configured) via `WELCOME_BONUS` transaction
|
||||
7. Creates Google Wallet object and Apple Wallet serial
|
||||
8. Returns card details with "Add to Wallet" URLs
|
||||
|
||||
### Self-Enrollment (Public)
|
||||
|
||||
Customer enrolls via public page (if `allow_self_enrollment` enabled):
|
||||
1. Customer visits `/loyalty/join` page
|
||||
2. Enters email and name
|
||||
3. System creates customer + card
|
||||
4. Redirected to success page with card number
|
||||
5. Can add to Google/Apple Wallet from success page
|
||||
2. Enters email, name, and optional birthday
|
||||
3. System resolves customer (cross-store lookup for existing cardholders under the same merchant)
|
||||
4. If already enrolled: returns existing card with success page showing location info
|
||||
- Cross-location enabled: "Your card works at all our locations" + store list
|
||||
- Cross-location disabled: "Your card is registered at {original_store}"
|
||||
5. If new: creates customer + card, redirected to success page with card number
|
||||
6. Can add to Google/Apple Wallet from success page
|
||||
|
||||
## Scheduled Tasks
|
||||
|
||||
|
||||
Reference in New Issue
Block a user