Files
orion/docs/modules/loyalty.md
Samir Boulahtit 74bbf84702 fix(loyalty): use Code 128 barcode for retail scanner compatibility
Switch wallet pass barcodes from QR to Code 128 format using the
card_number (digits only), so standard retail barcode scanners can
read loyalty cards. Apple Wallet keeps QR as fallback in barcodes
array. Also fix stale Vendor.loyalty_program relationship (now
company-based), add parent init calls in vendor JS components,
and update module docs to reflect Phase 2 completion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 18:55:20 +01:00

11 KiB

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 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

┌─────────────────┐       ┌─────────────────┐
│    Company      │───────│ LoyaltyProgram  │
└─────────────────┘  1:1  └─────────────────┘
        │                         │
        ▼                ┌────────┼──────────┐
┌──────────────────┐     │        │          │
│  CompanyLoyalty  │     ▼        ▼          ▼
│  Settings        │┌──────────┐┌──────────┐┌────────┐
└──────────────────┘│ StaffPin ││LoyaltyCard││(config)│
                    └──────────┘└──────────┘└────────┘
                          │          │
                          │          ▼
                          │   ┌──────────────┐
                          └──▶│ Transaction  │
                               └──────────────┘
                                     │
                                     ▼
                          ┌──────────────────────┐
                          │AppleDeviceRegistration│
                          └──────────────────────┘

Database Tables

Table Purpose
loyalty_programs Company's program configuration (type, targets, branding)
loyalty_cards Customer cards with stamp/point balances (company-scoped)
loyalty_transactions Immutable audit log of all operations
staff_pins Hashed PINs for fraud prevention
apple_device_registrations Apple Wallet push notification tokens
company_loyalty_settings Admin-controlled per-company 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

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

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/{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).

# 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

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

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

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
Company Detail /admin/loyalty/companies/{id} Detailed view of a company's program
Company Settings /admin/loyalty/companies/{id}/settings Admin-controlled company settings
Analytics /admin/loyalty/analytics Platform-wide analytics

Vendor Pages

Page Path Description
Terminal /vendor/loyalty/terminal Scan card, add stamps/points, redeem
Cards List /vendor/loyalty/cards Browse customer cards
Card Detail /vendor/loyalty/cards/{id} Individual card detail
Enroll /vendor/loyalty/enroll Enroll new customer
Settings /vendor/loyalty/settings Program settings
Stats /vendor/loyalty/stats Vendor-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