GuidesCustomerRewards

Rewards

Rewards are redeemable items in the loyalty program catalog, gated by star cost and tier.

Reward Catalog

RewardStarsMin TierType
Free Cookie25Greenfree_food
Free Brewed Coffee50Greenfree_drink
Free Handcrafted Drink100Greenfree_drink
Free Breakfast Item150Goldfree_food
Free Merchandise400Platinumfree_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 reward
  • percentage — Discount percentage (for discount_percent)
  • amount — Fixed discount amount (for discount_amount)

Redemption Flow

  1. Customer opens Rewards tab
  2. Sees available rewards filtered by their tier
  3. Rewards they can’t afford (insufficient stars) are shown but disabled
  4. Taps “Redeem” on a reward
  5. LoyaltyService.redeemReward():
    • Verifies sufficient starsBalance
    • Verifies tier requirement
    • Creates RewardRedemption record with status pending
    • Deducts stars from starsBalance
    • Records redeemed transaction in star_transactions
  6. 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
}