# Хуки и события

В Celena есть две системы расширения: фильтры (`Hook::filter`) и действия (`Hook::action`). Плагин Shop подписывается на одни и диспатчит свои.

## Хуки, на которые плагин подписывается

| Имя | Тип | Зачем |
|---|---|---|
| `template.globals` | filter | Прокидывает все настройки магазина в шаблоны (`{shop.currency_symbol}`, `{shop.store_name}` и т.д.) и флаг `{shop.enabled}="1"`. |
| `admin.sidebar` | filter | Добавляет пункт «Магазин» с подменю (Товары / Производители / Атрибуты / Заказы / Клиенты / Доставка / Города / Оплата / Промокоды / Отзывы / Аналитика / Импорт CSV / Настройки). |
| `admin.notifications` | filter | Добавляет в колокольчик уведомления о новых заказах (`status='new'`). |
| `admin.dashboard.sections` | filter | Добавляет виджет «Витрина магазина» на главный дашборд. |
| `customfields.entities` | filter | Регистрирует сущность `shop_product` для модуля «Доп. поля». |
| `categories.entities` | filter | Регистрирует сущность `shop_product` для модуля «Категории». |
| `category.deleted` | action | Отвязывает товары от удалённой категории (без каскадного удаления самих товаров). |
| `search.sources` | filter | Добавляет товары в общую выдачу `/search`. |

Подписки находятся в `plugins/shop/plugin.php`. Если плагин **деактивирован** — подписки не выполняются: пункт меню исчезает, колокольчик не считает заказы, в селекторе «Применяется к» нет «Товары (магазин)», в общем поиске товары не находятся.

## Inline-теги, которые регистрирует плагин

| Тег | Что |
|---|---|
| `{price amount="1234.5"}` | Форматирует число согласно настройкам валюты: `$ 1,234.50` |
| `{currency}` | Возвращает символ текущей валюты: `$`, `€`, `₴` |
| `[products limit="6" sort="price"]…[/products]` | Блок-тег для вывода товаров в темах (по аналогии с `[news]` и `[categories]`) |

## События, которые плагин диспатчит

Любой плагин или модуль может подписаться через `Hook::addAction(…)` или `Hook::addFilter(…)`.

### `shop.order.created` — action

Срабатывает после успешного оформления заказа (после insert в `shop_orders`).

**Аргумент:**

```php
[
  'id'          => 42,
  'number'      => 'ORD-1A2B3C4D',
  'total'       => 1280.50,
  'email'       => 'customer@example.com',
  'name'        => 'Іван Петренко',
  'items_count' => 3,
  'source'      => 'web',     // или 'admin' для ручного создания
]
```

**Пример** — отправить email клиенту:

```php
Hook::on('shop.order.created', function (array $order) {
    $mailer = Kernel::container()->make(Mailer::class);
    $mailer->send($order['email'], 'Ваш заказ ' . $order['number'], '...');
});
```

### `shop.order.status_changed` — action

Срабатывает при смене статуса заказа в `Заказы → карточка → выбрать статус`.

**Аргумент:**

```php
[
  'id'     => 42,
  'number' => 'ORD-1A2B3C4D',
  'from'   => 'new',
  'to'     => 'paid',
  'email'  => 'customer@example.com',
]
```

**Пример** — отправлять webhook каждый раз при оплате:

```php
Hook::on('shop.order.status_changed', function (array $e) {
    if ($e['to'] !== 'paid') return;
    file_get_contents('https://your-erp.example/api/orders?paid=' . $e['number']);
});
```

## Свои AI-задачи

Плагин не привязывает свои AI-задачи, но кнопка «Заполнить SEO через ИИ» в карточке товара использует ядерную задачу `generate_meta`. Можно зарегистрировать свою:

```php
Kernel::container()->make(AiManager::class)->registerTask(
    'shop_product_description',
    function (array $args) use ($provider) {
        return $provider->complete(
            system: 'Сгенерируй продающее описание товара на 3-4 абзаца.',
            user: 'Название: ' . ($args['title'] ?? ''),
            opts: ['max_tokens' => 500],
        );
    }
);
```

После этого можно поставить кнопку `<button class="ai-btn" data-ai-task="shop_product_description" data-ai-target="[name=body]">…</button>` в форму товара (через оверрайд `products_form.tpl` в теме).

## Чек-лист «как добавить свою функциональность»

1. **Уведомление по email при заказе** — подписаться на `shop.order.created`.
2. **Свой статус заказа** (например, `refunded_request`) — пока статусы хардкодятся в `Support\OrderStatuses::STATUSES`. Это требует правки плагина, в будущей версии сделаем через хук `shop.order.statuses`.
3. **Своё поле товара** — `Доп. поля → Создать`, выбрать сущность «Товары (магазин)». Не нужно править плагин.
4. **Платёжный шлюз** (Stripe, PayPal, LiqPay, Kaspi…) — отдельный плагин, который добавляет `kind=online_gateway`-метод оплаты, подписывается на фильтр `shop.checkout.redirect` и отмечает оплату через `Support\OrderPayments` (см. раздел «Платёжные шлюзы» ниже).
5. **Email-рассылка по клиентам** — отдельный плагин, читающий `shop_customers` с фильтром `marketing_opt_in = true`.

## Платёжные шлюзы (с версии 1.2.0)

Шлюз — отдельный плагин. Ему нужны три вещи:

1. **Способ оплаты** в `shop_payment_methods` с `kind='online_gateway'` и своим `slug` (например `stripe`). Плагин создаёт строку в своей миграции, если её ещё нет; администратор может править название/описание в «Оплата».
2. **Фильтр `shop.checkout.redirect`** — вызывается сразу после создания заказа на витрине. Первый аргумент — URL по умолчанию (`/checkout/thanks/{number}`), второй — массив события (тот же, что у `shop.order.created`: `id, number, total, grand_total, currency, email, name, items_count, source, payment_method_id, payment_method_slug, payment_method_kind`). Если заказ оформлен с вашим способом оплаты — верните URL своей страницы оплаты:

```php
Hook::addFilter('shop.checkout.redirect', function (string $url, array $order): string {
    if (($order['payment_method_slug'] ?? '') !== 'stripe') return $url;
    return '/checkout/pay/stripe/' . $order['number'];
});
```

3. **`Celena\Plugin\Shop\Support\OrderPayments`** — единая точка смены статуса оплаты из обработчика вебхука: `markPaid(int $orderId, string $provider, string $txnId, ?float $amount, string $currency): bool` (идемпотентно; ставит `payment_status=paid`, `paid_at`, статус заказа `new → paid`, пишет историю, диспатчит `shop.order.paid` и `shop.order.status_changed`), `markRefunded(...)`, `note(int $orderId, string $comment)`, `findByNumber(string $number)`.

### `shop.order.paid` — action
`['id','number','email','total','currency','provider','txn_id']` — один раз при переходе заказа в `paid` через шлюз.

### `shop.order.refunded` — action
`['id','number','email','provider','txn_id','amount','currency']`.

Страница «Спасибо» (`plugins/shop/front/thanks`) получает `{order.payment_status}` и `{order.payment_method_title}` — тема может показать «Оплачено» / «Ожидает оплаты».
