Сценарий простой: заказ уже оплачен, а менеджер или клиент пытается поменять адрес доставки, состав заказа или сумму. В WooCommerce это часто происходит не из-за одной настройки, а из-за сочетания прав пользователя, статуса заказа и того, как у вас устроена админка. Если это не ограничить, легко получить расхождения между оплатой, складом и документами.
Ниже — рабочий способ закрыть редактирование ключевых полей после оплаты, не ломая обычную обработку заказов до оплаты.
Когда проблема проявляется на практике
Чаще всего жалоба выглядит так: заказ в статусе processing или completed, но в админке его всё ещё можно открыть, поменять адрес, товары или сумму, а затем сохранить. Иногда изменения проходят даже после того, как платёж уже подтверждён. Это особенно заметно, если у вас подключены ручные правки менеджерами, интеграция с CRM или нестандартные роли пользователей.
Если бизнес-процесс требует, чтобы оплаченный заказ был неизменяемым, нужно ограничивать не только интерфейс, но и серверную обработку. Скрыть поля в форме недостаточно: данные можно отправить запросом напрямую.
Диагностика: что именно у вас сейчас разрешено
Перед правкой кода проверьте три вещи:
- какие статусы у заказов считаются «оплаченными» в вашей схеме;
- есть ли у менеджеров права на редактирование заказов в админке;
- не меняет ли заказ сторонний плагин через REST API, CRM или кастомный endpoint.
Для быстрой проверки откройте оплаченный заказ в админке и попробуйте изменить:
- billing/shipping адрес;
- позиции заказа;
- итоговую сумму или доставку;
- статус заказа.
Если хотя бы одно из этих действий проходит без ошибок, значит блокировка у вас пока не работает на уровне сохранения данных.
Пошаговое решение: блокируем редактирование после оплаты
Надёжнее всего разделить задачу на две части: запретить изменения в интерфейсе и остановить сохранение на сервере. Ниже пример для functions.php дочерней темы или собственного мини-плагина.
1. Скрываем поля редактирования для оплаченных заказов
Это не защита, а удобство: менеджер сразу видит, что заказ уже закрыт для правок.
add_action( 'admin_head', function () {
global $post;
if ( ! $post || 'shop_order' !== $post->post_type ) {
return;
}
$order = wc_get_order( $post->ID );
if ( ! $order ) {
return;
}
$paid_statuses = array( 'processing', 'completed', 'on-hold' );
if ( in_array( $order->get_status(), $paid_statuses, true ) ) {
echo '<style>
#woocommerce-order-items,
.woocommerce_order_items_wrapper,
.order_data_column .address,
.order_data_column .edit_address {
pointer-events: none;
opacity: .65;
}
</style>';
}
} );Этот приём не блокирует сохранение сам по себе, но снижает риск случайных правок.
2. Запрещаем сохранение адреса и суммы на сервере
Для реальной блокировки используйте фильтр woocommerce_before_order_object_save. Он срабатывает до записи заказа в базу и позволяет сравнить старые и новые значения.
add_action( 'woocommerce_before_order_object_save', function ( $order ) {
if ( ! $order instanceof WC_Order ) {
return;
}
$paid_statuses = array( 'processing', 'completed' );
if ( ! in_array( $order->get_status(), $paid_statuses, true ) ) {
return;
}
$order_id = $order->get_id();
$original = wc_get_order( $order_id );
if ( ! $original ) {
return;
}
$changed = false;
if ( $order->get_billing_address_1() !== $original->get_billing_address_1() ) {
$changed = true;
}
if ( $order->get_shipping_address_1() !== $original->get_shipping_address_1() ) {
$changed = true;
}
if ( (float) $order->get_total() !== (float) $original->get_total() ) {
$changed = true;
}
if ( $changed ) {
throw new Exception( 'Оплаченный заказ нельзя редактировать.' );
}
}, 10, 1 );Такой подход полезен, если у вас есть менеджеры, которые правят заказы вручную, или интеграции, которые пытаются перезаписать поля после оплаты.
3. Если нужно запретить только адрес, а сумму оставить
Иногда бизнес-процесс допускает корректировку доставки, но не позволяет менять стоимость. Тогда лучше проверять только адресные поля и не трогать итог заказа.
add_action( 'woocommerce_before_order_object_save', function ( $order ) {
if ( ! $order instanceof WC_Order ) {
return;
}
if ( ! in_array( $order->get_status(), array( 'processing', 'completed' ), true ) ) {
return;
}
$original = wc_get_order( $order->get_id() );
if ( ! $original ) {
return;
}
$address_fields = array(
'billing_first_name',
'billing_last_name',
'billing_address_1',
'billing_city',
'billing_postcode',
'shipping_first_name',
'shipping_last_name',
'shipping_address_1',
'shipping_city',
'shipping_postcode',
);
foreach ( $address_fields as $field ) {
$getter = 'get_' . $field;
if ( method_exists( $order, $getter ) && method_exists( $original, $getter ) ) {
if ( $order->{$getter}() !== $original->{$getter}() ) {
throw new Exception( 'Адрес оплаченного заказа менять нельзя.' );
}
}
}
}, 10, 1 );Что выбрать: плагин, код или гибрид
| Подход | Когда подходит | Минус |
|---|---|---|
| Код | Нужно закрыть конкретные поля и статусы | Требует теста после обновлений WooCommerce |
| Плагин для управления ролями | Нужно ограничить только менеджеров без разработки | Не всегда блокирует сохранение на уровне данных |
| Гибрид | Нужен и контроль интерфейса, и защита от прямого сохранения | Чуть больше поддержки |
Если у вас небольшой магазин и правки делает один человек, код обычно проще и прозрачнее. Если заказами занимается отдел продаж, лучше сочетать серверную проверку с ограничением ролей.
Как проверить, что решение сработало
После внедрения не ограничивайтесь визуальной проверкой в админке. Нужны три теста:
- Откройте оплаченный заказ и попробуйте изменить адрес вручную через форму.
- Попробуйте сохранить заказ после изменения через браузерную консоль или повторной отправкой формы.
- Если у вас есть интеграция с CRM, проверьте, не перезаписывает ли она заказ после смены статуса.
Если код работает корректно, WooCommerce должен вернуть ошибку сохранения, а изменения не должны попасть в базу. Для дополнительной проверки сравните данные заказа до и после теста в таблице wp_postmeta или через экран заказа в админке.
Частые ошибки и как их исправить
Слишком широкий список статусов
Если вы включили в блокировку on-hold, но у вас этот статус используется до фактической оплаты, менеджеры потеряют возможность вносить рабочие правки слишком рано. Проверьте, какие статусы реально означают «деньги уже получены».
Проверка только в интерфейсе
Скрыть поля CSS-ом недостаточно. Любой запрос можно отправить повторно. Нужна серверная проверка через хук сохранения заказа.
Конфликт с плагинами доставки или CRM
Если сторонний плагин пересохраняет заказ, он может вызывать исключение. В таком случае проверьте, на каком этапе он меняет данные, и при необходимости ограничьте проверку только ручным редактированием в админке.
Использование неподходящего хука
Иногда пытаются ловить изменения через save_post. Для WooCommerce-заказов это менее удобно, потому что объект заказа может сохраняться не так, как обычная запись WordPress. Для проверки изменений заказа лучше работать с объектом WC_Order.
Безопасность и производительность: что учесть
Не храните логику блокировки в случайном сниппете без контроля версий. Лучше вынести её в маленький mu-plugin или отдельный плагин проекта. Так код не потеряется при смене темы.
Если у вас высокая нагрузка и много заказов, не делайте тяжёлые запросы к базе внутри проверки. Сравнивайте только нужные поля текущего объекта и оригинального заказа. Это дешевле, чем строить дополнительные запросы на каждом сохранении.
И ещё один практический момент: если бизнес допускает изменения после оплаты только через отдельную процедуру, лучше не «разрешать всем и надеяться на дисциплину», а явно описать, кто и как может вносить правки. Тогда код будет соответствовать процессу, а не спорить с ним.