# 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 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](../deployment/hetzner-server-setup.md#step-25-google-wallet-integration) for full instructions. 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