feat(loyalty): implement complete loyalty module MVP

Add stamp-based and points-based loyalty programs for vendors with:

Database Models (5 tables):
- loyalty_programs: Vendor program configuration
- loyalty_cards: Customer cards with stamp/point balances
- loyalty_transactions: Immutable audit log
- staff_pins: Fraud prevention PINs (bcrypt hashed)
- apple_device_registrations: Apple Wallet push tokens

Services:
- program_service: Program CRUD and statistics
- card_service: Customer enrollment and card lookup
- stamp_service: Stamp operations with anti-fraud checks
- points_service: Points earning and redemption
- pin_service: Staff PIN management with lockout
- wallet_service: Unified wallet abstraction
- google_wallet_service: Google Wallet API integration
- apple_wallet_service: Apple Wallet .pkpass generation

API Routes:
- Admin: /api/v1/admin/loyalty/* (programs list, stats)
- Vendor: /api/v1/vendor/loyalty/* (stamp, points, cards, PINs)
- Public: /api/v1/loyalty/* (enrollment, Apple Web Service)

Anti-Fraud Features:
- Staff PIN verification (configurable per program)
- Cooldown period between stamps (default 15 min)
- Daily stamp limits (default 5/day)
- PIN lockout after failed attempts

Wallet Integration:
- Google Wallet: LoyaltyClass and LoyaltyObject management
- Apple Wallet: .pkpass generation with PKCS#7 signing
- Apple Web Service endpoints for device registration/updates

Also includes:
- Alembic migration for all tables with indexes
- Localization files (en, fr, de, lu)
- Module documentation
- Phase 2 interface and user journey plan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 23:04:00 +01:00
parent fbcf07914e
commit b5a803cde8
44 changed files with 8073 additions and 0 deletions

275
docs/modules/loyalty.md Normal file
View File

@@ -0,0 +1,275 @@
# Loyalty Module
The Loyalty Module provides stamp-based and points-based loyalty programs for Wizamart vendors with Google Wallet and Apple Wallet integration.
## Overview
| Aspect | Description |
|--------|-------------|
| Module Code | `loyalty` |
| Dependencies | `customers` |
| Status | Phase 1 MVP 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
```
┌─────────────────┐ ┌─────────────────┐
│ Vendor │───────│ LoyaltyProgram │
└─────────────────┘ 1:1 └─────────────────┘
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ StaffPin │ │LoyaltyCard│ │ (config)│
└──────────┘ └──────────┘ └──────────┘
│ │
│ ▼
│ ┌──────────────┐
└───▶│ Transaction │
└──────────────┘
┌──────────────────────┐
│AppleDeviceRegistration│
└──────────────────────┘
```
### Database Tables
| Table | Purpose |
|-------|---------|
| `loyalty_programs` | Vendor's program configuration (type, targets, branding) |
| `loyalty_cards` | Customer cards with stamp/point balances |
| `loyalty_transactions` | Immutable audit log of all operations |
| `staff_pins` | Hashed PINs for fraud prevention |
| `apple_device_registrations` | Apple Wallet push notification tokens |
## 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
### Vendor Endpoints (`/api/v1/vendor/loyalty/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/program` | Get vendor'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 |
### Public Endpoints (`/api/v1/loyalty/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/programs/{vendor_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, vendor_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, vendor_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 old points (future enhancement) |
## Localization
Available in 4 languages:
- English (`en.json`)
- French (`fr.json`)
- German (`de.json`)
- Luxembourgish (`lu.json`)
## Future Enhancements (Phase 2)
- Rewards catalog with configurable tiers
- Customer tiers (Bronze/Silver/Gold)
- Referral program
- Gamification (spin wheel, scratch cards)
- POS integration
- Points expiration rules
- Batch import of existing loyalty cards

View File

@@ -0,0 +1,670 @@
# Loyalty Module Phase 2: Admin & Vendor Interfaces
## Executive Summary
This document outlines the plan for building admin and vendor interfaces for the Loyalty Module, along with detailed user journeys for stamp-based and points-based loyalty programs. The design follows market best practices from leading loyalty platforms (Square Loyalty, Toast, Fivestars, Belly, Punchh).
---
## Part 1: Interface Design
### 1.1 Vendor Dashboard (Retail Store)
#### Main Loyalty Dashboard (`/vendor/loyalty`)
```
┌─────────────────────────────────────────────────────────────────┐
│ 🎯 Loyalty Program [Setup] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐│
│ │ 1,247 │ │ 892 │ │ 156 │ │ €2.3k ││
│ │ Members │ │ Active │ │ Redeemed │ │ Saved ││
│ │ Total │ │ 30 days │ │ This Month │ │ Value ││
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 📊 Activity Chart (Last 30 Days) ││
│ │ [Stamps Issued] [Rewards Redeemed] [New Members] ││
│ │ ═══════════════════════════════════════════════ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────────┐ │
│ │ 🔥 Quick Actions │ │ 📋 Recent Activity │ │
│ │ │ │ │ │
│ │ [ Add Stamp] │ │ • John D. earned stamp #8 │ │
│ │ [🎁 Redeem Reward] │ │ • Marie L. redeemed reward │ │
│ │ [👤 Enroll Customer] │ │ • Alex K. joined program │ │
│ │ [🔍 Look Up Card] │ │ • Sarah M. earned 50 pts │ │
│ │ │ │ │ │
│ └─────────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
#### Stamp/Points Terminal (`/vendor/loyalty/terminal`)
**Primary interface for daily operations - optimized for tablet/touchscreen:**
```
┌─────────────────────────────────────────────────────────────────┐
│ 🎯 Loyalty Terminal │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 📷 SCAN QR CODE │ │
│ │ │ │
│ │ [Camera Viewfinder Area] │ │
│ │ │ │
│ │ or enter card number │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Card Number... │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ [Use Camera] [Enter Manually] [Recent Cards ▼] │
│ │
└─────────────────────────────────────────────────────────────────┘
```
**After scanning - Customer Card View:**
```
┌─────────────────────────────────────────────────────────────────┐
│ ← Back Customer Card │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 👤 Marie Laurent │ │
│ │ marie.laurent@email.com │ │
│ │ Member since: Jan 2024 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ☕ ☕ ☕ ☕ ☕ ☕ ☕ ☕ ○ ○ │ │
│ │ │ │
│ │ 8 / 10 stamps │ │
│ │ 2 more until FREE COFFEE │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ [ ADD STAMP ] [ 🎁 REDEEM ] │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ⚠️ Next stamp available in 12 minutes │
│ │
└─────────────────────────────────────────────────────────────────┘
```
**PIN Entry Modal (appears when adding stamp):**
```
┌─────────────────────────────────────────────────────────────────┐
│ Enter Staff PIN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ ● ● ● ● │ │
│ └─────────────────┘ │
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 1 │ │ 2 │ │ 3 │ │
│ └─────┘ └─────┘ └─────┘ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 4 │ │ 5 │ │ 6 │ │
│ └─────┘ └─────┘ └─────┘ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 7 │ │ 8 │ │ 9 │ │
│ └─────┘ └─────┘ └─────┘ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ ⌫ │ │ 0 │ │ ✓ │ │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ [Cancel] │
└─────────────────────────────────────────────────────────────────┘
```
#### Program Setup (`/vendor/loyalty/settings`)
```
┌─────────────────────────────────────────────────────────────────┐
│ ⚙️ Loyalty Program Settings │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Program Type │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ☑️ Stamps │ │ ☐ Points │ │ ☐ Hybrid │ │
│ │ Buy 10 Get 1 │ │ Earn per € │ │ Both systems │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ Stamp Configuration │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Stamps needed for reward: [ 10 ▼ ] │ │
│ │ Reward description: [ Free coffee of choice ] │ │
│ │ Reward value (optional): [ €4.50 ] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 🛡️ Fraud Prevention │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ☑️ Require staff PIN for operations │ │
│ │ Cooldown between stamps: [ 15 ] minutes │ │
│ │ Max stamps per day: [ 5 ] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 🎨 Card Branding │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Card name: [ Café Loyalty Card ] │ │
│ │ Primary color: [████] #4F46E5 │ │
│ │ Logo: [Upload] cafe-logo.png ✓ │ │
│ │ │ │
│ │ Preview: ┌────────────────────┐ │ │
│ │ │ ☕ Café Loyalty │ │ │
│ │ │ ████████░░ │ │ │
│ │ │ 8/10 stamps │ │ │
│ │ └────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Save Changes] │
└─────────────────────────────────────────────────────────────────┘
```
#### Staff PIN Management (`/vendor/loyalty/pins`)
```
┌─────────────────────────────────────────────────────────────────┐
│ 🔐 Staff PINs [+ Add PIN] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 👤 Marie (Manager) [Edit] [🗑️] │ │
│ │ Last used: Today, 14:32 │ │
│ │ Status: ✅ Active │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 👤 Thomas (Staff) [Edit] [🗑️] │ │
│ │ Last used: Today, 11:15 │ │
│ │ Status: ✅ Active │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ 👤 Julie (Staff) [Edit] [🗑️] │ │
│ │ Last used: Yesterday │ │
│ │ Status: 🔒 Locked (3 failed attempts) [Unlock] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
Staff PINs prevent unauthorized stamp/point operations. │
│ PINs are locked after 5 failed attempts for 30 minutes. │
│ │
└─────────────────────────────────────────────────────────────────┘
```
#### Customer Cards List (`/vendor/loyalty/cards`)
```
┌─────────────────────────────────────────────────────────────────┐
│ 👥 Loyalty Members 🔍 [Search...] [Export]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ Filter: [All ▼] [Active ▼] [Has Reward Ready ▼] │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Customer │ Card # │ Stamps │ Last Visit │ ⋮ ││
│ ├───────────────────┼──────────────┼────────┼────────────┼────┤│
│ │ Marie Laurent │ 4821-7493 │ 8/10 ⭐│ Today │ ⋮ ││
│ │ Jean Dupont │ 4821-2847 │ 10/10 🎁│ Yesterday │ ⋮ ││
│ │ Sophie Martin │ 4821-9382 │ 3/10 │ 3 days ago │ ⋮ ││
│ │ Pierre Bernard │ 4821-1029 │ 6/10 │ 1 week ago │ ⋮ ││
│ │ ... │ ... │ ... │ ... │ ⋮ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ Showing 1-20 of 1,247 members [← Prev] [1] [2] [Next →]│
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 Admin Dashboard (Platform)
#### Platform Loyalty Overview (`/admin/loyalty`)
```
┌─────────────────────────────────────────────────────────────────┐
│ 🎯 Loyalty Programs Platform │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐│
│ │ 47 │ │ 38 │ │ 12,847 │ │ €47k ││
│ │ Programs │ │ Active │ │ Members │ │ Saved ││
│ │ Total │ │ Programs │ │ Total │ │ Value ││
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────┘│
│ │
│ Programs by Type: │
│ ═══════════════════════════════════════ │
│ Stamps: ████████████████████ 32 (68%) │
│ Points: ███████ 11 (23%) │
│ Hybrid: ████ 4 (9%) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Vendor │ Type │ Members │ Activity │ Status ││
│ ├───────────────────┼─────────┼─────────┼──────────┼──────────┤│
│ │ Café du Coin │ Stamps │ 1,247 │ High │ ✅ Active││
│ │ Boulangerie Paul │ Points │ 892 │ Medium │ ✅ Active││
│ │ Pizza Roma │ Stamps │ 456 │ Low │ ⚠️ Setup ││
│ │ ... │ ... │ ... │ ... │ ... ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Part 2: User Journeys
### 2.1 Stamp-Based Loyalty Journey
#### Customer Journey: Enrollment
```
┌─────────────────────────────────────────────────────────────────┐
│ STAMP LOYALTY - ENROLLMENT │
└─────────────────────────────────────────────────────────────────┘
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ DISCOVER│────▶│ JOIN │────▶│ SAVE │────▶│ USE │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ 1. Customer sees │ 2. Scans QR at │ 3. Card added │ 4. Ready to │
│ sign at counter│ register or │ to Google/ │ collect │
│ "Join our │ gives email │ Apple Wallet│ stamps! │
│ loyalty!" │ to cashier │ │ │
└─────────────────────────────────────────────────────────┘
```
**Detailed Steps:**
1. **Discovery** (In-Store)
- Customer sees loyalty program signage/tent card
- QR code displayed at counter
- Staff mentions program during checkout
2. **Sign Up** (30 seconds)
- Customer scans QR code with phone
- Lands on mobile enrollment page
- Enters: Email (required), Name (optional)
- Accepts terms with checkbox
- Submits
3. **Card Creation** (Instant)
- System creates loyalty card
- Generates unique card number & QR code
- Shows "Add to Wallet" buttons
- Sends welcome email with card link
4. **Wallet Save** (Optional but encouraged)
- Customer taps "Add to Google Wallet" or "Add to Apple Wallet"
- Pass appears in their wallet app
- Always accessible, works offline
#### Customer Journey: Earning Stamps
```
┌─────────────────────────────────────────────────────────────────┐
│ STAMP LOYALTY - EARNING │
└─────────────────────────────────────────────────────────────────┘
Customer Staff System Wallet
│ │ │ │
│ 1. Makes │ │ │
│ purchase │ │ │
│───────────────▶│ │ │
│ │ │ │
│ 2. Shows │ │ │
│ loyalty card │ │ │
│───────────────▶│ │ │
│ │ 3. Scans QR │ │
│ │─────────────────▶│ │
│ │ │ │
│ │ 4. Enters PIN │ │
│ │─────────────────▶│ │
│ │ │ │
│ │ 5. Confirms │ │
│ │◀─────────────────│ │
│ │ "Stamp added!" │ │
│ │ │ │
│ 6. Verbal │ │ 7. Push │
│ confirmation │ │ notification │
│◀───────────────│ │────────────────▶│
│ │ │ │
│ │ 8. Pass updates│
│◀───────────────────────────────────│────────────────▶│
│ "8/10 stamps" │ │
```
**Anti-Fraud Checks (Automatic):**
1. ✅ Card is active
2. ✅ Program is active
3. ✅ Staff PIN is valid
4. ✅ Cooldown period elapsed (15 min since last stamp)
5. ✅ Daily limit not reached (max 5/day)
**Success Response:**
```json
{
"success": true,
"stamp_count": 8,
"stamps_target": 10,
"stamps_until_reward": 2,
"message": "2 more stamps until your free coffee!",
"next_stamp_available": "2024-01-28T15:30:00Z"
}
```
#### Customer Journey: Redeeming Reward
```
┌─────────────────────────────────────────────────────────────────┐
│ STAMP LOYALTY - REDEMPTION │
└─────────────────────────────────────────────────────────────────┘
Customer Staff System
│ │ │
│ 1. "I'd like │ │
│ to redeem my │ │
│ free coffee" │ │
│───────────────▶│ │
│ │ │
│ 2. Shows card │ │
│ (10/10 stamps)│ │
│───────────────▶│ │
│ │ 3. Scans + sees │
│ │ "REWARD READY" │
│ │─────────────────▶│
│ │ │
│ │ 4. Clicks │
│ │ [REDEEM REWARD] │
│ │─────────────────▶│
│ │ │
│ │ 5. Enters PIN │
│ │─────────────────▶│
│ │ │
│ │ 6. Confirms │
│ │◀─────────────────│
│ │ "Reward redeemed"│
│ │ Stamps reset: 0 │
│ │ │
│ 7. Gives free │ │
│ coffee │ │
│◀───────────────│ │
│ │ │
│ 🎉 HAPPY │ │
│ CUSTOMER! │ │
```
### 2.2 Points-Based Loyalty Journey
#### Customer Journey: Earning Points
```
┌─────────────────────────────────────────────────────────────────┐
│ POINTS LOYALTY - EARNING │
└─────────────────────────────────────────────────────────────────┘
Customer Staff System
│ │ │
│ 1. Purchases │ │
│ €25.00 order │ │
│───────────────▶│ │
│ │ │
│ 2. Shows │ │
│ loyalty card │ │
│───────────────▶│ │
│ │ 3. Scans card │
│ │─────────────────▶│
│ │ │
│ │ 4. Enters amount │
│ │ €25.00 │
│ │─────────────────▶│
│ │ │
│ │ 5. Enters PIN │
│ │─────────────────▶│
│ │ │ ┌──────────┐
│ │ │ │Calculate:│
│ │ │ │€25 × 10 │
│ │ │ │= 250 pts │
│ │ │ └──────────┘
│ │ 6. Confirms │
│ │◀─────────────────│
│ │ "+250 points!" │
│ │ │
│ 7. Receipt │ │
│ shows points │ │
│◀───────────────│ │
```
**Points Calculation:**
```
Purchase: €25.00
Rate: 10 points per euro
Points Earned: 250 points
New Balance: 750 points
```
#### Customer Journey: Redeeming Points
```
┌─────────────────────────────────────────────────────────────────┐
│ POINTS LOYALTY - REDEMPTION │
└─────────────────────────────────────────────────────────────────┘
Customer Staff System
│ │ │
│ 1. Views │ │
│ rewards in │ │
│ wallet app │ │
│ │ │ │
│ ▼ │ │
│ ┌──────────┐ │ │
│ │ REWARDS │ │ │
│ │──────────│ │ │
│ │ 500 pts │ │ │
│ │ Free │ │ │
│ │ Drink │ │ │
│ │──────────│ │ │
│ │ 1000 pts │ │ │
│ │ Free │ │ │
│ │ Meal │ │ │
│ └──────────┘ │ │
│ │ │
│ 2. "I want to │ │
│ redeem for │ │
│ free drink" │ │
│───────────────▶│ │
│ │ 3. Scans card │
│ │ Selects reward │
│ │─────────────────▶│
│ │ │
│ │ 4. Enters PIN │
│ │─────────────────▶│
│ │ │
│ │ 5. Confirms │
│ │◀─────────────────│
│ │ "-500 points" │
│ │ Balance: 250 pts │
│ │ │
│ 6. Gets free │ │
│ drink │ │
│◀───────────────│ │
```
---
## Part 3: Market Best Practices
### 3.1 Competitive Analysis
| Feature | Square Loyalty | Toast | Fivestars | **Wizamart** |
|---------|---------------|-------|-----------|--------------|
| Stamp cards | ✅ | ✅ | ✅ | ✅ |
| Points system | ✅ | ✅ | ✅ | ✅ |
| Google Wallet | ✅ | ❌ | ✅ | ✅ |
| Apple Wallet | ✅ | ✅ | ✅ | ✅ |
| Staff PIN | ❌ | ✅ | ✅ | ✅ |
| Cooldown fraud protection | ❌ | ❌ | ✅ | ✅ |
| Daily limits | ❌ | ❌ | ✅ | ✅ |
| Tablet terminal | ✅ | ✅ | ✅ | ✅ (planned) |
| Customer app | ✅ | ✅ | ✅ | Via Wallet |
| Analytics dashboard | ✅ | ✅ | ✅ | ✅ |
### 3.2 Best Practices to Implement
#### UX Best Practices
1. **Instant gratification** - Show stamp/points immediately after transaction
2. **Progress visualization** - Clear progress bars/stamp grids
3. **Reward proximity** - "Only 2 more until your free coffee!"
4. **Wallet-first** - Push customers to save to wallet
5. **Offline support** - Card works even without internet (via wallet)
#### Fraud Prevention Best Practices
1. **Multi-layer security** - PIN + cooldown + daily limits
2. **Staff accountability** - Every transaction tied to a staff PIN
3. **Audit trail** - Complete history with IP/device info
4. **Lockout protection** - Automatic PIN lockout after failures
5. **Admin oversight** - Unlock and PIN management in dashboard
#### Engagement Best Practices
1. **Welcome bonus** - Give 1 stamp on enrollment (configurable)
2. **Birthday rewards** - Extra stamps/points on customer birthday
3. **Milestone notifications** - "Congrats! 50 stamps earned lifetime!"
4. **Re-engagement** - Remind inactive customers via email
5. **Double points days** - Promotional multipliers (future)
---
## Part 4: Implementation Roadmap
### Phase 2A: Vendor Interface (Priority)
| Task | Effort | Priority |
|------|--------|----------|
| Loyalty terminal (scan/stamp/redeem) | 3 days | P0 |
| Program setup wizard | 2 days | P0 |
| Staff PIN management | 1 day | P0 |
| Customer cards list | 1 day | P1 |
| Dashboard with stats | 2 days | P1 |
| Export functionality | 1 day | P2 |
### Phase 2B: Admin Interface
| Task | Effort | Priority |
|------|--------|----------|
| Programs list view | 1 day | P1 |
| Platform-wide stats | 1 day | P1 |
| Program detail view | 0.5 day | P2 |
### Phase 2C: Customer Experience
| Task | Effort | Priority |
|------|--------|----------|
| Enrollment page (mobile) | 1 day | P0 |
| Card detail page | 0.5 day | P1 |
| Wallet pass polish | 1 day | P1 |
| Email templates | 1 day | P2 |
### Phase 2D: Polish & Advanced
| Task | Effort | Priority |
|------|--------|----------|
| QR code scanner (JS) | 2 days | P0 |
| Real-time updates (WebSocket) | 1 day | P2 |
| Receipt printing | 1 day | P3 |
| POS integration hooks | 2 days | P3 |
---
## Part 5: Technical Specifications
### Vendor Terminal Requirements
- **Responsive**: Works on tablet (primary), desktop, mobile
- **Touch-friendly**: Large buttons, numpad for PIN
- **Camera access**: For QR code scanning (WebRTC)
- **Offline-capable**: Queue operations if network down (future)
- **Real-time**: WebSocket for instant updates
### Frontend Stack
- **Framework**: React/Vue components (match existing stack)
- **QR Scanner**: `html5-qrcode` or `@aspect-sdk/barcode-reader`
- **Charts**: Existing charting library (Chart.js or similar)
- **Animations**: CSS transitions for stamp animations
### API Considerations
- All vendor endpoints require `vendor_id` from auth token
- Staff PIN passed in request body, not headers
- Rate limiting on lookup/scan endpoints
- Pagination on card list (default 50)
---
## Appendix: Mockup Reference Images
### Stamp Card Visual (Wallet Pass)
```
┌────────────────────────────────────┐
│ ☕ Café du Coin │
│ │
│ ████ ████ ████ ████ ████ │
│ ████ ████ ████ ░░░░ ░░░░ │
│ │
│ 8/10 STAMPS │
│ 2 more until FREE COFFEE │
│ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
│ ▓▓▓▓▓▓▓▓ QR CODE ▓▓▓▓▓▓▓▓ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
│ │
│ Card #4821-7493-2841 │
└────────────────────────────────────┘
```
### Points Card Visual (Wallet Pass)
```
┌────────────────────────────────────┐
│ 🍕 Pizza Roma Rewards │
│ │
│ ★ 750 ★ │
│ POINTS │
│ │
│ ────────────────────── │
│ Next reward: 500 pts │
│ Free drink │
│ ────────────────────── │
│ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
│ ▓▓▓▓▓▓▓▓ QR CODE ▓▓▓▓▓▓▓▓ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
│ │
│ Card #4821-2847-9283 │
└────────────────────────────────────┘
```
---
*Document Version: 1.0*
*Created: 2025-01-28*
*Author: Wizamart Engineering*