Skip to main content
LOYALTI — cover

Избранное

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 Purchase200purchase, 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.