Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
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 API updates
- Program created → Create
LoyaltyClassvia Google API - Customer enrolls → Create
LoyaltyObjectvia Google API - Stamp/points change →
PATCHthe object - Generate JWT for "Add to Wallet" button
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