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

@@ -33,6 +33,10 @@ function adminLoyaltyAnalytics() {
showMerchantDropdown: false,
searchingMerchants: false,
// Wallet integration status
walletStatus: null,
walletStatusLoading: false,
loading: false,
error: null,
@@ -59,6 +63,7 @@ function adminLoyaltyAnalytics() {
window._loyaltyAnalyticsInitialized = true;
await this.loadStats();
await this.loadWalletStatus();
loyaltyAnalyticsLog.info('=== LOYALTY ANALYTICS PAGE INITIALIZATION COMPLETE ===');
},
@@ -166,6 +171,21 @@ function adminLoyaltyAnalytics() {
await this.loadStats();
},
async loadWalletStatus() {
this.walletStatusLoading = true;
try {
const response = await apiClient.get('/admin/loyalty/wallet-status');
if (response) {
this.walletStatus = response;
loyaltyAnalyticsLog.info('Wallet status loaded');
}
} catch (error) {
loyaltyAnalyticsLog.error('Failed to load wallet status:', error);
} finally {
this.walletStatusLoading = false;
}
},
formatNumber(num) {
if (num === null || num === undefined) return '0';
return new Intl.NumberFormat('en-US').format(num);