docs: add Google Wallet setup guide and loyalty env vars
Some checks failed
Some checks failed
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:
@@ -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
|
||||
|
||||
| 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 (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] **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)
|
||||
|
||||
@@ -374,4 +429,8 @@ ENABLE_METRICS=True
|
||||
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
|
||||
SENTRY_ENVIRONMENT=production
|
||||
CLOUDFLARE_ENABLED=True
|
||||
|
||||
# Google Wallet (Loyalty)
|
||||
LOYALTY_GOOGLE_ISSUER_ID=3388000000023089598
|
||||
LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/app/google-wallet-sa.json
|
||||
```
|
||||
|
||||
@@ -102,6 +102,26 @@ Complete step-by-step guide for deploying Orion on a Hetzner Cloud VPS.
|
||||
|
||||
**Steps 1–24 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"
|
||||
**Completed:**
|
||||
|
||||
@@ -1753,6 +1773,196 @@ This document has been updated with Steps 19–24. 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
|
||||
|
||||
| Service | Internal Port | External Port | Domain (via Caddy) |
|
||||
|
||||
Reference in New Issue
Block a user