Files
orion/docs/proposals/loyalty-go-live-readiness.md
Samir Boulahtit a3fb7029bd
All checks were successful
CI / ruff (push) Successful in 14s
CI / pytest (push) Successful in 2h25m39s
CI / validate (push) Successful in 31s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Successful in 50s
CI / deploy (push) Successful in 1m11s
docs(loyalty): add Android terminal E2E tests to user-journeys checklist
The web user-journey checklist (Tests 1–8) only covers human-using-loyalty
flows from a browser. The cashier-facing Android tablet built in Phases
A–F goes through a different surface and has its own failure modes that
won't surface in any web test. Adding 6 dedicated Android tests so a
tablet-in-hand verification has the same level of structure as the web
side.

- Test 9: Tablet pairing — QR scan + manual entry fallback, with the
  audit (paired-device row appears, last_seen_at populated)
- Test 10: PIN screen — wrong/right PIN, offline-capable bcrypt verify,
  locked-PIN rejection
- Test 11: Daily flows — search, scan, enroll, stamp, earn, redeem,
  with the acting_terminal_device_id audit column check at the end
- Test 12: Offline queue + sync — airplane mode → queued → re-online →
  drain; redeem is hard-disabled offline per spec
- Test 13: Auto-lock + manual lock — 2 min idle, immediate lock button,
  the known caveat that AlertDialog pointer events don't bubble
- Test 14: Device revocation — revoke on web → 401 on tablet next call

Updated the go-live readiness snapshot to reference these as Step 6b
(gated on user obtaining a tablet, not on schedule).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:05:00 +02:00

141 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (13 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 web user-journey E2E tests | ⏳ | **The remaining gate** — user does this with a real test customer |
| 6b | 6 Android terminal E2E tests | ⏳ | Pairing, PIN, daily flows, offline queue, auto-lock, device revoke — gated on user obtaining a tablet |
| 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, 13 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
<support@wizard.lu>` — 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 web user-journey E2E tests** — checklist at the bottom of
`app/modules/loyalty/docs/user-journeys.md`. Use a personal email
as the test customer.
2b. **Once a tablet is on hand: walk the 6 Android terminal tests**
same doc, "Android Terminal Tests" section (Tests 914). Covers
pairing (QR + manual), offline PIN bcrypt verify, daily flows
(stamp/earn/redeem/enroll), offline queue drain, idle auto-lock,
and device revocation cutoff.
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