Connect the fully-implemented Google Wallet service to the loyalty module: - Create wallet class/object on customer enrollment - Sync wallet passes on stamp and points operations - Expose wallet URLs in storefront API responses - Add conditional "Add to Google Wallet" buttons on dashboard and enroll-success pages - Use platform-wide env var config (not per-merchant DB column) - Add Google service account patterns to .gitignore - Add LOYALTY_GOOGLE_* fields to app Settings - Update deployment docs and add local testing guide Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
Loyalty Module
The Loyalty Module provides stamp-based and points-based loyalty programs for Orion stores with Google Wallet and Apple Wallet integration.
Overview
| Aspect | Description |
|---|---|
| Module Code | loyalty |
| Dependencies | customers |
| Status | Phase 2 Complete |
Key Features
- Stamp-based loyalty: Collect N stamps, get a reward (e.g., "Buy 10 coffees, get 1 free")
- Points-based loyalty: Earn points per euro spent, redeem for rewards
- Hybrid programs: Support both stamps and points simultaneously
- Anti-fraud system: Staff PINs, cooldown periods, daily limits, lockout protection
- Wallet integration: Google Wallet and Apple Wallet pass generation
- Full audit trail: Transaction logging with IP, user agent, and staff attribution
Entity Model
┌─────────────────┐ ┌─────────────────┐
│ Merchant │───────│ LoyaltyProgram │
└─────────────────┘ 1:1 └─────────────────┘
│ │
▼ ┌────────┼──────────┐
┌──────────────────┐ │ │ │
│ MerchantLoyalty │ ▼ ▼ ▼
│ Settings │┌──────────┐┌──────────┐┌────────┐
└──────────────────┘│ StaffPin ││LoyaltyCard││(config)│
└──────────┘└──────────┘└────────┘
│ │
│ ▼
│ ┌──────────────┐
└──▶│ Transaction │
└──────────────┘
│
▼
┌──────────────────────┐
│AppleDeviceRegistration│
└──────────────────────┘
Database Tables
| Table | Purpose |
|---|---|
loyalty_programs |
Merchant's program configuration (type, targets, branding) |
loyalty_cards |
Customer cards with stamp/point balances (merchant-scoped) |
loyalty_transactions |
Immutable audit log of all operations |
staff_pins |
Hashed PINs for fraud prevention |
apple_device_registrations |
Apple Wallet push notification tokens |
merchant_loyalty_settings |
Admin-controlled per-merchant settings |
Configuration
Environment variables (prefix: LOYALTY_):
# Anti-fraud defaults
LOYALTY_DEFAULT_COOLDOWN_MINUTES=15
LOYALTY_MAX_DAILY_STAMPS=5
LOYALTY_PIN_MAX_FAILED_ATTEMPTS=5
LOYALTY_PIN_LOCKOUT_MINUTES=30
# Points
LOYALTY_DEFAULT_POINTS_PER_EURO=10
# Google Wallet
LOYALTY_GOOGLE_ISSUER_ID=3388000000012345678
LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/path/to/service-account.json
# Apple Wallet
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
API Endpoints
Store Endpoints (/api/v1/store/loyalty/)
| Method | Endpoint | Description |
|---|---|---|
GET |
/program |
Get store's loyalty program |
POST |
/program |
Create loyalty program |
PATCH |
/program |
Update loyalty program |
GET |
/stats |
Get program statistics |
GET |
/cards |
List customer cards |
POST |
/cards/enroll |
Enroll customer in program |
POST |
/cards/lookup |
Look up card by QR/number |
POST |
/stamp |
Add stamp to card |
POST |
/stamp/redeem |
Redeem stamps for reward |
POST |
/points |
Earn points from purchase |
POST |
/points/redeem |
Redeem points for reward |
GET |
/pins |
List staff PINs |
POST |
/pins |
Create staff PIN |
PATCH |
/pins/{id} |
Update staff PIN |
DELETE |
/pins/{id} |
Delete staff PIN |
POST |
/pins/{id}/unlock |
Unlock locked PIN |
Admin Endpoints (/api/v1/admin/loyalty/)
| Method | Endpoint | Description |
|---|---|---|
GET |
/programs |
List all loyalty programs |
GET |
/programs/{id} |
Get specific program |
GET |
/programs/{id}/stats |
Get program statistics |
GET |
/stats |
Platform-wide statistics |
Storefront Endpoints (/api/v1/storefront/loyalty/)
| Method | Endpoint | Description |
|---|---|---|
GET |
/card |
Get customer's loyalty card and balance |
GET |
/transactions |
Get customer's transaction history |
POST |
/enroll |
Self-enrollment in loyalty program |
Public Endpoints (/api/v1/loyalty/)
| Method | Endpoint | Description |
|---|---|---|
GET |
/programs/{store_code} |
Get program info for enrollment |
GET |
/passes/apple/{serial}.pkpass |
Download Apple Wallet pass |
POST |
/apple/v1/devices/... |
Apple Web Service: register device |
DELETE |
/apple/v1/devices/... |
Apple Web Service: unregister |
GET |
/apple/v1/devices/... |
Apple Web Service: get updates |
GET |
/apple/v1/passes/... |
Apple Web Service: get pass |
Anti-Fraud System
Staff PIN Verification
All stamp and points operations require staff PIN verification (configurable per program).
# PIN is hashed with bcrypt
pin.set_pin("1234") # Stores bcrypt hash
pin.verify_pin("1234") # Returns True/False
Lockout Protection
- Max failed attempts: 5 (configurable)
- Lockout duration: 30 minutes (configurable)
- After lockout expires, PIN can be used again
- Admin can manually unlock via API
Cooldown Period
Prevents rapid stamp collection (fraud prevention):
Customer scans card → Gets stamp → Must wait 15 minutes → Can get next stamp
Daily Limits
Maximum stamps per card per day (default: 5).
Wallet Integration
Google Wallet
Architecture: Server-side storage with automatic API updates
All wallet operations are triggered automatically — no manual API calls needed:
| Event | Wallet Action | Trigger |
|---|---|---|
| Customer enrolls | Create LoyaltyClass (first time) + LoyaltyObject |
card_service.enroll_customer() → wallet_service.create_wallet_objects() |
| Stamp/points change | PATCH the object with new balance |
stamp_service/points_service → wallet_service.sync_card_to_wallets() |
| Customer views dashboard | Generate JWT "Add to Wallet" URL (1h expiry) | GET /storefront/loyalty/card → wallet_service.get_add_to_wallet_urls() |
Setup: Configure LOYALTY_GOOGLE_ISSUER_ID and LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON env vars. This is a platform-wide setting — all merchants automatically get Google Wallet support. See Google Wallet Setup for full instructions.
No device registration needed — Google syncs automatically.
Apple Wallet
Architecture: Push notification model
- Customer adds pass → Device registers with our server
- Stamp/points change → Send push notification to APNs
- Device receives push → Fetches updated pass from our server
Requires apple_device_registrations table for push tokens.
Usage Examples
Create a Loyalty Program
from app.modules.loyalty.services import program_service
from app.modules.loyalty.schemas import ProgramCreate
data = ProgramCreate(
loyalty_type="stamps",
stamps_target=10,
stamps_reward_description="Free coffee",
cooldown_minutes=15,
max_daily_stamps=5,
require_staff_pin=True,
card_color="#4F46E5",
)
program = program_service.create_program(db, store_id=1, data=data)
Enroll a Customer
from app.modules.loyalty.services import card_service
card = card_service.enroll_customer(db, customer_id=123, store_id=1)
# Returns LoyaltyCard with unique card_number and qr_code_data
Add a Stamp
from app.modules.loyalty.services import stamp_service
result = stamp_service.add_stamp(
db,
qr_code="abc123xyz",
staff_pin="1234",
ip_address="192.168.1.1",
)
# Returns dict with stamp_count, reward_earned, next_stamp_available_at, etc.
Earn Points from Purchase
from app.modules.loyalty.services import points_service
result = points_service.earn_points(
db,
card_number="123456789012",
purchase_amount_cents=2500, # €25.00
order_reference="ORD-12345",
staff_pin="1234",
)
# Returns dict with points_earned (250 at 10pts/€), points_balance, etc.
Services
| Service | Purpose |
|---|---|
program_service |
Program CRUD and statistics |
card_service |
Card enrollment, lookup, management |
stamp_service |
Stamp operations with anti-fraud |
points_service |
Points operations and redemption |
pin_service |
Staff PIN CRUD and verification |
wallet_service |
Unified wallet abstraction |
google_wallet_service |
Google Wallet API integration |
apple_wallet_service |
Apple Wallet pass generation |
Scheduled Tasks
| Task | Schedule | Description |
|---|---|---|
loyalty.sync_wallet_passes |
Hourly | Sync cards that missed real-time updates |
loyalty.expire_points |
Daily 02:00 | Expire points for inactive cards (based on points_expiration_days) |
UI Pages
Admin Pages
| Page | Path | Description |
|---|---|---|
| Programs Dashboard | /admin/loyalty/programs |
List all loyalty programs with stats |
| Merchant Detail | /admin/loyalty/merchants/{id} |
Detailed view of a merchant's program |
| Merchant Settings | /admin/loyalty/merchants/{id}/settings |
Admin-controlled merchant settings |
| Analytics | /admin/loyalty/analytics |
Platform-wide analytics |
Store Pages
| Page | Path | Description |
|---|---|---|
| Terminal | /store/loyalty/terminal |
Scan card, add stamps/points, redeem |
| Cards List | /store/loyalty/cards |
Browse customer cards |
| Card Detail | /store/loyalty/cards/{id} |
Individual card detail |
| Enroll | /store/loyalty/enroll |
Enroll new customer |
| Settings | /store/loyalty/settings |
Program settings |
| Stats | /store/loyalty/stats |
Store-level statistics |
Storefront Pages
| Page | Path | Description |
|---|---|---|
| Dashboard | /loyalty/dashboard |
Customer's card and balance |
| History | /loyalty/history |
Transaction history |
| Enroll | /loyalty/enroll |
Self-enrollment page |
Localization
Available in 5 languages:
- English (
en.json) - French (
fr.json) - German (
de.json) - Luxembourgish (
lu.json,lb.json)
Future Enhancements (Phase 3+)
- Rewards catalog with configurable tiers
- Customer tiers (Bronze/Silver/Gold)
- Promotions engine (bonus points, discounts, free items)
- Referral program
- Gamification (spin wheel, scratch cards)
- POS integration
- Batch import of existing loyalty cards
- Real-time WebSocket updates
- Receipt printing