Избранное
LOYALTI
Мультитенантная платформа лояльности и бонусов с PremiumBonus-совместимым POS API, типизированными кошельками и targeted-bonus аллокатором.
- PHP 8.4
- Symfony 8
- PostgreSQL 18
- Doctrine
- Bitrix D7
- Docker Swarm
- GitLab CI
- PHPStan L9
Проблема
Программу лояльности легко показать в демо и трудно сделать правильно. Деньги — на пути списания: частичные write-off’ы, targeted-подарки, которые применяются только к части товаров, reservation-холды, которые нельзя потратить дважды, и касса, которая ретраит один запрос три раза по плохому соединению. Ошибись где угодно — либо отдашь деньги дважды, либо откажешь в законной скидке на кассе.
Архитектура
LOYALTI — гексагональный бэкенд на Symfony 8 / PostgreSQL 18. У каждого участника кошелёк с типизированными балансами — gifted, promo, earned — потому что они истекают и списываются по разным правилам. Списание идёт через двухфазный аллокатор:
- Фаза 1 — targeted: сначала гасятся гранты с привязкой к товарам/корзине, с per-item и per-cart лимитами, FIFO по ближайшему сроку, с reservation-локами, чтобы остаток targeted-грантов не сжёг другой поток.
- Фаза 2 — baseline: остаток списывается с общих балансов под tenant-лимитом max-redeem, targeted-траты сверх лимита не учитываются.
POS-поверхность реализует спеку PremiumBonus Purchase200 — purchase, reserve, write-off, reversal — все идемпотентны по external purchase id, так что повторный запрос возвращает исходный результат, а не списывает дважды.
Highlights
- Reservation-flow с TTL-холдами и атомарными
SQL UPDATEдекрементами — никаких lost updates при конкурентных кассах. - Bitrix D7-модуль встраивает платформу в существующий e-commerce: разделы каталога, sync оплаты/доставки и 4-шаговый redeem-виджет.
- Полный GitLab CI/CD на Docker Swarm: lint → PHPUnit (с одноразовыми Postgres/Redis/RabbitMQ) → build →
docker stack deploy.
Результат
721 PHPUnit-тест / 1798 ассертов, PHPStan level 9 чисто, плюс blackbox flow-сьют, который дёргает реальные POS-эндпоинты. Мультитенантность с первого дня — один деплой, много мерчантов.
Уроки
- Код про деньги хочет ключей идемпотентности раньше, чем фич. Контракт по purchase id убрал целый класс double-spend багов.
- Типизированные балансы лучше одного числа. Правила живут в типе, а не в разбросанных
if.