Rewards
Rewards are redeemable items in the loyalty program catalog, gated by star cost and tier.
Reward Catalog
| Reward | Stars | Min Tier | Type |
|---|---|---|---|
| Free Cookie | 25 | Green | free_food |
| Free Brewed Coffee | 50 | Green | free_drink |
| Free Handcrafted Drink | 100 | Green | free_drink |
| Free Breakfast Item | 150 | Gold | free_food |
| Free Merchandise | 400 | Platinum | free_food |
Reward Types
enum RewardType: String, Codable {
case freeDrink = "free_drink"
case freeFood = "free_food"
case discountPercent = "discount_percent"
case discountAmount = "discount_amount"
}Each reward can have a RewardValue with:
maxValue— Maximum dollar value for the rewardpercentage— Discount percentage (fordiscount_percent)amount— Fixed discount amount (fordiscount_amount)
Redemption Flow
- Customer opens Rewards tab
- Sees available rewards filtered by their tier
- Rewards they can’t afford (insufficient stars) are shown but disabled
- Taps “Redeem” on a reward
LoyaltyService.redeemReward():- Verifies sufficient
starsBalance - Verifies tier requirement
- Creates
RewardRedemptionrecord with statuspending - Deducts stars from
starsBalance - Records
redeemedtransaction instar_transactions
- Verifies sufficient
- Returns updated balance
Tier Gating
Rewards are filtered by the canAccessReward function:
private func canAccessReward(_ reward: Reward, forTier tier: LoyaltyTier) -> Bool {
let tierOrder: [LoyaltyTier] = [.green, .gold, .platinum]
guard let currentIndex = tierOrder.firstIndex(of: tier),
let requiredIndex = tierOrder.firstIndex(of: reward.minTier) else {
return false
}
return currentIndex >= requiredIndex
}A Gold tier user can access Green and Gold rewards, but not Platinum rewards.
Redemption Statuses
enum RedemptionStatus: String, Codable {
case pending // Redeemed but not yet applied to an order
case applied // Applied to a specific order
case cancelled // Redemption was cancelled
}