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>
317 lines
11 KiB
Markdown
317 lines
11 KiB
Markdown
# 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_`):
|
|
|
|
```bash
|
|
# 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).
|
|
|
|
```python
|
|
# 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**
|
|
|
|
1. Program created → Create `LoyaltyClass` via Google API
|
|
2. Customer enrolls → Create `LoyaltyObject` via Google API
|
|
3. Stamp/points change → `PATCH` the object
|
|
4. Generate JWT for "Add to Wallet" button
|
|
|
|
No device registration needed - Google syncs automatically.
|
|
|
|
### Apple Wallet
|
|
|
|
Architecture: **Push notification model**
|
|
|
|
1. Customer adds pass → Device registers with our server
|
|
2. Stamp/points change → Send push notification to APNs
|
|
3. Device receives push → Fetches updated pass from our server
|
|
|
|
Requires `apple_device_registrations` table for push tokens.
|
|
|
|
## Usage Examples
|
|
|
|
### Create a Loyalty Program
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|