Описание
Email-уведомления о заказе (customer-channel) уходят на msCustomer.email, а не на email, указанный пользователем в форме этого конкретного заказа. Это проблема для типового сценария «один браузер, несколько заказов от разных людей».
Воспроизведение
- Открыть сайт в приватной вкладке
- Оформить заказ с
email = alice@example.com, дождаться email-уведомления — уходит корректно на alice@example.com
- Не закрывая вкладку (session token тот же) оформить второй заказ, в форме указать
email = bob@example.com
- Ожидание: письмо о втором заказе уходит на
bob@example.com
- Факт: письмо уходит на
alice@example.com
Причина
Controllers/Customer/Customer.php::getOrCreate() находит существующего msCustomer по token (сессионный ID) — и не обновляет email/phone из новых данных формы. Это может быть осознанным решением (чтобы не затирать данные клиента), но оно приводит к багу в отправке уведомлений.
Services/Order/OrderStatusService.php::getCustomerRecipient() (строки 204-262) при сборе recipient для notification-канала смотрит в:
msCustomer.email / .phone (строки 220-228)
modUserProfile как fallback (строки 242-256)
msOrderAddress — самый свежий источник контактов, относящийся к этому заказу — не используется вообще. Email, который пользователь только что ввёл в форму, не попадает в recipient.
Предложение
В OrderStatusService::getCustomerRecipient() поставить msOrderAddress первым источником — как контакты, прикреплённые именно к этому заказу. msCustomer и modUserProfile становятся fallback'ом, когда в адресе пусто (backwards-совместимо для старых заказов, где адрес мог не сохраняться).
Псевдокод:
```php
protected function getCustomerRecipient(msOrder $msOrder): ?array
{
$recipient = ['type' => 'customer', 'email' => null, 'phone' => null, 'telegram_chat_id' => null];
$hasContact = false;
// 1) Order-level address — самые свежие контакты, относящиеся к этому заказу
/** @var msOrderAddress|null \$address */
\$address = \$msOrder->getOne('Address');
if (\$address) {
if (\$email = \$address->get('email')) { \$recipient['email'] = \$email; \$hasContact = true; }
if (\$phone = \$address->get('phone')) { \$recipient['phone'] = \$phone; \$hasContact = true; }
}
// 2) msCustomer — fallback, если в адресе пусто. Плюс всегда для telegram_chat_id/customer.*
\$customer = \$msOrder->getOne('Customer');
if (\$customer) {
\$recipient['customer'] = \$customer->toArray();
if (empty(\$recipient['email']) && \$customer->get('email')) { \$recipient['email'] = \$customer->get('email'); \$hasContact = true; }
if (empty(\$recipient['phone']) && \$customer->get('phone')) { \$recipient['phone'] = \$customer->get('phone'); \$hasContact = true; }
\$ext = \$customer->get('extended');
if (is_array(\$ext) && !empty(\$ext['telegram_chat_id'])) {
\$recipient['telegram_chat_id'] = \$ext['telegram_chat_id'];
\$hasContact = true;
}
}
// 3) modUserProfile — последний fallback для email/phone, без изменений
// ...
return \$hasContact ? \$recipient : null;
}
```
Почему именно такой порядок
- Заказ — это source of truth. Что юзер ввёл на checkout'е — то и контакты уведомления о заказе. Менять email в
msCustomer каждый раз опасно (сломает merge клиентов по email и CRM-интеграции).
- Telegram/SMS логика не ломается.
telegram_chat_id по-прежнему читается только из customer-extended (в адресе его нет — это отдельный канал доставки).
- Manager-recipient не трогаем.
getManagerRecipient() (строки 264+) никак не зависит от заказа, читает системные настройки — остаётся как есть.
Обратная совместимость
- Старые заказы, где в
msOrderAddress email пустой, продолжат работать через fallback на msCustomer — поведение не меняется.
- Для заказов, где email в форме совпадает с email в customer'е — поведение не меняется.
- Изменится только тот самый сценарий, который сейчас багован.
Альтернатива на одном плагине (временное решение, пока не смержено)
Перехват через событие msOnBeforeSendNotification с мутацией $recipient['email'] из $notification->getOrder()->getOne('Address')->get('email'). Работает точечно, не требует правки ядра, не меняет поведение если email в адресе отсутствует.
Затронутые файлы
core/components/minishop3/src/Services/Order/OrderStatusService.php:204-262 (getCustomerRecipient)
Дополнительно
Отдельным вопросом — стоит ли также обновлять msCustomer.email при новой submission (merge стратегия). Но это другой уровень решения и зависит от бизнес-правил (привязка клиента к единственному email vs. история контактов). Пока — ограничиваемся корректировкой resolver'а для уведомлений.
Описание
Email-уведомления о заказе (customer-channel) уходят на
msCustomer.email, а не на email, указанный пользователем в форме этого конкретного заказа. Это проблема для типового сценария «один браузер, несколько заказов от разных людей».Воспроизведение
email = alice@example.com, дождаться email-уведомления — уходит корректно наalice@example.comemail = bob@example.combob@example.comalice@example.comПричина
Controllers/Customer/Customer.php::getOrCreate()находит существующегоmsCustomerпоtoken(сессионный ID) — и не обновляетemail/phoneиз новых данных формы. Это может быть осознанным решением (чтобы не затирать данные клиента), но оно приводит к багу в отправке уведомлений.Services/Order/OrderStatusService.php::getCustomerRecipient()(строки 204-262) при сборе recipient для notification-канала смотрит в:msCustomer.email/.phone(строки 220-228)modUserProfileкак fallback (строки 242-256)msOrderAddress— самый свежий источник контактов, относящийся к этому заказу — не используется вообще. Email, который пользователь только что ввёл в форму, не попадает в recipient.Предложение
В
OrderStatusService::getCustomerRecipient()поставитьmsOrderAddressпервым источником — как контакты, прикреплённые именно к этому заказу.msCustomerиmodUserProfileстановятся fallback'ом, когда в адресе пусто (backwards-совместимо для старых заказов, где адрес мог не сохраняться).Псевдокод:
```php
protected function getCustomerRecipient(msOrder $msOrder): ?array
{
$recipient = ['type' => 'customer', 'email' => null, 'phone' => null, 'telegram_chat_id' => null];
$hasContact = false;
}
```
Почему именно такой порядок
msCustomerкаждый раз опасно (сломает merge клиентов по email и CRM-интеграции).telegram_chat_idпо-прежнему читается только из customer-extended (в адресе его нет — это отдельный канал доставки).getManagerRecipient()(строки 264+) никак не зависит от заказа, читает системные настройки — остаётся как есть.Обратная совместимость
msOrderAddressemail пустой, продолжат работать через fallback наmsCustomer— поведение не меняется.Альтернатива на одном плагине (временное решение, пока не смержено)
Перехват через событие
msOnBeforeSendNotificationс мутацией$recipient['email']из$notification->getOrder()->getOne('Address')->get('email'). Работает точечно, не требует правки ядра, не меняет поведение если email в адресе отсутствует.Затронутые файлы
core/components/minishop3/src/Services/Order/OrderStatusService.php:204-262(getCustomerRecipient)Дополнительно
Отдельным вопросом — стоит ли также обновлять
msCustomer.emailпри новой submission (merge стратегия). Но это другой уровень решения и зависит от бизнес-правил (привязка клиента к единственному email vs. история контактов). Пока — ограничиваемся корректировкой resolver'а для уведомлений.