Сценарий знакомый: клиент оплатил заказ, банк деньги списал, в кабинете платежного провайдера операция успешна, а в WooCommerce заказ так и висит в статусе pending или on-hold. Иногда письмо не уходит, иногда не срабатывает выдача доступа, а в логах тишина. В таких случаях проблема обычно не в самой оплате, а в том, как магазин принимает и подтверждает событие от шлюза.
Ниже разберём, где именно искать сбой, как быстро отделить проблему фронта от вебхука и как проверить, что заказ начинает обновляться стабильно.
Что обычно ломается в этой цепочке
У WooCommerce есть несколько точек, где платеж может «потеряться»: возврат пользователя на сайт после оплаты, обработка webhook от провайдера, проверка подписи запроса, смена статуса заказа и запуск внутренних действий по этому статусу. Если ломается хотя бы один шаг, заказ остаётся без движения.
Типичные признаки
- в кабинете провайдера платёж успешный, а в WooCommerce заказ не меняет статус;
- платёж виден только в логах шлюза, но не в истории заказа;
- статус меняется вручную, но автоматические письма и интеграции не срабатывают;
- после оплаты пользователь возвращается на страницу «Спасибо», но заказ остаётся неоплаченным;
- вебхуки приходят, но WooCommerce отвечает 200 и ничего не делает.
Диагностика проблемы: с чего начать
Не стоит сразу править код. Сначала нужно понять, где именно обрывается цепочка. В большинстве случаев достаточно проверить три вещи: включены ли логи шлюза, доходит ли webhook до сайта и совпадает ли статус заказа с тем, что ожидает плагин оплаты.
Проверьте журнал WooCommerce
Откройте WooCommerce → Статус → Журналы и найдите лог вашего платежного плагина. Если лог пустой, значит плагин либо не пишет события, либо запросы не доходят до обработчика. Если в логе есть ошибки подписи, таймаута или неверного URL, это уже конкретная зацепка.
Проверьте, вызывается ли webhook
У многих шлюзов подтверждение оплаты приходит отдельным запросом на сайт. Если сервер режет такие запросы, заказ не обновится даже при успешной оплате. Частые причины: защита от ботов, Basic Auth на staging, неверный siteurl, блокировка REST API, WAF на хостинге.
Сравните статус заказа и статус платежа
Иногда платёжный шлюз считает операцию завершённой, но WooCommerce ждёт другой статус. Например, плагин переводит заказ в processing только после capture, а провайдер отправляет событие об авторизации без списания. Это не ошибка, а несовпадение логики.
Пошаговое решение: как найти и устранить разрыв
Ниже рабочая последовательность, которая помогает в реальных магазинах. Идти лучше именно в этом порядке: от внешнего события к внутренней обработке.
1. Включите логирование у платежного плагина
Если плагин поддерживает debug-режим, включите его в настройках. После этого повторите тестовую оплату и проверьте, появляется ли запись о входящем webhook или callback.
Если нужно быстро добавить собственную диагностику в тему или мини-плагин, можно временно повеситься на смену статуса заказа:
add_action( 'woocommerce_order_status_changed', function( $order_id, $old_status, $new_status, $order ) {
if ( ! $order instanceof WC_Order ) {
return;
}
error_log( sprintf(
'Order %d status changed: %s -> %s',
$order_id,
$old_status,
$new_status
) );
}, 10, 4 );Этот код не чинит оплату сам по себе, но помогает понять, доходит ли WooCommerce до смены статуса вообще.
2. Проверьте доступность endpoint webhook
Если провайдер шлёт запрос на REST endpoint или отдельный URL, убедитесь, что он не закрыт кэшем, редиректом или авторизацией. Для теста можно отправить запрос вручную через curl с тем же URL, который указан в настройках шлюза.
curl -i -X POST https://example.com/?wc-api=wc_gateway_name \
-H 'Content-Type: application/json' \
-d '{"test":"ping"}'Если вместо ответа шлюза вы видите 301, 403 или HTML-страницу защиты, проблема на уровне маршрутизации или безопасности, а не в WooCommerce.
3. Убедитесь, что подпись webhook проверяется корректно
У многих провайдеров webhook подписан секретом. Если секрет в настройках плагина не совпадает с тем, что указан в кабинете провайдера, WooCommerce будет игнорировать событие. Это особенно часто случается после переноса сайта или клонирования staging-среды.
Ниже пример проверки HMAC-подписи. Это не универсальный код для всех шлюзов, но сам принцип рабочий:
function wppay_verify_webhook_signature( $payload, $signature, $secret ) {
$expected = hash_hmac( 'sha256', $payload, $secret );
return hash_equals( $expected, $signature );
}
$raw_body = file_get_contents( 'php://input' );
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$secret = 'your-webhook-secret';
if ( ! wppay_verify_webhook_signature( $raw_body, $signature, $secret ) ) {
status_header( 401 );
exit( 'Invalid signature' );
}4. Проверьте, не мешает ли кэш и защита
Страницы оформления заказа и endpoint вебхуков не должны попадать под агрессивный кэш. Если на сайте стоит Cloudflare, серверный кэш или плагин оптимизации, исключите из кэширования:
/checkout/;/cart/;/my-account/;- URL вебхуков платежного шлюза;
- REST API, если шлюз использует его для подтверждения оплаты.
Если у вас есть плагин вроде Clearfy Pro, его имеет смысл использовать для точечной чистки дублей и служебных страниц, но не как замену нормальной настройке кэша и исключений.
Когда проблема в статусах заказа, а не в платеже
Бывает, что webhook приходит, но заказ остаётся в pending. Тогда нужно смотреть логику перехода статуса внутри самого шлюза. Некоторые плагины переводят заказ в processing только при определённом типе товара или после подтверждения доставки цифрового контента.
Пример ручного обновления статуса после подтверждения
Если вы пишете собственную интеграцию или дополняете существующую, статус можно менять через API WooCommerce после валидации события:
add_action( 'rest_api_init', function() {
register_rest_route( 'wppay/v1', '/payment-confirm', array(
'methods' => 'POST',
'callback' => 'wppay_payment_confirm_callback',
'permission_callback' => '__return_true',
) );
} );
function wppay_payment_confirm_callback( WP_REST_Request $request ) {
$order_id = absint( $request->get_param( 'order_id' ) );
$order = wc_get_order( $order_id );
if ( ! $order ) {
return new WP_REST_Response( array( 'error' => 'Order not found' ), 404 );
}
$order->payment_complete();
$order->add_order_note( 'Payment confirmed via webhook.' );
return new WP_REST_Response( array( 'success' => true ), 200 );
}Здесь важно не забыть про проверку подписи и авторизации. Открытый endpoint без защиты — плохая идея, особенно если он меняет статус заказа.
Как проверить, что решение сработало
После правок не ограничивайтесь одной тестовой оплатой в браузере. Проверьте всю цепочку:
- создаётся ли заказ;
- приходит ли webhook в лог;
- меняется ли статус без ручного вмешательства;
- уходит ли письмо клиенту;
- срабатывает ли выдача цифрового товара, подписки или доступа в личный кабинет;
- не появляется ли повторная обработка одного и того же события.
Если у вас есть доступ к журналу сервера, полезно сверить время входящего webhook и время смены статуса. Разница в несколько секунд допустима, но если событие вообще не отражается в WooCommerce, значит проблема ещё не устранена.
Частые ошибки и как их исправить
Webhook уходит на старый домен
После переезда сайта провайдер продолжает слать запросы на старый URL. Исправление простое: обновить callback URL в кабинете платёжной системы и проверить, что в WordPress корректны siteurl и home.
Сайт отвечает 403 на входящий запрос
Обычно это защита хостинга, модуль безопасности или плагин, который режет POST-запросы. Нужно добавить исключение для webhook-URL и проверить, не блокируется ли запрос по User-Agent или IP.
Заказ меняется вручную, но автоматизация не запускается
Если вы меняете статус через админку, а письма и интеграции не срабатывают, проверьте, на какой именно хук завязана логика. Иногда код слушает только woocommerce_payment_complete, а вы переводите заказ в другой статус вручную.
Один и тот же webhook обрабатывается дважды
Это уже проблема идемпотентности. Нужно хранить идентификатор события и не применять его повторно. Иначе можно получить двойное списание логики: повторную выдачу доступа, повторное письмо или повторное изменение статуса.
Чек-лист перед запуском в продакшн
- проверен callback URL в кабинете провайдера;
- отключён кэш для checkout и webhook-эндпоинтов;
- включены логи платежного плагина;
- проверена подпись webhook;
- обработчик не принимает повторно одно и то же событие;
- заказ меняет статус только после подтверждённого события;
- тестовая оплата проходит полный цикл до письма и выдачи товара.
Безопасность и производительность
Не оставляйте debug-логирование включённым постоянно, если в нём пишутся персональные данные или токены. После отладки лучше ограничить объём логов и убрать временные error_log(). Для webhook-обработчиков используйте минимально необходимую логику: подтвердить подпись, найти заказ, записать событие, сменить статус. Всё тяжёлое — отчёты, синхронизацию с CRM, уведомления — лучше выносить в отдельный асинхронный процесс или хотя бы в отложенную задачу.
Если магазин большой, полезно отдельно проверить, не тормозит ли обработка заказа сторонними плагинами. Иногда проблема выглядит как «платёж не подтвердился», хотя на деле webhook приходит, но выполнение обрывается на медленном хуке другого плагина.
В итоге задача сводится не к магии, а к нормальной трассировке: где пришёл запрос, что он содержал, почему WooCommerce не сменил статус и что именно блокирует следующий шаг. Когда эта цепочка видна, «невидимые» платежи перестают быть загадкой.