diff --git a/app/modules/loyalty/docs/production-launch-plan.md b/app/modules/loyalty/docs/production-launch-plan.md index 6d3be423..99d86a62 100644 --- a/app/modules/loyalty/docs/production-launch-plan.md +++ b/app/modules/loyalty/docs/production-launch-plan.md @@ -325,10 +325,13 @@ Tracked separately, not blocking launch. Everything below must be completed before going live. Items are ordered by dependency. -### Step 1: Seed email templates on prod DB -- [ ] SSH into prod server -- [ ] Run: `python scripts/seed/seed_email_templates_loyalty.py` -- [ ] Verify: 20 rows created (5 templates × 4 locales) +> **Latest status snapshot:** see [Go-Live Readiness (2026-05-10)](../../proposals/loyalty-go-live-readiness.md) +> — only Steps 6 and 8 still pending; Step 9 is post-launch. + +### Step 1: Seed email templates on prod DB ✅ +- [x] SSH into prod server +- [x] Run: `python scripts/seed/seed_email_templates_loyalty.py` +- [x] Verify: 20 rows created (5 templates × 4 locales) - [ ] Review EN email copy — adjust subject lines/body if needed via admin UI at `/admin/email-templates` ### Step 2: Google Wallet — already deployed, verify config @@ -339,12 +342,13 @@ The Google Wallet integration is already deployed on the Hetzner server (see Ste - Service account linked to Issuer with Admin role ✅ Verify after deploy: -- [ ] Restart app — confirm no startup error (config validator checks file exists) -- [ ] `GET /api/v1/admin/loyalty/wallet-status` returns `google_configured: true` +- [x] Restart app — confirm no startup error (config validator checks file exists) +- [x] `GET /api/v1/admin/loyalty/wallet-status` returns `google_configured: true` +- [x] Wallet diagnostics screen at `/admin/loyalty/wallet-debug` → Config Validation → all green (validated 2026-05-10) -### Step 3: Apply database migrations -- [ ] Run: `alembic upgrade heads` -- [ ] Verify migrations applied: `loyalty_003` through `loyalty_006`, `customers_003` +### Step 3: Apply database migrations ✅ +- [x] Run: `alembic upgrade heads` +- [x] Verify migrations applied: all four module heads current incl. `loyalty_011` ### Step 4: FR/DE/LB translations for new analytics i18n keys - [ ] Add translations for 7 keys in `app/modules/loyalty/locales/{fr,de,lb}.json`: @@ -375,12 +379,12 @@ Follow the **Pre-Launch E2E Test Checklist** at the bottom of `user-journeys.md` - [ ] **Test 7:** Admin oversight (programs, merchants, analytics) - [ ] **Test 8:** Cross-location disabled behavior (separate cards per store) -### Step 7: Google Wallet real-device test (demo mode) +### Step 7: Google Wallet real-device test (demo mode) ✅ Google Wallet currently works in **demo/test mode** — only your Google account and explicitly added test accounts can see passes. This is sufficient for launch testing. -- [ ] Enroll a test customer on prod -- [ ] Tap "Add to Google Wallet" on success page -- [ ] Open Google Wallet on Android device — verify pass renders with merchant branding -- [ ] Trigger a stamp/points transaction — verify pass auto-updates within 60s +- [x] Enroll a test customer on prod +- [x] Tap "Add to Google Wallet" on success page +- [x] Open Google Wallet on Android device — verify pass renders with merchant branding +- [x] Trigger a stamp/points transaction — verify pass auto-updates within 60s ### Step 8: Go live - [ ] Remove any test data from prod DB (test customers, test cards) diff --git a/docs/proposals/loyalty-go-live-readiness.md b/docs/proposals/loyalty-go-live-readiness.md new file mode 100644 index 00000000..4a28790c --- /dev/null +++ b/docs/proposals/loyalty-go-live-readiness.md @@ -0,0 +1,134 @@ +# Loyalty Go-Live Readiness — 2026-05-10 + +Snapshot of where the loyalty platform stands the night of 2026-05-10. The +canonical sequenced plan is still +[`app/modules/loyalty/docs/production-launch-plan.md`](../modules/loyalty/production-launch-plan.md); +this doc records the *current state* (✅ / ⏳ / 🟡) and what surfaced +during the prod readiness pass. + +## TL;DR + +The technical pre-launch checklist is green. The remaining gate is a +human one — walking the 8 user-journey E2E tests on prod with a real +test customer and confirming nothing surprises us. After that, flip the +loyalty platform live for FASHIONHUB's stores and start the Google +Wallet production-access review in parallel (1–3 day Google review, +non-blocking). + +## Status board + +| # | Pre-launch step | State | Notes | +|---|---|---|---| +| 1 | Seed loyalty email templates on prod | ✅ | 20 rows (5 templates × 4 locales) all `is_active=true` | +| 2 | Google Wallet config on Hetzner | ✅ | Wallet config validator green: credentials valid, issuer `3388000000023089598`, origin `https://rewardflow.lu`, default logo reachable | +| 3 | Database migrations | ✅ | All four module heads current incl. `loyalty_011` (acting-device audit) on prod | +| 4 | FR/DE/LB translations for analytics i18n keys | 🟡 | 8 keys still EN-only. Cosmetic, doesn't block soft launch | +| 5 | `messaging.manage_templates` permission for store owners | 🟡 | Only matters if merchants self-edit templates. Admin can edit centrally. Defer | +| 6 | 8 user-journey E2E tests | ⏳ | **The remaining gate** — user does this with a real test customer | +| 7 | Google Wallet real-device pass test | ✅ | Already confirmed earlier — cards register, points/redeem visible on personal Google Wallet | +| 8 | Go live | ⏳ | Gated by #6. Cleanup test data + enable platform feature flags for FASHIONHUB | +| 9 | Google Wallet production access | ⏳ | Post-launch, 1–3 day Google review. App-side change is zero; same issuer + service account, passes become public-visible once approved | + +## What got sorted tonight + +### SMTP wired to a self-hosted mail server + +Started here: + +- prod `.env` had `EMAIL_PROVIDER=sendgrid` + a SendGrid API key +- SendGrid free trial (60 days) had expired +- `SMTP_*` env vars were placeholders pointing at `smtp.example.com` + +Discovered that `/admin/settings` lets you store SMTP config in the DB +(table `admin_settings`, category `email`) and those values **win over +.env**. User had already configured: + +- `email_provider=smtp` +- `smtp_host=mail1.myservices.hosting` +- `smtp_port=465` ← problematic +- `smtp_user=support@wizard.lu` / encrypted password +- `smtp_use_ssl=true, smtp_use_tls=false` + +Diagnosis from the prod container: + +| Check | Result | +|---|---| +| DNS resolves `mail1.myservices.hosting` | ✅ `185.26.107.245` | +| TCP `mail1.myservices.hosting:465` | ❌ timed out | +| TCP `mail1.myservices.hosting:587` | ✅ open | + +Either Hetzner blocks 465 outbound for this VPS or the provider +firewalls Hetzner's IP range on 465. Either way, port 587 (submission ++ STARTTLS) is the modern path and works. + +**Fix:** changed `/admin/settings` to port 587, SSL off, TLS on. Test +email landed in inbox immediately, sender header `Support Wizard +` — proving the DB override was being used. + +### Cosmetic bug found and fixed + +The test email's body claimed the configuration that **would have been +used if .env were authoritative** — i.e. it said `Provider: sendgrid` +and `From: noreply@wizard.lu` even though the actual send went via +SMTP from `support@wizard.lu`. Two places in the code: + +1. `app/modules/core/routes/api/admin_settings.py::send_test_email` — + body template hardcoded `app_settings.email_provider` and + `app_settings.email_from_address` +2. `app/modules/messaging/services/email_service.py` — the "template + not found" `EmailLog` branch recorded `settings.email_provider` / + `settings.email_from_address` instead of the effective config + +Both now read from `get_effective_email_config(db)` / +`self._platform_config`, so the test email page and audit logs reflect +what was actually used. + +Commit: `f2d1bdcd` on master, deployed via Gitea Actions. + +## What the user does next + +In priority order: + +1. **Tonight or tomorrow — review email copy.** Open + `/admin/email-templates` and skim the 5 loyalty templates (EN + locale). `loyalty_enrollment`, `loyalty_welcome_bonus` and + `loyalty_reward_ready` are the customer-visible ones — adjust + subject lines + body copy if anything reads off-brand. +2. **Walk the 8 user-journey E2E tests** — checklist at the bottom of + `app/modules/loyalty/docs/user-journeys.md`. Use a personal email + as the test customer. +3. **Flip live for FASHIONHUB** — clean any test data, double-check + Celery (`docker compose ps | grep celery`), enable loyalty feature + on FASHIONHUB's stores via the admin UI. +4. **In parallel, file Google Wallet production access** — + [pay.google.com/business/console](https://pay.google.com/business/console) + → Wallet API → Manage → Request production access. Use sample pass + screenshots from FASHIONHUB. Google reviews the Issuer, not + individual merchants — once approved all merchants on the platform + are covered. + +## Open follow-ups (non-blocking) + +These can wait but are worth tracking: + +- **FR/DE/LB translations** for the 8 analytics i18n keys + (`store.analytics.revenue_title`, `store.analytics.cohort_title`, + etc.). EN shows through; cosmetic only. +- **`messaging.manage_templates` permission discovery for + merchant_owner role** — needed if/when merchants self-edit templates. + Admin can edit centrally for v1. +- **Failed-PIN-attempt reporting from Android tablet → server lockout + counter** — tablet bcrypts locally and silently fails; a stolen + tablet's brute-forcer doesn't trip server-side lockout. Add a tiny + `POST /pins/{id}/record-failed-attempt` endpoint plus a call from the + PinViewModel's failure branch. +- **Splash screen + per-action success animation** for the Android + tablet — Phase F polish that was intentionally deferred. + +## Reference + +- Canonical plan: [`app/modules/loyalty/docs/production-launch-plan.md`](../modules/loyalty/production-launch-plan.md) +- Hetzner runbook: [`deployment/hetzner-server-setup.md`](../deployment/hetzner-server-setup.md) +- Wallet diagnostics page: `/admin/loyalty/wallet-debug` (super admin only) +- Recent commits relevant to this session: + - `f2d1bdcd` fix(messaging): test email + EmailLog show effective config