Просто повесить один чекбокс «Я согласен с правилами» уже недостаточно — особенно если вы хотите разделять обязательные юридические согласия (оферта, ПДн) и маркетинг (рассылки).
Делюсь проверенным решением, которое решает эту задачу «под ключ» для классического чекаута WooCommerce [woocommerce_checkout].
Как это работает
- Шаг 1. Вывод полей — перед кнопкой «Оформить заказ» выводим кастомные чекбоксы. Если страница перезагрузится из-за ошибки, выбранные состояния сохранятся.
- Шаг 2. Валидация — проверяем, отмечены ли обязательные галочки. Если нет — выводим системное уведомление WooCommerce и останавливаем оформление.
- Шаг 3. Сохранение — записываем метаданные прямо в объект заказа перед его сохранением в БД.
- Шаг 4. Админка — выводим статус всех согласий прямо в карточку заказа под адресом плательщика.
- Шаг 5. Чистка — полностью убираем стандартную логику термсов.
Весь код сниппета целиком
<?php
/**
* Добавляем чекбоксы согласия на странице оформления заказа WooCommerce
* и сохраняем их с поддержкой HPOS и классического хранилища (Post Meta).
*
* - Обязательные: оферта, политика конфиденциальности
* - Необязательный: согласие на рекламные рассылки
*
* ВАЖНО: код рассчитан на классический чекаут WooCommerce (шорткод [woocommerce_checkout]).
* Если магазин использует новый чекаут на блоках (Cart & Checkout Blocks), эти хуки не сработают.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Защита от прямого доступа
}
// 1. Выводим чекбоксы перед кнопкой "Оформить заказ"
add_action( 'woocommerce_review_order_before_submit', 'rb_add_agreement_checkboxes', 20 );
function rb_add_agreement_checkboxes() {
$oferta_checked = ! empty( $_POST['rb_oferta_agree'] ) ? 1 : 0;
$privacy_checked = ! empty( $_POST['rb_privacy_agree'] ) ? 1 : 0;
$marketing_checked = ! empty( $_POST['rb_marketing_agree'] ) ? 1 : 0;
?>
<div class="rb-agreement-checkboxes" style="margin-bottom: 15px;">
<p class="form-row validate-required" style="margin-bottom: 8px;">
<label class="woocommerce-form__label woocommerce-form__label-for-checkbox checkbox">
<input type="checkbox"
name="rb_oferta_agree"
id="rb_oferta_agree"
value="1"
<?php checked( $oferta_checked, 1 ); ?> />
<span>
Вы согласны с условиями
<a href="https://moysite.com/oferta/" target="_blank" rel="noopener">публичной оферты</a>
<span class="required">*</span>
</span>
</label>
</p>
<p class="form-row validate-required" style="margin-bottom: 8px;">
<label class="woocommerce-form__label woocommerce-form__label-for-checkbox checkbox">
<input type="checkbox"
name="rb_privacy_agree"
id="rb_privacy_agree"
value="1"
<?php checked( $privacy_checked, 1 ); ?> />
<span>
Вы согласны с
<a href="https://moysite.com/privacy-policy/" target="_blank" rel="noopener">политикой конфиденциальности</a>
и обработкой персональных данных
<span class="required">*</span>
</span>
</label>
</p>
<p class="form-row" style="margin-bottom: 8px;">
<label class="woocommerce-form__label woocommerce-form__label-for-checkbox checkbox">
<input type="checkbox"
name="rb_marketing_agree"
id="rb_marketing_agree"
value="1"
<?php checked( $marketing_checked, 1 ); ?> />
<span>
Вы согласны получать рекламные и информационные сообщения от Продавца.
</span>
</label>
</p>
</div>
<?php
}
// 2. Валидация — не даём оформить заказ, если обязательные галочки не отмечены
add_action( 'woocommerce_checkout_process', 'rb_validate_agreement_checkboxes' );
function rb_validate_agreement_checkboxes() {
if ( empty( $_POST['rb_oferta_agree'] ) ) {
wc_add_notice(
'Пожалуйста, подтвердите согласие с условиями публичной оферты.',
'error'
);
}
if ( empty( $_POST['rb_privacy_agree'] ) ) {
wc_add_notice(
'Пожалуйста, подтвердите согласие с политикой конфиденциальности.',
'error'
);
}
}
// 3. Сохранение данных заказа (совместимо с HPOS и со старой базой CPT)
add_action( 'woocommerce_checkout_create_order', 'rb_save_agreement_checkboxes', 10, 2 );
function rb_save_agreement_checkboxes( $order, $data ) {
$now = current_time( 'mysql' );
// Оферта
if ( ! empty( $_POST['rb_oferta_agree'] ) ) {
$order->update_meta_data( '_rb_oferta_agree', 'yes' );
$order->update_meta_data( '_rb_oferta_agree_date', $now );
} else {
$order->update_meta_data( '_rb_oferta_agree', 'no' );
}
// Политика конфиденциальности
if ( ! empty( $_POST['rb_privacy_agree'] ) ) {
$order->update_meta_data( '_rb_privacy_agree', 'yes' );
$order->update_meta_data( '_rb_privacy_agree_date', $now );
} else {
$order->update_meta_data( '_rb_privacy_agree', 'no' );
}
// Маркетинговые рассылки
$marketing_agree = ! empty( $_POST['rb_marketing_agree'] ) ? 'yes' : 'no';
$order->update_meta_data( '_rb_marketing_agree', $marketing_agree );
$order->update_meta_data( '_rb_marketing_agree_date', $now );
}
// 4. Вывод метаданных в админ-панели заказа (совместимо с HPOS и CPT)
add_action( 'woocommerce_admin_order_data_after_billing_address', 'rb_show_agreement_in_admin' );
function rb_show_agreement_in_admin( $order ) {
// Хук всегда передаёт объект WC_Order, но на всякий случай подстрахуемся
// от нестандартных вызовов (например, сторонними плагинами).
if ( ! is_a( $order, 'WC_Order' ) ) {
return;
}
$oferta = $order->get_meta( '_rb_oferta_agree' );
$privacy = $order->get_meta( '_rb_privacy_agree' );
$marketing = $order->get_meta( '_rb_marketing_agree' );
echo '<div class="address" style="clear:both; padding-top:10px;">';
echo '<p><strong>Согласие с офертой:</strong> ' . ( $oferta === 'yes' ? 'Да' : 'Нет' ) . '</p>';
echo '<p><strong>Согласие с политикой конфиденциальности:</strong> ' . ( $privacy === 'yes' ? 'Да' : 'Нет' ) . '</p>';
echo '<p><strong>Согласие на рекламные рассылки:</strong> ' . ( $marketing === 'yes' ? 'Да' : 'Нет' ) . '</p>';
echo '</div>';
}
// 5. Полностью убираем стандартный чекбокс "terms" WooCommerce
add_filter( 'woocommerce_get_terms_page_id', '__return_zero' );
Разбор кода по шагам.
Разберём каждую секцию, чтобы было понятно, за что отвечает каждый hook и каждая строчка.
Предотвращение прямого доступа
PHP
if ( ! defined( 'ABSPATH' ) ) {
exit; // Защита от прямого доступа
}
- Зачем нужно: Стандартная практика безопасности в WordPress. Если кто-то попытается открыть этот файл напрямую через браузер (по прямому URL-адресу), скрипт просто завершит работу, не раскрывая структуру сервера и потенциальные ошибки.
Шаг 1: Вывод чекбоксов на странице чекаута
PHP
add_action( 'woocommerce_review_order_before_submit', 'rb_add_agreement_checkboxes', 20 );
- Хук:
woocommerce_review_order_before_submitпомещает наш HTML-блок в форме оформления заказа непосредственно перед кнопкой «Оформить заказ». - Приоритет
20: Гарантирует, что наши поля появятся после большинства стандартных элементов блока оплаты.
PHP
$oferta_checked = ! empty( $_POST['rb_oferta_agree'] ) ? 1 : 0;
$privacy_checked = ! empty( $_POST['rb_privacy_agree'] ) ? 1 : 0;
$marketing_checked = ! empty( $_POST['rb_marketing_agree'] ) ? 1 : 0;
- Сохранение состояния: Если пользователь совершит ошибку (например, забудет ввести телефон) и страница перезагрузится через AJAX, поля не сбросятся. Код подтянет значения из
$_POSTи сохранит галочки там, где клиент их уже поставил.
PHP
<input type="checkbox" name="rb_oferta_agree" id="rb_oferta_agree" value="1" <?php checked( $oferta_checked, 1 ); ?> />
- Функция
checked(): Стандартная функция WordPress, которая сравнивает два значения и выводитchecked='checked', если они совпадают.
Шаг 2: Валидация перед созданием заказа
PHP
add_action( 'woocommerce_checkout_process', 'rb_validate_agreement_checkboxes' );
- Хук:
woocommerce_checkout_processсрабатывает в момент, когда пользователь нажимает кнопку «Оформить заказ», но до того, как заказ создаётся в базе данных. - Логика:PHP
if ( empty( $_POST['rb_oferta_agree'] ) ) { wc_add_notice( 'Пожалуйста, подтвердите согласие с условиями публичной оферты.', 'error' ); }Если обязательный чекбокс не отмечен, функцияwc_add_notice()добавляет ошибку в глобальный стек WooCommerce. Оформление заказа прерывается, а покупатель видит красное уведомление. - Маркетинговый чекбокс: Не участвует в валидации, так как по закону согласие на рекламные рассылки должно быть добровольным.
Шаг 3: Сохранение данных заказа и поддержке HPOS
PHP
add_action( 'woocommerce_checkout_create_order', 'rb_save_agreement_checkboxes', 10, 2 );
- Хук:
woocommerce_checkout_create_orderдает доступ к объекту заказа$order(экземпляр классаWC_Order) перед его записью в БД. - Фиксация времени (
$now):PHP$now = current_time( 'mysql' );Записывается точное время согласия. Это ключевой момент для юридической защиты на случай проверок или споров.
Почему это работает с HPOS (High-Performance Order Storage)?
В современных версиях WooCommerce данные заказов хранятся в отдельных таблицах, а не в привычном wp_postmeta.
- Правильный подход (использован в коде): PHP
$order->update_meta_data( '_rb_oferta_agree', 'yes' ); $order->update_meta_data( '_rb_oferta_agree_date', $now );Метод CRUD API$order->update_meta_data()абстрагирует работу с БД. WooCommerce сам решает, куда именно записать данные — в старыйpostmetaили в новую структуру HPOS.
💡 Префикс
_(нижнее подчёркивание): Использование префикса_rb_делает эти метаданные «скрытыми» по умолчанию, чтобы они не замусоривали стандартный интерфейс произвольных полей WordPress.
Шаг 4: Отображение данных в админке
PHP
add_action( 'woocommerce_admin_order_data_after_billing_address', 'rb_show_agreement_in_admin' );
- Хук:
woocommerce_admin_order_data_after_billing_addressвстраивает наш HTML-блок в панель управления заказом — сразу под блоком «Адрес плательщика». - Проверка типа объекта:PHP
if ( ! is_a( $order, 'WC_Order' ) ) { return; }Защита от сбоев. Если сторонний плагин вызовет этот хук и передаст не объект заказа, код корректно завершит работу без фатальной ошибки (Fatal Error). - Чтение данных:PHP
$oferta = $order->get_meta( '_rb_oferta_agree' );Снова используется стандартный метод$order->get_meta(), гарантирующий корректную чтение как с HPOS, так и без него.

Шаг 5: Отключение стандартных «Термсов» WooCommerce
PHP
add_filter( 'woocommerce_get_terms_page_id', '__return_zero' );
- Зачем нужно: У WooCommerce есть встроенный функционал чекбокса "Terms and Conditions". Если он включен в настройках магазина, то на чекауте появится дублирующий чекбокс.
- Как работает: Данный фильтр принудительно заставляет WooCommerce считать, что страница условий не задана (
0), благодаря чему встроенный чекбокс полностью скрывается, уступая место нашей кастомной системе.
Как внедрить на сайт
- Скопируйте весь код.
- Откройте файл
functions.phpвашей активной дочерней темы (или используйте плагин Code Snippets). - Замените URL-адреса
https://moysite.com/на ссылки вашего проекта. - Вставьте код и сохраните изменения.
