Skip to content

[Bug] Статус заказа расходится с отгрузкой и оплатой после 1.13.0 #754

Description

@Ibochkarev

Описание проблемы

После тега v1.13.0-beta1 (13 августа 2026, коммит f8858a3) в beta вошли lifecycle оплаты (#604), отгрузки (#605) и смена статуса заказа через порты (#596). На текущем HEAD f0dfe59 два сценария оставляют заказ и внешнее событие в разных состояниях. Учёт остатков в эту ветку не влит.

Шаги воспроизведения

Отгрузка:

  1. Включить ms3_shipment_enabled.
  2. Создать отгрузку заказа, который ещё не в статусе «Отправлен».
  3. Принять webhook shipped с provider_event_id. Параллельно сделать так, чтобы OrderStatusService::ensure() не смог поставить ms3_status_sent: плагин на msOnBeforeChangeOrderStatus возвращает ошибку, либо переход закрыт ms3_order_status_transitions.
  4. Проверить msOrder.status_id и строку в ms3_shipments.
  5. Повторить тот же webhook с тем же provider_event_id.

Оплата:

  1. Оставить ms3_payment_on_failed_status по умолчанию (5, отмена).
  2. Принять webhook failed по открытой попытке.
  3. Выпустить новую попытку на том же заказе и принять webhook paid с другим external_id.

Ожидаемое поведение

Webhook shipped либо переводит заказ в ms3_status_sent вместе с отгрузкой, либо не занимает provider_event_id, чтобы повторный запрос дожал статус.

Повторная успешная оплата того же заказа после failed записывается в попытку. Если статус заказа уже сохранён, а упало только письмо или msOnChangeOrderStatus, webhook не отвечает 409.

Фактическое поведение

ShipmentLifecycleService::applyWithEventClaim пишет provider_event_id и статус отгрузки, делает commit и только потом вызывает syncOrderStatus. Ошибка синхронизации уходит в error.log и не меняет ответ webhook. Повтор с тем же provider_event_id попадает в ветку, где claimEvent возвращает false: транзакция откатывается, метод возвращает текущую отгрузку и не вызывает afterCommit. Заказ остаётся в прежнем статусе, перевозчик получает успех.

PdoPaymentAttemptStore использует $modx->pdo. У MODX этот handle в ERRMODE_SILENT: неудачный execute() возвращает false и не бросает исключение. PdoShipmentStore::executeWrite это учитывает и на SQLSTATE 23000 возвращает false. PdoPaymentAttemptStore::recordEvent и update результат execute() не проверяют. Если вставка события не прошла без исключения, recordEvent всё равно возвращает true, и writeWithEvent коммитит изменение попытки без строки в ms3_payment_attempt_events. Следующий webhook с тем же id не видит hasEvent и может применить сумму ещё раз. В юнит-тестах PDO обычно кидает исключения, поэтому этот путь там не виден.

PaymentLifecycleService::commit сначала коммитит попытку в writeWithEvent, затем вызывает syncOrderStatus. Если OrderStatusService::change() уже сохранил status_id, а msOnChangeOrderStatus вернул ошибку, метод отдаёт текст ошибки и не доходит до уведомлений. syncOrderStatus превращает любую строку в PaymentLifecycleException, webhook отвечает 409. Повтор идёт в ensure(): статус уже целевой, метод сразу возвращает true и уведомление снова не отправляет.

При настройке по умолчанию webhook failed или cancelled ставит заказу статус 5. assertPaidPreconditions отклоняет следующий paid, пока заказ отменён. Проверка стоит до commit, поэтому попытка не помечается оплаченной, а клиент платёжки получает 409.

Скриншоты

Нет. Расхождение видно по строкам ms3_shipments, ms3_shipment_events, ms3_payment_attempts и по status_id заказа.

Окружение

  • MiniShop3: beta после v1.13.0-beta1, HEAD f0dfe59
  • MODX: 3.x
  • PHP: 8.2+
  • MySQL: 8, PDO из MODX (ERRMODE_SILENT)
  • Браузер: не участвует, это webhook и сервисы

Логи ошибок

Логи из core/cache/logs/error.log
ShipmentLifecycleService: order #<id> status sync to <status_id> failed: <текст ошибки>

Дополнительный контекст

Приоритеты по находкам:

  1. priority: high. Отгрузка и статус заказа расходятся, повтор webhook это не чинит. core/components/minishop3/src/Services/Shipment/ShipmentLifecycleService.php, applyWithEventClaim (около строк 464-504) и syncOrderStatus (около 339-366).

  2. priority: high. Неуспешная оплата по умолчанию отменяет заказ, следующая успешная оплата того же заказа получает 409 и не пишется в попытку. PaymentLifecycleService::orderStatusFor и assertPaidPreconditions в core/components/minishop3/src/Services/Payment/PaymentLifecycleService.php.

  3. priority: medium. PdoPaymentAttemptStore::recordEvent не смотрит на false от execute(). Тот же тихий PDO уже разобран в PdoShipmentStore::executeWrite.

  4. priority: medium. Ошибка плагина msOnChangeOrderStatus после уже сохранённого статуса даёт 409 на оплате и срывает уведомление. Повтор через ensure() его не досылает. OrderStatusService::change (около строк 201-219) и PaymentLifecycleService::commit (около 315-324).

Проверял код на ветке beta в диапазоне v1.13.0-beta1..HEAD (88 коммитов). Ветку учёта остатков не смотрел: её нет в HEAD.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingphpPull requests that update php codepriority: highВажно исправить в ближайшее время

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions