WooCommerce: как сбросить статус заказа при неудачном платеже

Сценарий знакомый: покупатель дошёл до оплаты, банк отклонил платёж, а заказ в WooCommerce остался в статусе processing или on-hold. В результате менеджер видит «почти оплаченный» заказ, склад может зарезервировать товар, а клиент получает путаные письма. Если платёжная система не возвращает заказ в нужное состояние сама, это приходится исправлять на стороне сайта.

Ниже — рабочая схема, которая помогает вернуть заказ в pending или failed после неудачной оплаты, не ломая остальные сценарии. Разберём, где именно возникает проблема, как выбрать точку интеграции и как проверить, что всё работает на реальном платёжном потоке.

Когда заказ «застревает» после ошибки оплаты

В WooCommerce статус заказа меняется не только самим магазином. На него влияют:

  • платёжный шлюз и его callback/webhook;
  • внутренние переходы статусов WooCommerce;
  • ручные действия менеджера;
  • асинхронные подтверждения от банка или платёжного агрегатора.

Проблема обычно проявляется в одном из двух вариантов:

  • заказ создан, но оплата не завершилась, а статус уже стал processing;
  • платёж отклонён, но заказ остался в on-hold и не переводится в failed.

Для магазина это не косметика. Статус влияет на письма, отчёты, списание остатков и автоматизацию через хуки.

Диагностика: что именно ломается

Перед правкой кода стоит понять, где сбой: в шлюзе, в теме или в кастомной логике. Это можно проверить без сложных инструментов.

Проверьте, какой статус ставится после отказа

Откройте заказ в админке и посмотрите историю статусов. Если после отказа платёжной системы заказ остаётся в pending payment или on-hold, значит шлюз не завершает переход. Если статус уже стал processing, а платёж фактически не прошёл, проблема чаще в callback-логике или в том, что шлюз слишком рано помечает заказ как оплаченный.

Посмотрите журнал WooCommerce

У многих шлюзов есть собственные логи в WooCommerce → Статус → Журналы. Ищите:

  • ошибки подписи webhook;
  • таймауты при запросе к API;
  • ответы банка с кодом отказа;
  • повторные уведомления по одному и тому же заказу.

Если логов нет, включите их в настройках самого шлюза, если такая опция предусмотрена.

Проверьте, не переопределяет ли статус тема или плагин

Иногда статус меняет не шлюз, а кастомный код в functions.php или сторонний плагин автоматизации. Быстрый способ поиска — временно отключить плагины, которые работают с заказами, и повторить тест на staging-копии.

ПодходКогда подходитМинус
Настройка в платёжном шлюзеЕсли шлюз умеет корректно обрабатывать отказНе всегда есть нужная логика
Код через хуки WooCommerceЕсли нужен точечный контроль статусаНужно тестировать на каждом шлюзе отдельно
Сторонний плагин автоматизацииЕсли логика простая и без кастомных условийМеньше контроля, возможны конфликты

Пошаговое решение через хуки WooCommerce

Если платёжный шлюз не возвращает заказ в нужный статус, можно перехватить событие и перевести заказ вручную. Для этого удобно использовать хук woocommerce_order_status_changed или события самого шлюза, если они доступны. Но для универсального сценария безопаснее опираться на изменение статуса заказа.

Ниже пример: если заказ перешёл в on-hold и в метаданных есть признак неудачной оплаты, переводим его в failed. Это не универсальная магия, а шаблон, который нужно адаптировать под конкретный шлюз и его мета-поля.

add_action( 'woocommerce_order_status_changed', 'wpdemo_mark_failed_payment_order', 20, 4 );
function wpdemo_mark_failed_payment_order( $order_id, $old_status, $new_status, $order ) {
    if ( ! $order instanceof WC_Order ) {
        return;
    }

    // Не трогаем уже завершённые или отменённые заказы.
    if ( in_array( $old_status, array( 'completed', 'cancelled', 'refunded', 'failed' ), true ) ) {
        return;
    }

    // Пример признака неудачной оплаты.
    // Замените на реальный meta_key вашего шлюза.
    $payment_result = $order->get_meta( '_payment_result' );

    if ( 'declined' !== $payment_result ) {
        return;
    }

    if ( 'on-hold' === $new_status || 'pending' === $new_status ) {
        $order->update_status( 'failed', 'Оплата отклонена платёжной системой.' );
    }
}

Что важно в этом примере:

  • мы не меняем статус без явного признака отказа;
  • не трогаем завершённые заказы;
  • оставляем комментарий в истории заказа, чтобы менеджер видел причину перехода.

Если шлюз даёт свой хук

Некоторые платёжные плагины вызывают собственные действия при неуспешной оплате. Это лучше, чем ловить общий переход статуса, потому что событие уже содержит контекст отказа. Но конкретный хук зависит от плагина, поэтому проверяйте документацию именно вашего шлюза.

Если у вас есть событие с заказом и кодом ошибки, логика обычно выглядит так:

add_action( 'your_gateway_payment_failed', function( $order_id, $error_code ) {
    $order = wc_get_order( $order_id );

    if ( ! $order ) {
        return;
    }

    if ( 'insufficient_funds' === $error_code || 'card_declined' === $error_code ) {
        $order->update_status( 'failed', 'Платёж отклонён: ' . $error_code );
    }
}, 10, 2 );

Здесь намеренно используется условный хук your_gateway_payment_failed как шаблон. Подставляйте реальное событие только из документации конкретного плагина оплаты.

Как не сломать рабочие заказы

Самая частая ошибка — переводить в failed любой заказ, который не дошёл до processing. Это опасно: у некоторых шлюзов оплата подтверждается с задержкой, а заказ сначала уходит в on-hold до проверки банка. Если слишком рано поставить failed, можно получить ложные отмены.

Поэтому логика должна учитывать хотя бы один из признаков:

  • код ошибки от шлюза;
  • мета-поле заказа с результатом оплаты;
  • таймаут ожидания подтверждения, если он реально нужен;
  • статус вебхука, если он уже обработан.

Если у вашего шлюза есть собственная документация по статусам, используйте её как источник истины. Не пытайтесь угадать поведение по одному заказу.

Проверка результата после внедрения

После добавления кода нужно проверить не только статус, но и побочные эффекты: письма, остатки, повторные уведомления.

  • Сделайте тестовый заказ с картой, которая гарантированно отклоняется в sandbox-режиме.
  • Убедитесь, что заказ переходит в failed или в тот статус, который вы выбрали.
  • Проверьте, не списался ли товар со склада.
  • Посмотрите, не ушло ли письмо «заказ оплачен», если оплата не прошла.
  • Откройте журнал WooCommerce и убедитесь, что нет повторной обработки одного и того же callback.

Если статус меняется, но письма идут не те, проверьте настройки уведомлений WooCommerce и сторонние плагины рассылки. Иногда проблема не в статусе, а в том, что письмо привязано к неверному событию.

Частые ошибки и как их исправить

Заказ переводится в failed слишком рано

Причина: код реагирует на pending или on-hold без проверки реального результата оплаты. Исправление: добавьте проверку мета-поля, кода ошибки или статуса webhook.

Статус меняется, но потом возвращается обратно

Причина: платёжный шлюз повторно отправляет callback и перезаписывает статус. Исправление: делайте код идемпотентным — проверяйте текущий статус заказа перед обновлением и не меняйте его повторно без необходимости.

Менеджер видит «failed», хотя деньги списались

Причина: асинхронное подтверждение от банка пришло позже, чем сработала ваша логика. Исправление: не переводите заказ в failed по одному только таймауту, если шлюз допускает отложенное подтверждение.

Не срабатывают письма WooCommerce

Причина: вы меняете мета-поля заказа напрямую, а не используете update_status(). Исправление: обновляйте статус через API заказа WooCommerce, чтобы сработали штатные события.

Практические советы по безопасности и производительности

Код для статусов заказов лучше держать не в теме, а в небольшом mu-plugin или в отдельном плагине сайта. Тогда он не исчезнет после обновления темы и проще отключается на staging.

Если логика зависит от конкретного шлюза, не пишите универсальные «проверки на всё». Чем меньше условий, тем проще сопровождать код и тем ниже риск задеть рабочие заказы.

Для магазинов с высокой нагрузкой полезно:

  • не делать тяжёлые запросы к внешним API внутри хука изменения статуса;
  • не запускать повторную обработку одного и того же заказа без флага идемпотентности;
  • хранить в мета заказа отметку о том, что отказ уже обработан;
  • тестировать изменения на копии магазина с тем же платёжным плагином.

Если вам нужно не только менять статус, но и чистить лишнюю автоматизацию вокруг заказов, иногда проще сначала убрать конфликтующие правила. В экосистеме WPShop для этого есть Clearfy Pro, но использовать его имеет смысл только если вы уже понимаете, какой именно дубль или лишнее действие мешает вашему сценарию.

Что проверить после выката на боевой сайт

Перед публикацией на продакшене пройдитесь по короткому чек-листу:

  • тестовый отказ оплаты переводит заказ в нужный статус;
  • успешная оплата не ломается;
  • письма приходят только по правильным событиям;
  • в журнале нет повторной обработки одного заказа;
  • код не зависит от конкретной темы оформления;
  • логика работает после очистки кеша и повторного входа в админку.

Если всё это проходит, значит решение не просто «сработало один раз», а действительно встроилось в рабочий процесс магазина.

Как закрыть от индексации страницы поискового фильтра в WordPress
19.08.2026
Как настроить robots.txt в WordPress, чтобы не закрыть важный контент
23.08.2026