💳 pbShop
pbShop — оплата и заказы поверх PageBlocks: тарифы, разовые покупки и подписки.
Это не магазин целиком
Корзины, доставки и складского учёта здесь нет. pbShop закрывает другое — деньги: заказ, оплата, подтверждение, продление. Что именно продаётся, решает сайт, поэтому один и тот же слой обслуживает и тариф доступа, и участие в событии, и услугу.
Требует установленного PageBlocks: компонент опирается на его модели, маршрутизацию, шаблоны и миграции.
⚙️ Возможности
- 💰 Заказы с историей и составом
- 🔁 Подписки с автопродлением
- 🧾 Счета на банковский перевод, с реквизитами покупателя
- 🌍 Несколько валют с фиксированными ценами
- 🔌 Пять платёжных драйверов из коробки и возможность добавить свой
- 📣 События для выдачи доступа на стороне сайта
- 🗂️ Каталог тарифов на фронте
💳 Способы оплаты
| Драйвер | Что это | Автопродление | Возврат через API |
|---|---|---|---|
manual | Офлайн: менеджер подтверждает вручную | Нет | Нет |
bill | Счёт на банковский перевод, PDF или HTML | Нет | Нет |
payanyway | MONETA.Assistant, редирект с колбэком | Нет | Нет |
yoomoney | Кнопка ЮMoney, перевод на кошелёк. Только рубли | Нет | Нет |
maib | Эквайринг maib, прямой API | Да | Да |
Ключи и секреты задаются не в системных настройках, а в самом способе оплаты: pbShop → Справочники → Способы оплаты, поле с настройками в JSON. Так на одном сайте уживаются несколько способов с разными доступами.
Свой драйвер регистрируется в core/App/config/pbshop.php.
Рекуррентные платежи умеет только maib
Остальные драйверы берут деньги один раз. Подписку на них завести можно, но продлеваться она будет только новой ручной оплатой.
🔁 Подписки
Отдельной сущности «подписка» нет: это заказ, у которого продукт имеет период, а сам заказ — срок действия и флаг автопродления. При первой оплате с автопродлением шлюз привязывает карту и возвращает токен, который сохраняется в заказе; продления идут по нему.
Расписание продлений — задача сайта
Компонент даёт методы: OrderService::dueForRenewal() находит заказы, которым скоро истекать, renew() списывает, expire() снимает просроченные. А вот когда их звать, он не решает — крон пишется на стороне сайта.
Настройка pbshop_renew_days_before задаёт, за сколько дней до срока браться за заказ.
📣 События
Главный способ связать оплату с тем, что она открывает. Слушатели регистрируются в site-owned core/App/config/pbshop.php:
| Событие | Когда |
|---|---|
pbShopOrderCreate | Заказ создан |
pbShopOrderPaid | Заказ оплачен |
pbShopOrderExtend | Срок продлён |
pbShopOrderExpire | Срок истёк |
pbShopOrderCancel | Заказ отменён |
pbShopOrderStatus | Сменился статус |
pbShopPaymentCreate, pbShopPaymentSuccess, pbShopPaymentFail, pbShopPaymentRefund | Жизненный цикл платежа |
Доступ выдаёт сайт: компонент сообщает, что оплачено, а что это значит — знает только он.
🗺️ Маршруты
POST /pay/checkout— оформление заказаGET|POST /pay/{method}/result— колбэк платёжной системыGET /pay/form/{uuid}— страница с формой для шлюзов, принимающих только POSTGET /pay/success/{uuid},/pay/fail/{uuid},/pay/pending/{uuid}— возврат клиентаPOST /pay/orders/{id}/cancel— отмена подписки/pay/requisites— реквизиты покупателя
Каталог появляется, если задан префикс маршрута: список категорий, категория, продукт.
Колбэк идёт без проверки CSRF, и так и должно быть
Платёжная система не знает вашего токена, поэтому с /pay/{method}/result защита снята намеренно. Подлинность проверяет сам драйвер — по подписи своего шлюза, — а сверх того компонент сверяет сумму из колбэка с выставленной. Пишете свой драйвер — проверка подписи целиком на вас.
🧮 Деньги
Суммы хранятся целыми числами в минорных единицах: копейках, центах, бань. Никаких дробных типов — 149.99 лежит как 14999.
Цены заданы на каждую валюту отдельно и по курсу не пересчитываются: сколько указали в евро, столько и будет.
🖥️ В менеджере
Раздел pbShop с четырьмя пунктами: Заказы, Платежи, Продукты и Справочники (категории, валюты, статусы, способы оплаты). У платежа есть кнопки подтверждения и возврата — для maib возврат уходит в шлюз по-настоящему.