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:
@@ -833,6 +833,62 @@ class ProgramService:
|
||||
"estimated_liability_cents": estimated_liability,
|
||||
}
|
||||
|
||||
def get_wallet_integration_status(self, db: Session) -> dict:
|
||||
"""Get wallet integration status for admin dashboard."""
|
||||
from app.modules.loyalty.models import LoyaltyCard
|
||||
from app.modules.loyalty.services.apple_wallet_service import (
|
||||
apple_wallet_service,
|
||||
)
|
||||
from app.modules.loyalty.services.google_wallet_service import (
|
||||
google_wallet_service,
|
||||
)
|
||||
|
||||
# Google Wallet
|
||||
google_config = google_wallet_service.validate_config()
|
||||
google_classes = []
|
||||
if google_config["credentials_valid"]:
|
||||
programs_with_class = (
|
||||
db.query(LoyaltyProgram)
|
||||
.filter(LoyaltyProgram.google_class_id.isnot(None))
|
||||
.all()
|
||||
)
|
||||
for prog in programs_with_class:
|
||||
status = google_wallet_service.get_class_status(
|
||||
prog.google_class_id,
|
||||
)
|
||||
google_classes.append({
|
||||
"program_id": prog.id,
|
||||
"program_name": prog.display_name,
|
||||
"class_id": prog.google_class_id,
|
||||
"review_status": status["review_status"] if status else "UNKNOWN",
|
||||
})
|
||||
|
||||
google_objects = (
|
||||
db.query(LoyaltyCard)
|
||||
.filter(LoyaltyCard.google_object_id.isnot(None))
|
||||
.count()
|
||||
)
|
||||
|
||||
# Apple Wallet
|
||||
apple_config = apple_wallet_service.validate_config()
|
||||
apple_passes = (
|
||||
db.query(LoyaltyCard)
|
||||
.filter(LoyaltyCard.apple_serial_number.isnot(None))
|
||||
.count()
|
||||
)
|
||||
|
||||
return {
|
||||
"google_wallet": {
|
||||
**google_config,
|
||||
"classes": google_classes,
|
||||
"total_objects": google_objects,
|
||||
},
|
||||
"apple_wallet": {
|
||||
**apple_config,
|
||||
"total_passes": apple_passes,
|
||||
},
|
||||
}
|
||||
|
||||
def get_merchant_stats(self, db: Session, merchant_id: int) -> dict:
|
||||
"""
|
||||
Get statistics for a merchant's loyalty program across all locations.
|
||||
|
||||
Reference in New Issue
Block a user