docs: add Google Wallet setup guide and loyalty env vars
Some checks failed
CI / ruff (push) Successful in 11s
CI / pytest (push) Failing after 45m26s
CI / validate (push) Successful in 22s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped

Step 25 in Hetzner docs with full Google Cloud/Wallet Console setup,
service account configuration, local testing, and architecture diagrams.
Loyalty module env vars added to environment.md and .env.example.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 00:31:43 +01:00
parent 272b62fbd3
commit 05d31a7fc5
3 changed files with 297 additions and 0 deletions

View File

@@ -203,6 +203,34 @@ R2_PUBLIC_URL=
# Cloudflare R2 backup bucket (used by scripts/backup.sh --upload) # Cloudflare R2 backup bucket (used by scripts/backup.sh --upload)
R2_BACKUP_BUCKET=orion-backups R2_BACKUP_BUCKET=orion-backups
# =============================================================================
# LOYALTY MODULE
# =============================================================================
# Anti-fraud defaults (all optional, shown values are defaults)
# LOYALTY_DEFAULT_COOLDOWN_MINUTES=15
# LOYALTY_MAX_DAILY_STAMPS=5
# LOYALTY_PIN_MAX_FAILED_ATTEMPTS=5
# LOYALTY_PIN_LOCKOUT_MINUTES=30
# Points configuration
# LOYALTY_DEFAULT_POINTS_PER_EURO=10
# Google Wallet integration
# See docs/deployment/hetzner-server-setup.md Step 25 for setup guide
# Get Issuer ID from https://pay.google.com/business/console
# LOYALTY_GOOGLE_ISSUER_ID=3388000000012345678
# LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/path/to/service-account.json
# Apple Wallet integration (requires Apple Developer account)
# LOYALTY_APPLE_PASS_TYPE_ID=pass.com.example.loyalty
# LOYALTY_APPLE_TEAM_ID=ABCD1234
# LOYALTY_APPLE_WWDR_CERT_PATH=/path/to/wwdr.pem
# LOYALTY_APPLE_SIGNER_CERT_PATH=/path/to/signer.pem
# LOYALTY_APPLE_SIGNER_KEY_PATH=/path/to/signer.key
# QR code size in pixels (default: 300)
# LOYALTY_QR_CODE_SIZE=300
# ============================================================================= # =============================================================================
# CLOUDFLARE CDN / PROXY # CLOUDFLARE CDN / PROXY
# ============================================================================= # =============================================================================

View File

@@ -300,6 +300,59 @@ the server filesystem.
--- ---
## Loyalty Module
Configuration for the loyalty module (stamp/points programs, wallet integration).
All variables use the `LOYALTY_` prefix and are managed by `app/modules/loyalty/config.py`.
### Anti-Fraud Defaults
| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_DEFAULT_COOLDOWN_MINUTES` | Minimum minutes between stamps for the same card | `15` | No |
| `LOYALTY_MAX_DAILY_STAMPS` | Maximum stamps per card per day | `5` | No |
| `LOYALTY_PIN_MAX_FAILED_ATTEMPTS` | Failed PIN attempts before lockout | `5` | No |
| `LOYALTY_PIN_LOCKOUT_MINUTES` | Duration of PIN lockout in minutes | `30` | No |
### Points
| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_DEFAULT_POINTS_PER_EURO` | Points earned per euro spent | `10` | No |
### Google Wallet
!!! info "Required for Google Wallet passes"
Both variables must be set for loyalty cards to appear in Google Wallet.
See [Hetzner Step 25](hetzner-server-setup.md#step-25-google-wallet-integration) for setup guide.
| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_GOOGLE_ISSUER_ID` | Google Wallet Issuer ID (numeric string from [Pay & Wallet Console](https://pay.google.com/business/console)) | `None` | Yes (for Google Wallet) |
| `LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON` | Path to the Google service account JSON key file | `None` | Yes (for Google Wallet) |
### Apple Wallet
!!! info "Required for Apple Wallet passes"
All five variables must be set for `.pkpass` generation.
Requires an Apple Developer account ($99/year).
| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_APPLE_PASS_TYPE_ID` | Pass type identifier (e.g., `pass.com.example.loyalty`) | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_TEAM_ID` | Apple Developer Team ID | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_WWDR_CERT_PATH` | Path to Apple WWDR intermediate certificate | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_SIGNER_CERT_PATH` | Path to pass signing certificate (`.pem`) | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_SIGNER_KEY_PATH` | Path to pass signing private key (`.pem`) | `None` | Yes (for Apple Wallet) |
### QR Code
| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_QR_CODE_SIZE` | QR code image size in pixels | `300` | No |
---
## Cloudflare CDN / Proxy ## Cloudflare CDN / Proxy
| Variable | Description | Default | Required | | Variable | Description | Default | Required |
@@ -335,6 +388,8 @@ marked **critical** will trigger a startup warning if left at their default valu
- [x] **Email (Mailgun):** `MAILGUN_API_KEY`, `MAILGUN_DOMAIN` — transactional only, no marketing features - [x] **Email (Mailgun):** `MAILGUN_API_KEY`, `MAILGUN_DOMAIN` — transactional only, no marketing features
- [x] **Email (SES):** `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` — cheapest at scale - [x] **Email (SES):** `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` — cheapest at scale
- [x] **R2 Storage:** `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY` - [x] **R2 Storage:** `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`
- [x] **Google Wallet:** `LOYALTY_GOOGLE_ISSUER_ID`, `LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON`
- [ ] **Apple Wallet:** `LOYALTY_APPLE_PASS_TYPE_ID`, `LOYALTY_APPLE_TEAM_ID`, `LOYALTY_APPLE_WWDR_CERT_PATH`, `LOYALTY_APPLE_SIGNER_CERT_PATH`, `LOYALTY_APPLE_SIGNER_KEY_PATH`
### Example `.env` file (production) ### Example `.env` file (production)
@@ -374,4 +429,8 @@ ENABLE_METRICS=True
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SENTRY_ENVIRONMENT=production SENTRY_ENVIRONMENT=production
CLOUDFLARE_ENABLED=True CLOUDFLARE_ENABLED=True
# Google Wallet (Loyalty)
LOYALTY_GOOGLE_ISSUER_ID=3388000000023089598
LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/app/google-wallet-sa.json
``` ```

View File

@@ -102,6 +102,26 @@ Complete step-by-step guide for deploying Orion on a Hetzner Cloud VPS.
**Steps 124 fully complete.** Enterprise infrastructure hardening done. **Steps 124 fully complete.** Enterprise infrastructure hardening done.
!!! success "Progress — 2026-02-24"
**Completed:**
- **Step 25: Google Wallet Integration** — Google Cloud project "Orion" created, Wallet API enabled, service account configured
- Google Pay Merchant ID: `BCR2DN5TW2CNXDAG`
- Google Wallet Issuer ID: `3388000000023089598`
- Service account: `wallet-service@orion-488322.iam.gserviceaccount.com` (admin role in Pay & Wallet Console)
- Service account JSON key generated
- Dependencies added to `requirements.txt`: `google-auth>=2.0.0`, `PyJWT>=2.0.0` (commit `d36783a`)
- Loyalty env vars added to `.env.example` and `docs/deployment/environment.md`
**Next steps:**
- [ ] Upload service account JSON to Hetzner server
- [ ] Set `LOYALTY_GOOGLE_ISSUER_ID` and `LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON` in production `.env`
- [ ] Restart app and test end-to-end: enroll → add pass → stamp → verify pass updates
- [ ] Wire "Add to Google Wallet" button into storefront enrollment success page
- [ ] Submit for Google production approval when ready
- [ ] Apple Wallet setup (APNs push, certificates, pass images)
!!! success "Progress — 2026-02-16" !!! success "Progress — 2026-02-16"
**Completed:** **Completed:**
@@ -1753,6 +1773,196 @@ This document has been updated with Steps 1924. Additional documentation chan
--- ---
## Step 25: Google Wallet Integration
Enable loyalty card passes in Google Wallet so customers can add their loyalty card to their Android phone.
### Prerequisites
- Google account (personal Gmail is fine)
- Loyalty module deployed and working
### 25.1 Google Pay & Wallet Console
Register as a Google Wallet Issuer:
1. Go to [pay.google.com/business/console](https://pay.google.com/business/console)
2. Enter your business name (e.g., "Letzshop" or your company name) — this is for Google's review, customers don't see it on passes
3. Note your **Issuer ID** from the Google Wallet API section
!!! info "Issuer ID"
The Issuer ID is a long numeric string (e.g., `3388000000023089598`). You'll find it under Google Wallet API → Manage in the Pay & Wallet Console.
### 25.2 Google Cloud Project
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
2. Create a new project (e.g., "Orion")
3. Enable the **Google Wallet API**:
- Navigate to "APIs & Services" → "Library"
- Search for "Google Wallet API" and enable it
### 25.3 Service Account
Create a service account for API access:
1. Go to "APIs & Services" → "Credentials" → "Create Credentials"
2. Select **Google Wallet API** as the API
3. Select **Application data** (not user data — your backend calls the API directly)
4. Name the service account (e.g., `wallet-service`)
5. Click "Done"
Download the JSON key:
1. Go to "IAM & Admin" → "Service Accounts"
2. Click on the service account you created
3. Go to **Keys** tab → **Add Key** → **Create new key** → **JSON**
4. Save the downloaded `.json` file securely
### 25.4 Link Service Account to Issuer
1. Go back to [pay.google.com/business/console](https://pay.google.com/business/console)
2. In the **left sidebar**, click **Users** (not inside the Wallet API section)
3. Invite the service account email (e.g., `wallet-service@orion-488322.iam.gserviceaccount.com`)
4. Assign **Admin** role
5. Verify it appears in the users list
!!! warning "Common mistake"
The "Users" link is in the **left sidebar** of the Pay & Wallet Console, not inside the "Google Wallet API" → "Manage" section. The Manage page has "Setup test accounts" which is a different feature.
### 25.5 Deploy to Server
Upload the service account JSON key to the Hetzner server:
```bash
# From your local machine
scp /path/to/orion-488322-xxxxx.json samir@91.99.65.229:~/apps/orion/google-wallet-sa.json
```
Add the environment variables to the production `.env`:
```bash
ssh samir@91.99.65.229
cd ~/apps/orion
nano .env
```
Add:
```bash
# Google Wallet (Loyalty Module)
LOYALTY_GOOGLE_ISSUER_ID=3388000000023089598
LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/app/google-wallet-sa.json
```
!!! note "Docker path"
The path must be relative to the Docker container's filesystem. If the file is in `~/apps/orion/`, it maps to `/app/` inside the container (check your `docker-compose.yml` volumes).
Mount the JSON file in `docker-compose.yml` if not already covered by the app volume:
```yaml
services:
api:
volumes:
- ./google-wallet-sa.json:/app/google-wallet-sa.json:ro
```
Restart the application:
```bash
docker compose --profile full up -d --build
```
### 25.6 Verify Configuration
Check the API health and wallet service status:
```bash
# Check the app logs for wallet service initialization
docker compose --profile full logs api | grep -i "wallet\|loyalty"
# Test via API — create a program and enroll a customer, then check the response
# for google_object_id and google Wallet URL fields
curl -s https://api.wizard.lu/health | python3 -m json.tool
```
### 25.7 Testing Google Wallet Passes
Google provides a **demo mode** — passes work in test without full production approval:
1. Console admins and developers (your Google account) can always test passes
2. For additional testers, add their Gmail addresses in Pay & Wallet Console → Google Wallet API → Manage → **Setup test accounts**
3. Use `walletobjects.sandbox` scope for initial testing (the code uses `wallet_object.issuer` which covers both)
**End-to-end test flow:**
1. Create a loyalty program via the store panel
2. Enroll a customer (via store or storefront self-enrollment)
3. The API returns a Google Wallet save URL
4. Open the URL on an Android device — the pass is added to Google Wallet
5. Add a stamp or points — the pass in Google Wallet auto-updates
### 25.8 Local Development Setup
You can test the full Google Wallet integration from your local machine:
```bash
# In your local .env (or export directly)
export LOYALTY_GOOGLE_ISSUER_ID=3388000000023089598
export LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/path/to/orion-488322-xxxxx.json
```
The `GoogleWalletService` calls Google's REST API directly over HTTPS — no special network configuration needed. The same service account JSON works on both local and server environments.
**Local testing checklist:**
- [x] Service account JSON downloaded and path set in env
- [x] `LOYALTY_GOOGLE_ISSUER_ID` set in env
- [ ] Start the app locally: `uvicorn app.main:app --reload`
- [ ] Create a loyalty program → verify `google_class_id` is set
- [ ] Enroll a customer → verify `google_object_id` is set
- [ ] Call `get_save_url()` → open the URL on Android to add pass
- [ ] Add stamps → verify pass updates in Google Wallet
### 25.9 How It Works (Architecture)
```
┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐
│ Merchant │────▶│ Orion API │────▶│ Google Wallet API │
│ creates │ │ │ │ │
│ program │ │ create_class │ │ POST /loyaltyClass │
└─────────────┘ └──────────────┘ └─────────────────────┘
┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐
│ Customer │────▶│ Orion API │────▶│ Google Wallet API │
│ enrolls │ │ │ │ │
│ │ │create_object │ │ POST /loyaltyObject │
│ │◀────│ save_url │ │ │
│ │ └──────────────┘ └─────────────────────┘
│ taps "Add │
│ to Wallet" │────▶ Google Wallet app adds pass automatically
└─────────────┘
┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐
│ Staff adds │────▶│ Orion API │────▶│ Google Wallet API │
│ stamp │ │ │ │ │
│ │ │update_object │ │ PATCH /loyaltyObject│
└─────────────┘ └──────────────┘ └─────────────────────┘
Pass auto-updates on
customer's phone
```
No push notifications needed — Google syncs object changes automatically.
### 25.10 Next Steps
After Google Wallet is verified working:
1. **Wire "Add to Google Wallet" button** into the storefront enrollment success page and card dashboard
2. **Submit for Google production approval** — required before non-test users can add passes
3. **Apple Wallet** — separate setup requiring Apple Developer account, APNs certificates, and pass signing certificates (see [Loyalty Module docs](../modules/loyalty.md#apple-wallet))
---
## Domain & Port Reference ## Domain & Port Reference
| Service | Internal Port | External Port | Domain (via Caddy) | | Service | Internal Port | External Port | Domain (via Caddy) |