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>
- 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>
Move 39 documentation files from top-level docs/ into each module's
docs/ folder, accessible via symlinks from docs/modules/. Create
data-model.md files for 10 modules with full schema documentation.
Replace originals with redirect stubs. Remove empty guide stubs.
Modules migrated: tenancy, billing, loyalty, marketplace, orders,
messaging, cms, catalog, inventory, hosting, prospecting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>