Files
orion/loyatly-ideas.html
Samir Boulahtit eeafe6389f fix: resolve all remaining legacy import issues
- Update models/database/__init__.py to import from module locations
- Update models/schema/__init__.py to remove deleted modules
- Update models/__init__.py to import Inventory from module
- Remove duplicate AdminNotification from models/database/admin.py
- Fix monitoring module to import AdminNotification from messaging
- Update stats schema imports in admin/vendor API
- Update notification schema imports
- Add order_item_exception.py schema to orders module
- Fix app/api/v1/__init__.py to use storefront instead of shop
- Add cms_admin_pages import to main.py
- Fix password_reset_token imports
- Fix AdminNotification test imports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:21:29 +01:00

679 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Loyalty Platform Architecture Google Wallet & Apple Wallet</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
background: #0b1020;
color: #f5f5f5;
}
header {
background: linear-gradient(135deg, #1f2937, #111827);
padding: 2rem 1.5rem;
border-bottom: 1px solid #374151;
}
header h1 {
margin: 0 0 0.5rem 0;
font-size: 1.8rem;
}
header p {
margin: 0;
color: #9ca3af;
}
main {
max-width: 960px;
margin: 0 auto;
padding: 1.5rem;
}
h2, h3, h4 {
color: #e5e7eb;
margin-top: 2rem;
}
h2 {
border-bottom: 1px solid #374151;
padding-bottom: 0.25rem;
}
code {
background: #111827;
padding: 0.15rem 0.35rem;
border-radius: 4px;
font-size: 0.9em;
}
pre {
background: #111827;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
font-size: 0.9em;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
font-size: 0.95em;
}
th, td {
border: 1px solid #374151;
padding: 0.5rem 0.75rem;
text-align: left;
}
th {
background: #111827;
}
a {
color: #60a5fa;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.tag {
display: inline-block;
padding: 0.1rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
background: #111827;
color: #9ca3af;
margin-right: 0.25rem;
}
.note {
border-left: 3px solid #3b82f6;
padding-left: 0.75rem;
margin: 1rem 0;
color: #d1d5db;
}
ul {
padding-left: 1.25rem;
}
</style>
</head>
<body>
<header>
<h1>Loyalty Platform Architecture</h1>
<p>Digital stamp & points system with Google Wallet and Apple Wallet support</p>
</header>
<main>
<section id="overview">
<h2>1. High-Level Overview</h2>
<p>
This document describes a modular loyalty platform that supports:
</p>
<ul>
<li><strong>Stamp-based loyalty</strong> (e.g., coffee shops, barbers)</li>
<li><strong>Points-based loyalty</strong> (e.g., clothing stores, restaurants)</li>
<li><strong>Google Wallet passes</strong> (JSON + API)</li>
<li><strong>Apple Wallet passes</strong> (<code>.pkpass</code> files)</li>
<li><strong>Staff PIN system</strong> for fraud prevention</li>
<li><strong>QR-based flows</strong> for visits and purchases</li>
<li><strong>Email collection</strong> and basic CRM</li>
</ul>
<p>
The architecture is designed to be extensible: you can add tiers, referrals, coupons, gift cards, and more without redesigning the core.
</p>
</section>
<section id="architecture">
<h2>2. Backend Architecture</h2>
<h3>2.1 Core Modules</h3>
<ul>
<li><strong>Auth & Merchant Management:</strong> merchants, stores, staff PINs, billing hooks.</li>
<li><strong>Customer & Pass Management:</strong> customers, pass objects, Wallet IDs.</li>
<li><strong>Loyalty Engine:</strong> stamp logic and points logic.</li>
<li><strong>QR Engine:</strong> static QR codes per store, scan endpoints.</li>
<li><strong>Dashboard API:</strong> analytics, customer lists, rewards, exports.</li>
<li><strong>Wallet Integrations:</strong> Google Wallet and Apple Wallet modules.</li>
</ul>
<h3>2.2 Folder Structure</h3>
<pre><code>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</code></pre>
</section>
<section id="data-model">
<h2>3. Data Model</h2>
<h3>3.1 Merchant vs Store</h3>
<p>
<span class="tag">Merchant</span> = business entity (e.g., “CoffeeLux”)
<span class="tag">Store</span> = specific location/branch (e.g., “CoffeeLux Gare”).
</p>
<h4>Merchant</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>name</td><td>string</td></tr>
<tr><td>email</td><td>string</td></tr>
<tr><td>password_hash</td><td>string</td></tr>
<tr><td>created_at</td><td>datetime</td></tr>
</table>
<h4>Store</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>merchant_id</td><td>FK → Merchant</td></tr>
<tr><td>name</td><td>string</td></tr>
<tr><td>qr_code_url</td><td>string</td></tr>
<tr><td>loyalty_type</td><td><code>"stamps"</code> | <code>"points"</code> | <code>"hybrid"</code></td></tr>
<tr><td>staff_pin_hash</td><td>string (hashed)</td></tr>
</table>
<h4>Customer</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>email</td><td>string (nullable)</td></tr>
<tr><td>phone</td><td>string (nullable)</td></tr>
<tr><td>email_consent</td><td>bool</td></tr>
<tr><td>email_consent_at</td><td>datetime (nullable)</td></tr>
<tr><td>created_at</td><td>datetime</td></tr>
</table>
<h4>PassObject</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>customer_id</td><td>FK → Customer</td></tr>
<tr><td>store_id</td><td>FK → Store</td></tr>
<tr><td>google_pass_id</td><td>string (nullable)</td></tr>
<tr><td>apple_serial</td><td>string (nullable)</td></tr>
<tr><td>apple_auth_token</td><td>string (nullable)</td></tr>
<tr><td>stamp_count</td><td>int</td></tr>
<tr><td>points_balance</td><td>int</td></tr>
<tr><td>reward_unlocked</td><td>bool</td></tr>
<tr><td>last_stamp_at</td><td>datetime (nullable)</td></tr>
<tr><td>last_points_at</td><td>datetime (nullable)</td></tr>
</table>
<h4>StampEvent</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>pass_id</td><td>FK → PassObject</td></tr>
<tr><td>store_id</td><td>FK → Store</td></tr>
<tr><td>timestamp</td><td>datetime</td></tr>
</table>
<h4>PointsEvent</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>pass_id</td><td>FK → PassObject</td></tr>
<tr><td>store_id</td><td>FK → Store</td></tr>
<tr><td>points_added</td><td>int</td></tr>
<tr><td>amount</td><td>decimal (optional)</td></tr>
<tr><td>timestamp</td><td>datetime</td></tr>
</table>
<h4>AppleDeviceRegistration</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>device_id</td><td>string</td></tr>
<tr><td>pass_serial</td><td>string</td></tr>
<tr><td>push_token</td><td>string</td></tr>
<tr><td>updated_at</td><td>datetime</td></tr>
</table>
</section>
<section id="flows">
<h2>4. Core Flows</h2>
<h3>4.1 Creating a Pass (Onboarding)</h3>
<ol>
<li>Customer scans a QR code or visits a link.</li>
<li>Page asks for optional data:
<ul>
<li>Name (optional)</li>
<li>Email (optional, with consent checkbox)</li>
<li>Phone (optional)</li>
</ul>
</li>
<li>Backend:
<ul>
<li>Creates or finds Customer.</li>
<li>Creates PassObject for the chosen store.</li>
<li>Generates Google Wallet link and/or Apple Wallet <code>.pkpass</code>.</li>
</ul>
</li>
<li>Customer taps “Add to Google Wallet” or “Add to Apple Wallet”.</li>
</ol>
<h3>4.2 Stamping a Pass (Visits)</h3>
<p><strong>Endpoint:</strong> <code>POST /stamp/{store_id}</code></p>
<ol>
<li>Customer scans store QR: <code>https://yourdomain.com/stamp/{store_id}</code>.</li>
<li>Page identifies the customers pass (via login, token, or Wallet ID).</li>
<li>Staff enters a PIN on the customers device.</li>
<li>Backend:
<ul>
<li>Validates staff PIN.</li>
<li>Checks cooldown (e.g., 1 stamp per X minutes).</li>
<li>Increments <code>stamp_count</code>.</li>
<li>Logs a StampEvent.</li>
<li>Updates Google Wallet and Apple Wallet passes.</li>
</ul>
</li>
<li>Customer sees updated stamp count.</li>
</ol>
<h3>4.3 Adding Points (Purchases)</h3>
<p><strong>Endpoint:</strong> <code>POST /points/{store_id}</code></p>
<pre><code>{
"pass_id": "abc123",
"amount": 59.90,
"staff_pin": "4821"
}</code></pre>
<ol>
<li>Staff enters purchase amount and PIN.</li>
<li>Backend:
<ul>
<li>Validates staff PIN.</li>
<li>Converts amount → points (e.g., €1 = 1 point).</li>
<li>Updates <code>points_balance</code>.</li>
<li>Logs a PointsEvent.</li>
<li>Updates Wallet passes.</li>
</ul>
</li>
</ol>
<h3>4.4 Reward Logic</h3>
<p>
Implemented in <code>loyalty_engine/stamps.py</code> and <code>loyalty_engine/points.py</code>.
</p>
<ul>
<li><strong>Stamps:</strong> reward after N stamps.</li>
<li><strong>Points:</strong> reward at thresholds (e.g., 100, 250, 500 points).</li>
<li><strong>Hybrid:</strong> store can use both stamps and points.</li>
</ul>
</section>
<section id="staff-pin">
<h2>5. Staff PIN System & Anti-Fraud</h2>
<h3>5.1 Staff PIN Concept</h3>
<p>
Each store has one or more staff PINs. A stamp or points addition is only valid if a correct PIN is provided.
</p>
<p><strong>Example request:</strong></p>
<pre><code>POST /stamp/{store_id}
{
"pass_id": "abc123",
"staff_pin": "4821"
}</code></pre>
<h3>5.2 Implementation</h3>
<ul>
<li><strong>Store table:</strong> <code>staff_pin_hash</code> (hashed with bcrypt/argon2).</li>
<li>Staff enters PIN on customers device after a purchase/visit.</li>
<li>Backend compares provided PIN with hash.</li>
</ul>
<h3>5.3 Additional Anti-Fraud Layers</h3>
<ul>
<li><strong>Cooldown:</strong> 1 stamp per X minutes per pass.</li>
<li><strong>Device fingerprinting:</strong> optional (IP, user agent, cookies).</li>
<li><strong>Reward state checks:</strong> dont stamp a full card.</li>
<li><strong>Staff PIN rotation:</strong> merchants can change PIN periodically.</li>
<li><strong>Attempt limits:</strong> block after N wrong PIN attempts.</li>
</ul>
<div class="note">
The QR code itself never directly grants a stamp or points. It only opens a URL; the backend decides whether to award anything.
</div>
</section>
<section id="email">
<h2>6. Email Collection & GDPR Considerations</h2>
<h3>6.1 When to Collect Email</h3>
<ul>
<li><strong>During pass creation:</strong> best moment, right after QR scan.</li>
<li><strong>On reward redemption:</strong> “Want your next reward reminder by email?”</li>
<li><strong>On progress view:</strong> small banner: “Add your email to get reward reminders.”</li>
</ul>
<h3>6.2 Data Model</h3>
<ul>
<li><code>Customer.email</code></li>
<li><code>Customer.email_consent</code></li>
<li><code>Customer.email_consent_at</code></li>
</ul>
<h3>6.3 GDPR-Friendly Flow (EU/Luxembourg)</h3>
<ul>
<li>Use an explicit checkbox:
<br><em>“I agree to receive loyalty updates and promotions.”</em>
</li>
<li>Do not pre-check the box.</li>
<li>Store consent timestamp.</li>
<li>Provide unsubscribe link in emails.</li>
</ul>
<h3>6.4 Usage</h3>
<ul>
<li>Reward notifications (“Your reward is ready”).</li>
<li>Progress nudges (“Youre 1 stamp away”).</li>
<li>Promotions and new collection announcements.</li>
<li>Birthday offers (if DOB is collected).</li>
</ul>
</section>
<section id="features">
<h2>7. Features Beyond Stamps & Points</h2>
<h3>7.1 Reward Types</h3>
<ul>
<li><strong>Tiered loyalty levels:</strong> Bronze, Silver, Gold based on spend or visits.</li>
<li><strong>Cashback / store credit:</strong> earn balance redeemable in-store.</li>
<li><strong>Birthday rewards:</strong> automatic bonuses around customers birthday.</li>
<li><strong>Referral rewards:</strong> invite friends, both get points or discounts.</li>
<li><strong>Hybrid models:</strong> stamps + points + referrals.</li>
</ul>
<h3>7.2 Coupons, Vouchers, Gift Cards</h3>
<ul>
<li>Digital coupons (percentage or fixed amount).</li>
<li>Digital gift cards with balance.</li>
<li>Time-limited offers (weekend-only, seasonal).</li>
</ul>
<h3>7.3 Marketing & Communication</h3>
<ul>
<li>Email campaigns.</li>
<li>SMS campaigns.</li>
<li>Wallet-based notifications (pass updates, messages).</li>
</ul>
<h3>7.4 Analytics & CRM</h3>
<ul>
<li>Visit frequency, average spend, redemption rates.</li>
<li>Customer lifetime value.</li>
<li>New vs returning customers.</li>
<li>Store-level and staff-level performance.</li>
<li>Basic CRM: customer profiles, segmentation.</li>
</ul>
<h3>7.5 Advanced Options</h3>
<ul>
<li>Receipt scanning with OCR.</li>
<li>POS integration (automatic points and redemptions).</li>
<li>Gamification (spin-the-wheel, scratch cards, challenges).</li>
<li>Paid memberships / VIP subscriptions.</li>
</ul>
</section>
<section id="google-wallet">
<h2>8. Google Wallet Integration</h2>
<h3>8.1 Core Concepts</h3>
<ul>
<li>Google Wallet uses <strong>Passes API</strong> with JSON-based classes and objects.</li>
<li>You define a loyalty class and create pass objects per customer.</li>
<li>Updates are done via API calls (patching fields like points, stamps, messages).</li>
</ul>
<h3>8.2 Required Setup</h3>
<ul>
<li>Google Cloud project.</li>
<li>Wallet API enabled.</li>
<li>Service account with JSON key.</li>
<li>Issuer ID and loyalty class definition.</li>
</ul>
<h3>8.3 Backend Responsibilities</h3>
<ul>
<li>Create pass objects for new customers.</li>
<li>Store <code>google_pass_id</code> in PassObject.</li>
<li>Update pass when stamps/points change.</li>
<li>Optionally send messages or promotions via pass updates.</li>
</ul>
<h3>8.4 Example Endpoint Sketch</h3>
<pre><code>@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}</code></pre>
</section>
<section id="apple-wallet">
<h2>9. Apple Wallet Integration</h2>
<h3>9.1 Core Difference</h3>
<p>
Apple Wallet uses <strong>signed <code>.pkpass</code> files</strong> instead of a JSON API.
A <code>.pkpass</code> is a ZIP containing:
</p>
<ul>
<li><code>pass.json</code> pass data</li>
<li>Images logo, icon, strip</li>
<li><code>manifest.json</code> SHA-1 hashes of all files</li>
<li><code>signature</code> signed manifest using your certificate</li>
</ul>
<h3>9.2 Requirements</h3>
<ul>
<li>Apple Developer Program membership.</li>
<li>Pass Type ID (e.g., <code>pass.com.yourbrand.loyalty</code>).</li>
<li>Pass certificate (<code>.p12</code>).</li>
<li>Push notification certificate for Wallet updates.</li>
</ul>
<h3>9.3 Pass Template (pass.json)</h3>
<pre><code>{
"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}}"
}</code></pre>
<h3>9.4 pkpass Generation Flow</h3>
<ol>
<li>Load <code>loyalty_pass.json</code> template.</li>
<li>Inject dynamic values (serial, points, stamps, auth token, etc.).</li>
<li>Add required images (icon, logo, strip).</li>
<li>Create <code>manifest.json</code> with SHA-1 hashes of all files.</li>
<li>Sign manifest with your <code>.p12</code> certificate and Apples WWDR cert.</li>
<li>Zip everything into a <code>.pkpass</code> file.</li>
<li>Return <code>.pkpass</code> from endpoint <code>GET /passes/apple/{pass_id}</code>.</li>
</ol>
<h3>9.5 Device Registration & Updates</h3>
<p>Apple Wallet uses a push + pull model for updates:</p>
<ol>
<li>Customer adds pass → Apple sends device registration to your backend.</li>
<li>You store device ID, pass serial, and push token in <code>AppleDeviceRegistration</code>.</li>
<li>When stamps/points change:
<ul>
<li>You send a push notification to Apple using the push token.</li>
<li>Apple notifies the device; Wallet calls your <code>webServiceURL</code>.</li>
<li>Device fetches updated pass data from <code>GET /apple/updates/{pass_type}/{serial}</code>.</li>
</ul>
</li>
</ol>
<h3>9.6 Data Model Additions</h3>
<ul>
<li><code>PassObject.apple_serial</code></li>
<li><code>PassObject.apple_auth_token</code></li>
<li><code>AppleDeviceRegistration</code> table for device + push tokens.</li>
</ul>
<h3>9.7 What Stays the Same</h3>
<ul>
<li>Merchant, Store, Customer, PassObject core structure.</li>
<li>Stamp and points logic.</li>
<li>QR flows and staff PIN system.</li>
<li>Dashboard and analytics.</li>
</ul>
</section>
<section id="multi-wallet">
<h2>10. Multi-Wallet Strategy</h2>
<p>
Your platform supports both Google Wallet and Apple Wallet by treating them as two output formats for the same underlying PassObject.
</p>
<ul>
<li><strong>Google:</strong> store <code>google_pass_id</code>, update via API.</li>
<li><strong>Apple:</strong> store <code>apple_serial</code> and <code>apple_auth_token</code>, update via push + web service.</li>
</ul>
<p>
Whenever stamps or points change, you:
</p>
<ul>
<li>Patch the Google Wallet pass (if it exists).</li>
<li>Trigger Apple Wallet update (if Apple pass exists).</li>
</ul>
<p>
The merchant and store dont need to care about the technical differences—your backend abstracts it away.
</p>
</section>
<section id="deployment">
<h2>11. Deployment & Stack</h2>
<ul>
<li><strong>Backend:</strong> FastAPI (Python).</li>
<li><strong>Database:</strong> PostgreSQL (or MySQL), with migrations.</li>
<li><strong>Caching / rate limiting:</strong> Redis (optional but recommended).</li>
<li><strong>Web server:</strong> Nginx or similar reverse proxy.</li>
<li><strong>Containerization:</strong> Docker for reproducible deployments.</li>
<li><strong>Security:</strong> HTTPS via Cloudflare or Lets Encrypt.</li>
<li><strong>Cloud:</strong> Any provider; Google Cloud is convenient for Wallet API.</li>
</ul>
</section>
<section id="mvp">
<h2>12. MVP vs Future Phases</h2>
<h3>12.1 MVP Scope</h3>
<ul>
<li>Merchant + Store management.</li>
<li>Customer + PassObject models.</li>
<li>Stamp-based loyalty with staff PIN.</li>
<li>Points-based loyalty for clothing stores.</li>
<li>Google Wallet integration.</li>
<li>Apple Wallet integration (basic, without advanced segmentation).</li>
<li>Basic dashboard: customers, stamps, points, rewards.</li>
<li>Email collection with consent.</li>
</ul>
<h3>12.2 Phase 2 Ideas</h3>
<ul>
<li>Tiers (Bronze/Silver/Gold).</li>
<li>Referrals and invite links.</li>
<li>Coupons, vouchers, and gift cards.</li>
<li>Gamification (spin-the-wheel, scratch cards).</li>
<li>Receipt scanning and POS integration.</li>
<li>Paid memberships / VIP programs.</li>
</ul>
</section>
<section id="closing">
<h2>13. Closing Notes</h2>
<p>
The key design choice is that you do <strong>not</strong> redesign the system when adding:
</p>
<ul>
<li>Points on top of stamps.</li>
<li>Apple Wallet on top of Google Wallet.</li>
<li>New reward types (tiers, referrals, coupons).</li>
</ul>
<p>
You extend the same core models and flows with additional modules and fields, keeping the architecture modular and future-proof.
</p>
</section>
</main>
</body>
</html>