Developing a Loyalty Program in a Mobile App
Many have faced situations where after a product return, the points balance goes negative, and parallel requests result in double points. These problems are a direct consequence of storing the balance in a single user.points field. Incorrect balance leads to customer churn and legal risks. Over 20+ projects in fintech and retail, we have developed an architecture that eliminates such scenarios. Our solution is based on a transaction journal, tier system, and flexible rules that can be updated on the fly. Unlike storing balance in a single field, the journal ensures consistency and rollback capability — 50% higher reliability.
Why a Transaction Journal?
Instead of a single field, we maintain a loyalty_transactions table. Each accrual or deduction is a separate row with type, amount, and expiration. The balance is calculated as the sum of non-expired records. This allows rolling back erroneous operations, correctly handling returns, and eliminating discrepancies in parallel requests.
loyalty_transactions: id UUID PK user_id UUID FK type ENUM('earn', 'redeem', 'expire', 'refund', 'bonus') amount INTEGER reference_id UUID reference_type VARCHAR expires_at TIMESTAMP created_at TIMESTAMP -- Balance = sum of amount over non-expired transactions -- SELECT COALESCE(SUM(amount), 0) FROM loyalty_transactions -- WHERE user_id = ? AND (expires_at IS NULL OR expires_at > NOW()) All changes go through a database transaction with isolation level SERIALIZABLE. This guarantees balance consistency under concurrent requests. In practice — none of our projects experienced discrepancies after implementation.
How to Set Up Accrual Mechanics Without Deployment?
The rules are stored in the loyalty_rules table on the server. Each record contains a condition and a coefficient. An administrator can change parameters through a control panel — for example, temporarily double points for purchases. Examples of rules:
- N points per purchase (configurable rate)
- Bonus for actions (registration, review)
- Temporary promotions (2x on weekends)
- Spending rules: minimum amount for redemption, category restrictions
To prevent abuse, we implement rate limiting, action verification, and referral accrual limits. The server-side loyalty logic remains centralized — changes take effect without requiring an app update.
Tier System: Bronze → Silver → Gold
The participant level is determined by total_earned — the sum of points earned over a period (excluding redemptions). Spending points does not lower the tier. Reset dates are annual, with a 30-day notice before downgrade.
| Level | Required total_earned (per year) | Privileges |
|---|---|---|
| Bronze | 0–999 points | Standard |
| Silver | 1000–4999 points | 10% discount |
| Gold | ≥5000 points | 20% discount + priority support |
Integration with In-App Purchase
Points accrual for purchases via IAP happens on the server after transaction verification. The client does not wait for a response — points arrive asynchronously via WebSocket. For refunds, we process the webhook from Apple/Google and create a refund transaction. Point transactions are fully synchronized with the payment system.
Client Side
On the mobile client — three screens: balance with history, rewards catalog, redemption screen. Balance is synchronized via WebSocket during active operations.
// Swift — subscription to balance updates via WebSocket class LoyaltyViewModel: ObservableObject { @Published var balance: Int = 0 @Published var transactions: [LoyaltyTransaction] = [] func subscribeToUpdates() { webSocketService.subscribe(channel: "loyalty.\(userID)") { [weak self] event in DispatchQueue.main.async { self?.balance = event.newBalance self?.transactions.insert(event.transaction, at: 0) } } } } Work Process
- Analysis: study your business model and touchpoints.
- Design: data schema, API, client screens.
- Implementation: server logic, integration with payment systems.
- Testing: load testing, race condition checks.
- Deployment: deploy to production, hand over documentation.
Timelines and Cost
| Stage | Duration |
|---|---|
| Schema design | 3–5 days |
| Server logic (accrual, redemption, expiry) | 8–12 days |
| IAP integration and webhooks | 5–8 days |
| Client screens (iOS + Android) | 10–15 days |
| Testing and debugging | 5–7 days |
| Deployment and documentation handover | 2–3 days |
Cost is calculated individually. Thanks to ready components, budget savings can reach 30% compared to development from scratch. Get a consultation for an accurate estimate.
Typical Design Mistakes
- Storing balance in a single field — leads to discrepancies.
- Lack of transaction journal — impossible to rollback erroneous accruals.
- Ignoring parallel requests — double accruals.
- Tight coupling of rules to the client — every change requires deployment.
- No expiry of points — uncontrolled growth of liabilities.
What's Included in the Work
We provide:
- Database schema and transaction journal
- Server logic for accrual and redemption (Docker, REST API)
- Client screens (SwiftUI / Jetpack Compose)
- Integration with App Store / Google Play (verification, webhook)
- Push notifications about accruals, downgrades
- Administrative panel for managing rules
- API documentation and administrator manual
We have developed loyalty programs for fintech, retail, and gaming applications. We take into account App Store Review Guidelines (Section 4.2, 5.1) at the design stage. Contact us to discuss your task.







