1. High-Level Overview
This document describes a modular loyalty platform that supports:
- Stamp-based loyalty (e.g., coffee shops, barbers)
- Points-based loyalty (e.g., clothing stores, restaurants)
- Google Wallet passes (JSON + API)
- Apple Wallet passes (
.pkpassfiles) - Staff PIN system for fraud prevention
- QR-based flows for visits and purchases
- Email collection and basic CRM
The architecture is designed to be extensible: you can add tiers, referrals, coupons, gift cards, and more without redesigning the core.
2. Backend Architecture
2.1 Core Modules
- Auth & Merchant Management: merchants, stores, staff PINs, billing hooks.
- Customer & Pass Management: customers, pass objects, Wallet IDs.
- Loyalty Engine: stamp logic and points logic.
- QR Engine: static QR codes per store, scan endpoints.
- Dashboard API: analytics, customer lists, rewards, exports.
- Wallet Integrations: Google Wallet and Apple Wallet modules.
2.2 Folder Structure
backend/
app/
main.py
config.py
dependencies.py
api/
merchants.py
stores.py
passes.py
stamps.py
points.py
qr.py
dashboard.py
apple_passes.py
core/
google_wallet.py
loyalty_engine/
stamps.py
points.py
qr_generator.py
apple_wallet/
__init__.py
signer.py
generator.py
templates/
loyalty_pass.json
images/
icon.png
logo.png
strip.png
models/
merchant.py
store.py
customer.py
pass_object.py
stamp_event.py
points_event.py
apple_device_registration.py
db/
base.py
session.py
migrations/
utils/
security.py
id_generator.py
validators.py
requirements.txt
3. Data Model
3.1 Merchant vs Store
Merchant = business entity (e.g., “CoffeeLux”) Store = specific location/branch (e.g., “CoffeeLux Gare”).
Merchant
| Field | Type |
|---|---|
| id | UUID |
| name | string |
| string | |
| password_hash | string |
| created_at | datetime |
Store
| Field | Type |
|---|---|
| id | UUID |
| merchant_id | FK → Merchant |
| name | string |
| qr_code_url | string |
| loyalty_type | "stamps" | "points" | "hybrid" |
| staff_pin_hash | string (hashed) |
Customer
| Field | Type |
|---|---|
| id | UUID |
| string (nullable) | |
| phone | string (nullable) |
| email_consent | bool |
| email_consent_at | datetime (nullable) |
| created_at | datetime |
PassObject
| Field | Type |
|---|---|
| id | UUID |
| customer_id | FK → Customer |
| store_id | FK → Store |
| google_pass_id | string (nullable) |
| apple_serial | string (nullable) |
| apple_auth_token | string (nullable) |
| stamp_count | int |
| points_balance | int |
| reward_unlocked | bool |
| last_stamp_at | datetime (nullable) |
| last_points_at | datetime (nullable) |
StampEvent
| Field | Type |
|---|---|
| id | UUID |
| pass_id | FK → PassObject |
| store_id | FK → Store |
| timestamp | datetime |
PointsEvent
| Field | Type |
|---|---|
| id | UUID |
| pass_id | FK → PassObject |
| store_id | FK → Store |
| points_added | int |
| amount | decimal (optional) |
| timestamp | datetime |
AppleDeviceRegistration
| Field | Type |
|---|---|
| id | UUID |
| device_id | string |
| pass_serial | string |
| push_token | string |
| updated_at | datetime |
4. Core Flows
4.1 Creating a Pass (Onboarding)
- Customer scans a QR code or visits a link.
- Page asks for optional data:
- Name (optional)
- Email (optional, with consent checkbox)
- Phone (optional)
- Backend:
- Creates or finds Customer.
- Creates PassObject for the chosen store.
- Generates Google Wallet link and/or Apple Wallet
.pkpass.
- Customer taps “Add to Google Wallet” or “Add to Apple Wallet”.
4.2 Stamping a Pass (Visits)
Endpoint: POST /stamp/{store_id}
- Customer scans store QR:
https://yourdomain.com/stamp/{store_id}. - Page identifies the customer’s pass (via login, token, or Wallet ID).
- Staff enters a PIN on the customer’s device.
- Backend:
- Validates staff PIN.
- Checks cooldown (e.g., 1 stamp per X minutes).
- Increments
stamp_count. - Logs a StampEvent.
- Updates Google Wallet and Apple Wallet passes.
- Customer sees updated stamp count.
4.3 Adding Points (Purchases)
Endpoint: POST /points/{store_id}
{
"pass_id": "abc123",
"amount": 59.90,
"staff_pin": "4821"
}
- Staff enters purchase amount and PIN.
- Backend:
- Validates staff PIN.
- Converts amount → points (e.g., €1 = 1 point).
- Updates
points_balance. - Logs a PointsEvent.
- Updates Wallet passes.
4.4 Reward Logic
Implemented in loyalty_engine/stamps.py and loyalty_engine/points.py.
- Stamps: reward after N stamps.
- Points: reward at thresholds (e.g., 100, 250, 500 points).
- Hybrid: store can use both stamps and points.
5. Staff PIN System & Anti-Fraud
5.1 Staff PIN Concept
Each store has one or more staff PINs. A stamp or points addition is only valid if a correct PIN is provided.
Example request:
POST /stamp/{store_id}
{
"pass_id": "abc123",
"staff_pin": "4821"
}
5.2 Implementation
- Store table:
staff_pin_hash(hashed with bcrypt/argon2). - Staff enters PIN on customer’s device after a purchase/visit.
- Backend compares provided PIN with hash.
5.3 Additional Anti-Fraud Layers
- Cooldown: 1 stamp per X minutes per pass.
- Device fingerprinting: optional (IP, user agent, cookies).
- Reward state checks: don’t stamp a full card.
- Staff PIN rotation: merchants can change PIN periodically.
- Attempt limits: block after N wrong PIN attempts.
6. Email Collection & GDPR Considerations
6.1 When to Collect Email
- During pass creation: best moment, right after QR scan.
- On reward redemption: “Want your next reward reminder by email?”
- On progress view: small banner: “Add your email to get reward reminders.”
6.2 Data Model
Customer.emailCustomer.email_consentCustomer.email_consent_at
6.3 GDPR-Friendly Flow (EU/Luxembourg)
- Use an explicit checkbox:
“I agree to receive loyalty updates and promotions.” - Do not pre-check the box.
- Store consent timestamp.
- Provide unsubscribe link in emails.
6.4 Usage
- Reward notifications (“Your reward is ready”).
- Progress nudges (“You’re 1 stamp away”).
- Promotions and new collection announcements.
- Birthday offers (if DOB is collected).
7. Features Beyond Stamps & Points
7.1 Reward Types
- Tiered loyalty levels: Bronze, Silver, Gold based on spend or visits.
- Cashback / store credit: earn balance redeemable in-store.
- Birthday rewards: automatic bonuses around customer’s birthday.
- Referral rewards: invite friends, both get points or discounts.
- Hybrid models: stamps + points + referrals.
7.2 Coupons, Vouchers, Gift Cards
- Digital coupons (percentage or fixed amount).
- Digital gift cards with balance.
- Time-limited offers (weekend-only, seasonal).
7.3 Marketing & Communication
- Email campaigns.
- SMS campaigns.
- Wallet-based notifications (pass updates, messages).
7.4 Analytics & CRM
- Visit frequency, average spend, redemption rates.
- Customer lifetime value.
- New vs returning customers.
- Store-level and staff-level performance.
- Basic CRM: customer profiles, segmentation.
7.5 Advanced Options
- Receipt scanning with OCR.
- POS integration (automatic points and redemptions).
- Gamification (spin-the-wheel, scratch cards, challenges).
- Paid memberships / VIP subscriptions.
8. Google Wallet Integration
8.1 Core Concepts
- Google Wallet uses Passes API with JSON-based classes and objects.
- You define a loyalty class and create pass objects per customer.
- Updates are done via API calls (patching fields like points, stamps, messages).
8.2 Required Setup
- Google Cloud project.
- Wallet API enabled.
- Service account with JSON key.
- Issuer ID and loyalty class definition.
8.3 Backend Responsibilities
- Create pass objects for new customers.
- Store
google_pass_idin PassObject. - Update pass when stamps/points change.
- Optionally send messages or promotions via pass updates.
8.4 Example Endpoint Sketch
@router.post("/passes/create")
async def create_pass(data: PassCreateRequest, db: Session = Depends(get_db)):
customer = get_or_create_customer(db, data)
pass_obj = get_or_create_pass(db, customer, data.store_id)
google_link = google_wallet.create_pass(pass_obj)
return {"add_to_wallet_url": google_link}
9. Apple Wallet Integration
9.1 Core Difference
Apple Wallet uses signed .pkpass files instead of a JSON API.
A .pkpass is a ZIP containing:
pass.json– pass data- Images – logo, icon, strip
manifest.json– SHA-1 hashes of all filessignature– signed manifest using your certificate
9.2 Requirements
- Apple Developer Program membership.
- Pass Type ID (e.g.,
pass.com.yourbrand.loyalty). - Pass certificate (
.p12). - Push notification certificate for Wallet updates.
9.3 Pass Template (pass.json)
{
"formatVersion": 1,
"passTypeIdentifier": "pass.com.yourbrand.loyalty",
"teamIdentifier": "ABCDE12345",
"organizationName": "Your Brand",
"serialNumber": "{{serial}}",
"description": "Loyalty Card",
"logoText": "Your Brand",
"foregroundColor": "rgb(255,255,255)",
"backgroundColor": "rgb(0,0,0)",
"storeCard": {
"primaryFields": [
{
"key": "points",
"label": "Points",
"value": "{{points}}"
}
],
"secondaryFields": [
{
"key": "stamps",
"label": "Stamps",
"value": "{{stamps}}"
}
]
},
"barcode": {
"format": "PKBarcodeFormatQR",
"message": "{{pass_id}}",
"messageEncoding": "iso-8859-1"
},
"webServiceURL": "https://yourdomain.com/apple/updates",
"authenticationToken": "{{auth_token}}"
}
9.4 pkpass Generation Flow
- Load
loyalty_pass.jsontemplate. - Inject dynamic values (serial, points, stamps, auth token, etc.).
- Add required images (icon, logo, strip).
- Create
manifest.jsonwith SHA-1 hashes of all files. - Sign manifest with your
.p12certificate and Apple’s WWDR cert. - Zip everything into a
.pkpassfile. - Return
.pkpassfrom endpointGET /passes/apple/{pass_id}.
9.5 Device Registration & Updates
Apple Wallet uses a push + pull model for updates:
- Customer adds pass → Apple sends device registration to your backend.
- You store device ID, pass serial, and push token in
AppleDeviceRegistration. - When stamps/points change:
- You send a push notification to Apple using the push token.
- Apple notifies the device; Wallet calls your
webServiceURL. - Device fetches updated pass data from
GET /apple/updates/{pass_type}/{serial}.
9.6 Data Model Additions
PassObject.apple_serialPassObject.apple_auth_tokenAppleDeviceRegistrationtable for device + push tokens.
9.7 What Stays the Same
- Merchant, Store, Customer, PassObject core structure.
- Stamp and points logic.
- QR flows and staff PIN system.
- Dashboard and analytics.
10. Multi-Wallet Strategy
Your platform supports both Google Wallet and Apple Wallet by treating them as two output formats for the same underlying PassObject.
- Google: store
google_pass_id, update via API. - Apple: store
apple_serialandapple_auth_token, update via push + web service.
Whenever stamps or points change, you:
- Patch the Google Wallet pass (if it exists).
- Trigger Apple Wallet update (if Apple pass exists).
The merchant and store don’t need to care about the technical differences—your backend abstracts it away.
11. Deployment & Stack
- Backend: FastAPI (Python).
- Database: PostgreSQL (or MySQL), with migrations.
- Caching / rate limiting: Redis (optional but recommended).
- Web server: Nginx or similar reverse proxy.
- Containerization: Docker for reproducible deployments.
- Security: HTTPS via Cloudflare or Let’s Encrypt.
- Cloud: Any provider; Google Cloud is convenient for Wallet API.
12. MVP vs Future Phases
12.1 MVP Scope
- Merchant + Store management.
- Customer + PassObject models.
- Stamp-based loyalty with staff PIN.
- Points-based loyalty for clothing stores.
- Google Wallet integration.
- Apple Wallet integration (basic, without advanced segmentation).
- Basic dashboard: customers, stamps, points, rewards.
- Email collection with consent.
12.2 Phase 2 Ideas
- Tiers (Bronze/Silver/Gold).
- Referrals and invite links.
- Coupons, vouchers, and gift cards.
- Gamification (spin-the-wheel, scratch cards).
- Receipt scanning and POS integration.
- Paid memberships / VIP programs.
13. Closing Notes
The key design choice is that you do not redesign the system when adding:
- Points on top of stamps.
- Apple Wallet on top of Google Wallet.
- New reward types (tiers, referrals, coupons).
You extend the same core models and flows with additional modules and fields, keeping the architecture modular and future-proof.