- 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>
679 lines
22 KiB
HTML
679 lines
22 KiB
HTML
<!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 customer’s pass (via login, token, or Wallet ID).</li>
|
||
<li>Staff enters a PIN on the customer’s 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 customer’s 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> don’t 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 (“You’re 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 customer’s 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 Apple’s 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 don’t 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 Let’s 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>
|