Давайте сделаем товар в подарок по купону. Добавьте сниппет-код:
/*
Plugin Name: Товар в подарок
Description: По купону товар в подарок
Version: 1.0.0
Author: avs-alexandra
Author URI: https://github.com/avs-alexandra
*/
/* ----------------------------------------
* 1. Добавляем тип купона
* ---------------------------------------- */
add_filter('woocommerce_coupon_discount_types', function($types) {
$types['free_gift'] = 'Товар в подарок';
return $types;
});
/* ----------------------------------------
* 2. Поле выбора подарка в настройках купона
* ---------------------------------------- */
add_action('woocommerce_coupon_options_usage_restriction', function($coupon_id, $coupon) {
if (!is_object($coupon) || is_wp_error($coupon)) return;
if (!method_exists($coupon, 'is_type') || !$coupon->is_type('free_gift')) return;
$product_id = intval($coupon->get_meta('product_gift_single', true));
?>
<div class="options_group">
<p class="form-field">
<label>Товар в подарок</label>
<span style="display:flex; align-items:center; gap:6px;">
<select class="wc-product-search"
style="width:260px;"
name="product_gift_single"
data-placeholder="Выберите товар…"
data-action="woocommerce_json_search_products_and_variations">
<option value=""></option>
<?php if ($product_id):
$p = wc_get_product($product_id);
if ($p): ?>
<option value="<?php echo esc_attr($product_id); ?>" selected>
<?php echo esc_html(wp_strip_all_tags($p->get_formatted_name())); ?>
</option>
<?php endif;
endif; ?>
</select>
<button type="button"
class="button"
id="clear-gift-product"
style="line-height:1; padding:0 6px 2px;">
✕
</button>
</span>
<script>
jQuery(function($){
$('#clear-gift-product').on('click', function(){
let $sel = $('select[name="product_gift_single"]');
$sel.val('').trigger('change');
});
});
</script>
</p>
</div>
<?php
}, 10, 2);
/* ----------------------------------------
* 3. Сохранение выбранного товара
* ---------------------------------------- */
add_action('woocommerce_coupon_options_save', function($coupon_id, $coupon) {
if (!is_object($coupon) || is_wp_error($coupon)) return;
$product_id = isset($_POST['product_gift_single']) ? intval($_POST['product_gift_single']) : 0;
if (!$product_id) {
$coupon->update_meta_data('product_gift_single', '');
} else {
$coupon->update_meta_data('product_gift_single', $product_id);
}
$coupon->save();
}, 10, 2);
/* ----------------------------------------
* 4. Добавляем подарок при применении купона
* ---------------------------------------- */
add_action('woocommerce_applied_coupon', function($coupon_code) {
if ( empty($coupon_code) ) return;
$coupon = new WC_Coupon( $coupon_code );
if ( is_wp_error($coupon) || ! is_object($coupon) ) return;
if ( ! $coupon->is_type('free_gift') ) return;
$product_id = intval( $coupon->get_meta('product_gift_single', true) );
if ( ! $product_id ) return;
$product = wc_get_product($product_id);
if ( ! $product || ! $product->is_purchasable() || ! $product->is_in_stock() ) return;
if ( ! WC()->cart ) return;
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( intval($cart_item['product_id']) === $product_id ) return;
}
WC()->cart->add_to_cart(
$product_id,
1,
0,
array(),
array(
'free_gift' => $coupon_code,
)
);
});
/* ----------------------------------------
* 5. Удаляем подарок при удалении купона
* ---------------------------------------- */
add_action('woocommerce_removed_coupon', function($coupon_code) {
if ( empty($coupon_code) || ! WC()->cart ) return;
foreach ( WC()->cart->get_cart() as $cart_key => $cart_item ) {
if ( ! empty($cart_item['free_gift']) && $cart_item['free_gift'] === $coupon_code ) {
WC()->cart->remove_cart_item( $cart_key );
}
}
});
/* ----------------------------------------
* 6. Чистим подарок при загрузке корзины
* ---------------------------------------- */
add_action('woocommerce_cart_loaded_from_session', function() {
if ( ! WC()->cart ) return;
$applied = WC()->cart->get_applied_coupons();
foreach ( WC()->cart->get_cart() as $cart_key => $cart_item ) {
if ( ! empty($cart_item['free_gift']) ) {
if ( ! in_array( $cart_item['free_gift'], (array) $applied, true ) ) {
WC()->cart->remove_cart_item( $cart_key );
}
}
}
});
/* ----------------------------------------
* 7. Цена подарка = 0
* ---------------------------------------- */
add_filter('woocommerce_add_cart_item', function($cart_item) {
if ( ! empty($cart_item['free_gift']) && isset($cart_item['data']) ) {
// Установим цену товара в 0
$cart_item['data']->set_price(0);
// Защита: обеспечим наличие ключей line_total/line_subtotal
if ( ! isset( $cart_item['line_total'] ) ) {
$cart_item['line_total'] = 0;
}
if ( ! isset( $cart_item['line_subtotal'] ) ) {
$cart_item['line_subtotal'] = 0;
}
}
return $cart_item;
}, 20);
add_filter('woocommerce_get_cart_item_from_session', function($cart_item, $values) {
if ( ! empty($values['free_gift']) ) {
$cart_item['free_gift'] = $values['free_gift'];
if ( isset($cart_item['data']) ) {
$cart_item['data']->set_price(0);
}
// Защита: гарантируем числовые итоги для подарка
if ( ! isset( $cart_item['line_total'] ) ) {
$cart_item['line_total'] = 0;
}
if ( ! isset( $cart_item['line_subtotal'] ) ) {
$cart_item['line_subtotal'] = 0;
}
}
return $cart_item;
}, 20, 2);
/* ----------------------------------------
* 8. Блокируем изменение количества
* ---------------------------------------- */
add_filter('woocommerce_cart_item_quantity', function($product_quantity, $cart_item_key, $cart_item) {
if ( ! empty($cart_item['free_gift']) ) {
$qty = isset($cart_item['quantity']) ? intval($cart_item['quantity']) : 1;
return '<input type="hidden" class="qty" name="cart[' . esc_attr($cart_item_key) . '][qty]" value="' . esc_attr($qty) . '">'
. '<span class="gift-qty-display" aria-hidden="true">1</span>';
}
return $product_quantity;
}, 1000, 3);
/* ----------------------------------------
* 9. Скрываем цену подарка
* ---------------------------------------- */
add_filter('woocommerce_cart_item_price', function($price_html, $cart_item) {
if ( ! empty($cart_item['free_gift']) ) return '';
return $price_html;
}, 1000, 2);
/* ----------------------------------------
* 10. Пишем "Подарок" в сумме
* ---------------------------------------- */
add_filter('woocommerce_cart_item_subtotal', function($subtotal, $cart_item) {
if ( ! empty($cart_item['free_gift']) ) return 'Подарок';
return $subtotal;
}, 1000, 2);
/* ----------------------------------------
* 11. Если подарок удалили — удаляем купон
* ---------------------------------------- */
add_action('woocommerce_cart_item_removed', function($cart_item_key, $cart) {
$removed = $cart->removed_cart_contents[ $cart_item_key ] ?? null;
if ( ! $removed ) return;
if ( ! empty($removed['free_gift']) ) {
$coupon_code = $removed['free_gift'];
if ( in_array($coupon_code, (array) $cart->get_applied_coupons(), true) ) {
$cart->remove_coupon( $coupon_code );
wc_add_notice('Купон удалён, так как подарок был удалён из корзины.', 'notice');
}
}
}, 10, 2);
/* ----------------------------------------
* 12. Если товара в подарок нет в наличии выводим уведомление
* ---------------------------------------- */
add_filter('woocommerce_coupon_is_valid', function($valid, $coupon, $discount) {
if ( ! is_object($coupon) || ! $coupon->is_type('free_gift') ) {
return $valid;
}
$product_id = intval($coupon->get_meta('product_gift_single', true));
if ( ! $product_id ) {
return $valid;
}
$product = wc_get_product($product_id);
if ( ! $product || ! $product->is_purchasable() || ! $product->is_in_stock() ) {
wc_add_notice(
'Подарок по купону недоступен, так как товар закончился на складе.',
'error'
);
return false;
}
return $valid;
}, 10, 3);Появится новый вид купона «Товар в подарок».

Сохраните (опубликуйте) купон, чтобы появилось поле для выбора товара.

Вот так выглядит в классической корзине применение купона:

Если товар в подарок закончится, то появится сообщение «Подарок по купону недоступен, так как товар закончился на складе.» Ведь в реальности товары в подарок не бесконечны =)

Также при удалении товара в подарок, будет удаляться купон. И при удалении купона — удаляться товар в подарок.
Если применили купон, а потом закончился товар в подарок, то купон просто сам удалится, а вместе с ним и товар (это стандартное поведение woocommerce при не выполнении условия купона).
Если сделать заказ с товаром в подарок, перейти в личный кабинет Заказы и нажать «Заказать снова», то этот товар попадёт в корзину. Поэтому цену товара в подарок ставьте не 0 руб, а обычную или высокую.
Либо можно вообще запретить покупать этот товар без купона, но надо будет немного изменить код.
Вот и всё, этот сниппет не повлияет на скорость и нагрузку сайта, безопасен, протестирован с woocommerce 10.3.5