feat(loyalty): implement complete loyalty module MVP
Add stamp-based and points-based loyalty programs for vendors with: Database Models (5 tables): - loyalty_programs: Vendor program configuration - loyalty_cards: Customer cards with stamp/point balances - loyalty_transactions: Immutable audit log - staff_pins: Fraud prevention PINs (bcrypt hashed) - apple_device_registrations: Apple Wallet push tokens Services: - program_service: Program CRUD and statistics - card_service: Customer enrollment and card lookup - stamp_service: Stamp operations with anti-fraud checks - points_service: Points earning and redemption - pin_service: Staff PIN management with lockout - wallet_service: Unified wallet abstraction - google_wallet_service: Google Wallet API integration - apple_wallet_service: Apple Wallet .pkpass generation API Routes: - Admin: /api/v1/admin/loyalty/* (programs list, stats) - Vendor: /api/v1/vendor/loyalty/* (stamp, points, cards, PINs) - Public: /api/v1/loyalty/* (enrollment, Apple Web Service) Anti-Fraud Features: - Staff PIN verification (configurable per program) - Cooldown period between stamps (default 15 min) - Daily stamp limits (default 5/day) - PIN lockout after failed attempts Wallet Integration: - Google Wallet: LoyaltyClass and LoyaltyObject management - Apple Wallet: .pkpass generation with PKCS#7 signing - Apple Web Service endpoints for device registration/updates Also includes: - Alembic migration for all tables with indexes - Localization files (en, fr, de, lu) - Module documentation - Phase 2 interface and user journey plan Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
79
app/modules/loyalty/models/apple_device.py
Normal file
79
app/modules/loyalty/models/apple_device.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# app/modules/loyalty/models/apple_device.py
|
||||
"""
|
||||
Apple device registration database model.
|
||||
|
||||
Tracks devices that have added an Apple Wallet pass for push
|
||||
notification updates when the pass changes.
|
||||
"""
|
||||
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
|
||||
class AppleDeviceRegistration(Base, TimestampMixin):
|
||||
"""
|
||||
Apple Wallet device registration.
|
||||
|
||||
When a user adds a pass to Apple Wallet, the device registers
|
||||
with us to receive push notifications when the pass updates.
|
||||
|
||||
This implements the Apple Wallet Web Service for passbook updates:
|
||||
https://developer.apple.com/documentation/walletpasses/
|
||||
"""
|
||||
|
||||
__tablename__ = "apple_device_registrations"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Card relationship
|
||||
card_id = Column(
|
||||
Integer,
|
||||
ForeignKey("loyalty_cards.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# Device identification
|
||||
device_library_identifier = Column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Unique identifier for the device/library",
|
||||
)
|
||||
|
||||
# Push notification token
|
||||
push_token = Column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="APNs push token for this device",
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# Relationships
|
||||
# =========================================================================
|
||||
card = relationship("LoyaltyCard", back_populates="apple_devices")
|
||||
|
||||
# Indexes - unique constraint on device + card combination
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"idx_apple_device_card",
|
||||
"device_library_identifier",
|
||||
"card_id",
|
||||
unique=True,
|
||||
),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<AppleDeviceRegistration(id={self.id}, "
|
||||
f"device='{self.device_library_identifier[:8]}...', card_id={self.card_id})>"
|
||||
)
|
||||
Reference in New Issue
Block a user