Многие сталкивались с ситуацией, когда после возврата товара баланс баллов уходит в минус, а при параллельных запросах начисляется двойное количество баллов. Эти проблемы — прямое следствие хранения баланса в одном поле user.points. Некорректный баланс ведёт к уходу клиентов и юридическим рискам. За 20+ проектов в финтехе и ритейле мы выработали архитектуру, исключающую такие сценарии. Наше решение базируется на транзакционном журнале, tier-системе и гибких правилах, обновляемых на лету. В отличие от хранения баланса в одном поле, журнал обеспечивает консистентность и возможность отката — надёжность на 50% выше.
Почему транзакционный журнал?
Вместо единого поля мы ведём таблицу loyalty_transactions. Каждое начисление или списание — отдельная строка с типом, суммой и сроком действия. Баланс вычисляется как сумма по незаистёкшим записям. Это позволяет откатить ошибочную операцию, корректно обработать возврат и исключить расхождения при параллельных запросах.
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 -- Баланс = сумма amount по незаистёкшим транзакциям -- SELECT COALESCE(SUM(amount), 0) FROM loyalty_transactions -- WHERE user_id = ? AND (expires_at IS NULL OR expires_at > NOW()) Все изменения проходят через транзакцию БД с уровнем изоляции SERIALIZABLE. Это гарантирует консистентность баланса при одновременных запросах. На практике — ни один из наших проектов не имел расхождений после внедрения.
Как настроить механики начисления без деплоя?
Правила хранятся в таблице loyalty_rules на сервере. Каждая запись содержит условие и коэффициент. Администратор может менять параметры через панель управления — например, временно удвоить баллы за покупки. Примеры правил:
- N баллов за покупку (настраиваемый курс)
- Бонус за действие (регистрация, отзыв)
- Временные акции (x2 по выходным)
- Правила списания (spending rules): минимальная сумма для списания, ограничения по категориям
Для предотвращения накруток внедряем rate limiting, верификацию действий и лимиты на реферальные начисления. Серверная логика лояльности остаётся централизованной — изменения вступают в силу без перевыпуска приложения.
Тиерная система: Bronze → Silver → Gold
Уровень участника определяется по total_earned — сумме начисленных баллов за период (без учёта списаний). Трата баллов не понижает тир. Даты сброса — ежегодно, с уведомлением за 30 дней до даунгрейда.
| Уровень | Требуемый total_earned (за год) | Привилегии |
|---|---|---|
| Bronze | 0–999 баллов | Стандартные |
| Silver | 1000–4999 баллов | 10% скидка |
| Gold | ≥5000 баллов | 20% скидка + приоритетная поддержка |
Интеграция с In-App Purchase
Начисление баллов за покупки через IAP происходит на сервере после верификации транзакции. Клиент не ждёт ответа — баллы приходят асинхронно через WebSocket. При refund мы обрабатываем webhook от Apple/Google и создаём refund-транзакцию. Транзакции баллов полностью синхронизированы с платёжной системой.
Клиентская часть
На мобильном клиенте — три экрана: баланс с историей, каталог вознаграждений, экран списания. Баланс синхронизируется через WebSocket при активных операциях.
// Swift — подписка на обновления баланса через 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) } } } } Процесс работы
- Аналитика: изучаем вашу бизнес-модель и точки касания.
- Проектирование: схема данных, API, клиентские экраны.
- Реализация: серверная логика, интеграция с платёжными системами.
- Тестирование: нагрузочное тестирование, проверка гонок.
- Деплой: развёртывание на production, передача документации.
Сроки и стоимость
| Этап | Длительность |
|---|---|
| Проектирование схемы | 3–5 дней |
| Серверная логика (начисление, списание, expiry) | 8–12 дней |
| Интеграция IAP и вебхуки | 5–8 дней |
| Клиентские экраны (iOS + Android) | 10–15 дней |
| Тестирование и отладка | 5–7 дней |
| Деплой и передача документации | 2–3 дня |
Стоимость рассчитывается индивидуально. За счёт готовых компонентов экономия бюджета достигает 30% по сравнению с разработкой с нуля. Получите консультацию для точной оценки.
Типичные ошибки при проектировании
- Хранение баланса в одном поле — приводит к расхождениям.
- Отсутствие журнала транзакций — невозможно откатить ошибочное начисление.
- Игнорирование параллельных запросов — двойные начисления.
- Жёсткая привязка правил к клиенту — каждое изменение требует деплоя.
- Отсутствие expiry баллов — неконтролируемый рост обязательств.
Что входит в работу
Мы предоставляем:
- Схему базы данных и журнал транзакций
- Серверную логику начисления и списания (Docker, REST API)
- Клиентские экраны (SwiftUI / Jetpack Compose)
- Интеграцию с App Store / Google Play (верификация, webhook)
- Push-уведомления о начислениях, даунгрейдах
- Административную панель для управления правилами
- Документацию API и руководство администратора
Мы разрабатывали программы лояльности для финтех-, ритейл- и гейминг-приложений. Учитываем App Store Review Guidelines (Section 4.2, 5.1) на этапе проектирования. Свяжитесь с нами, чтобы обсудить вашу задачу.







