Pricing & promotions
Loyalty Points Program
Indicative price, not an offer: €34/mo; points earning, redemption, tier tiers (VIP), referral bonus
Generated from docs/plan/paid/loyalty-points-program/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
A paid module that awards customers points for purchases, tracks point balances, and allows redemption for discounts or free products. The system tiers points by customer (VIP = 2 points per €1, standard = 1 point per €1), prevents duplicate claims via transaction IDs, and displays point balance and redemption options at checkout.
The problem
A merchant’s repeat customers spend 3–4x more than one-time buyers, but without a loyalty program, they churn to competitors. Manual point tracking is error-prone; merchants need automated point earning, tracking, and redemption to retain high-value customers.
What it does
- Earn points: on order completion, award points = order_total_minor_units / 100 (base rate 1 pt per €1, configurable)
- Customer tiers: standard (1 pt per €1) and VIP (2 pt per €1), selected by merchant in admin
- Store customer balance: customer.loyalty_points_balance (integer), customer.points_history (array of { earned_at, amount, order_id, reason })
- Redeem points: at checkout, customer can ‘spend 500 points = €5 discount’, deduct from balance
- Prevent double-claims: order_id in points_history ensures points are awarded only once per order
- Admin dashboard: list all customers with point balances, tier, and redemption history
- Email notification: send ‘You earned 150 points!’ after purchase, ‘Your balance is now 450 points’
- Audit log: every point earn, redemption, and tier change
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Referral bonuses (see loyalty-points-vip-tiers-rewards paid module for referral logic)
- Expiration rules (points never expire in this module; loyalty-points-vip-tiers-rewards owns expiration if desired)
- Point transfers between customers (fraud risk; not supported)
- Bonus categories (e.g., 3x points on select products; see tier-quantity-pricing for category-based pricing)
Data model
Add loyalty_points_balance to customer table (integer, default 0). Add points_history table: { id, customer_id, points_earned_or_redeemed (signed int, negative = redeemed), reason, order_id, created_at }. Add customer.loyalty_tier (enum: ‘standard’, ‘vip’). Schema migration: add columns to customer table, create points_history table.
API
- GET /account/loyalty/balance — read customer’s current points and earning rate
- POST /account/loyalty/redeem — { points_to_redeem } → deduct from balance, return discount amount
- GET /admin/customers?include=loyalty_balance — list all customers with point balances
- PUT /admin/customers/:id/loyalty — update tier (‘standard’ or ‘vip’)
- GET /admin/loyalty/analytics — show total points earned, redeemed, and customer tier distribution
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Add ‘Loyalty Program’ section in Commerce > Settings. Toggles: ‘Enable loyalty program’, ‘Auto-assign VIP after $X spent’. Point earning rate slider (1–3 points per €1). Redemption ratio slider (500 points = €X discount). Dashboard: total points issued, total redeemed, distribution by customer tier. Customer admin: field to manually adjust points (with reason stored in audit log).
The seam — why this is paid
Paid module owns the points earning and redemption logic, tier management, and support for fraud prevention (duplicate claims). Core owns the customer data model; module extends it with loyalty fields.
Support commitment: points math, redemption SLA, fraud prevention (duplicate claims)
Dependencies
- Customer system (must track loyalty_points_balance)
- Order system (to award points on completion)
- Email system (to notify customers of earned points)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Order totaling €50 (5000 minor units) awards 50 points (50 = 5000 / 100)
- Customer with VIP tier on €50 order earns 100 points (2 points per €1)
- Customer redeems 500 points for €5 discount: balance decreases by 500, order discount = 500
- Attempting to redeem 500 points twice from same order is rejected (order_id in points_history prevents duplication)
- Admin can manually add 100 points to customer; audit log records who, when, and reason
- Email sent after order: ‘You earned 50 loyalty points! Balance: 450’
Risks
If points are stored as floats, redemption math breaks (500.5 points cannot exist). If order_id is not stored in points_history, merchant can claim points multiple times for same order. If tier change is retroactive (VIP points suddenly recalculated for old orders), customer confusion and support burden increase.
Commercial context
| Suggested price | €34/mo; points earning, redemption, tier tiers (VIP), referral bonus |
| Rival anchor | Smile.io: €49-299/mo; Attentive: €500+/mo; custom: €2000+ |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.