You could adapt something like Move coupon form before subtotal in WooCommerce checkout answer code, but it will not work for many reasons…
Revisited updated answer (simplified without Ajax, just like WooCommerce default one):
// Just hide default woocommerce coupon field
add_action( 'woocommerce_before_checkout_form', 'hide_checkout_coupon_form', 5 );
function hide_checkout_coupon_form() {
echo '<style>.woocommerce-form-coupon-toggle {display:none;}</style>';
}
// Add a custom coupon field before checkout payment section
add_action( 'woocommerce_review_order_before_payment', 'woocommerce_checkout_coupon_form_custom' );
function woocommerce_checkout_coupon_form_custom() {
echo '<div class="checkout-coupon-toggle"><div class="woocommerce-info">' . sprintf(
__("Have a coupon? %s"), '<a href="#" class="show-coupon">' . __("Click here to enter your code") . '</a>'
) . '</div></div>';
echo '<div class="coupon-form" style="margin-bottom:20px;" style="display:none !important;">
<p>' . __("If you have a coupon code, please apply it below.") . '</p>
<p class="form-row form-row-first woocommerce-validated">
<input type="text" name="coupon_code" class="input-text" placeholder="' . __("Coupon code") . '" id="coupon_code" value="">
</p>
<p class="form-row form-row-last">
<button type="button" class="button" name="apply_coupon" value="' . __("Apply coupon") . '">' . __("Apply coupon") . '</button>
</p>
<div class="clear"></div>
</div>';
}
// jQuery code
add_action( 'wp_footer', 'custom_checkout_jquery_script' );
function custom_checkout_jquery_script() {
if ( is_checkout() && ! is_wc_endpoint_url() ) :
?>
<script type="text/javascript">
jQuery( function($){
$('.coupon-form').css("display", "none"); // Be sure coupon field is hidden
// Show or Hide coupon field
$('.checkout-coupon-toggle .show-coupon').on( 'click', function(e){
$('.coupon-form').toggle(200);
e.preventDefault();
})
// Copy the inputed coupon code to WooCommerce hidden default coupon field
$('.coupon-form input[name="coupon_code"]').on( 'input change', function(){
$('form.checkout_coupon input[name="coupon_code"]').val($(this).val());
// console.log($(this).val()); // Uncomment for testing
});
// On button click, submit WooCommerce hidden default coupon form
$('.coupon-form button[name="apply_coupon"]').on( 'click', function(){
$('form.checkout_coupon').submit();
// console.log('click: submit form'); // Uncomment for testing
});
});
</script>
<?php
endif;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Original first answer:
You will need something completely custom, to be able to make work a coupon input field just before checkout payment section. It additionally requires Ajax and jQuery code as follows:
// Remove default coupon field
remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form', 10 );
// Add a custom coupon field before checkout payment section
add_action( 'woocommerce_review_order_before_payment', 'woocommerce_checkout_coupon_form_custom' );
function woocommerce_checkout_coupon_form_custom() {
echo '<div class="coupon-form" style="margin-bottom:20px;">
<p>' . __("If you have a coupon code, please apply it below.") . '</p>
<p class="form-row form-row-first woocommerce-validated">
<input type="text" name="coupon_code" class="input-text" placeholder="' . __("Coupon code") . '" id="coupon_code" value="">
</p>
<p class="form-row form-row-last">
<button type="button" class="button" name="apply_coupon" value="' . __("Apply coupon") . '">' . __("Apply coupon") . '</button>
</p>
<div class="clear"></div>
</div>';
}
// jQuery - Send Ajax request
add_action( 'wp_footer', 'custom_checkout_jquery_script' );
function custom_checkout_jquery_script() {
if ( is_checkout() && ! is_wc_endpoint_url() ) :
?>
<script type="text/javascript">
jQuery( function($){
if (typeof wc_checkout_params === 'undefined')
return false;
var couponCode = '';
$('input[name="coupon_code"]').on( 'input change', function(){
couponCode = $(this).val();
});
$('button[name="apply_coupon"]').on( 'click', function(){
$.ajax({
type: 'POST',
url: wc_checkout_params.ajax_url,
data: {
'action': 'apply_checkout_coupon',
'coupon_code': couponCode,
},
success: function (response) {
$(document.body).trigger("update_checkout"); // Refresh checkout
$('.woocommerce-error,.woocommerce-message').remove(); // Remove other notices
$('input[name="coupon_code"]').val(''); // Empty coupon code input field
$('form.checkout').before(response); // Display notices
// console.log(response); // Uncomment for testing
}
});
});
});
</script>
<?php
endif;
}
// Ajax receiver function
add_action( 'wp_ajax_apply_checkout_coupon', 'apply_checkout_coupon_ajax_receiver' );
add_action( 'wp_ajax_nopriv_apply_checkout_coupon', 'apply_checkout_coupon_ajax_receiver' );
function apply_checkout_coupon_ajax_receiver() {
if ( isset($_POST['coupon_code']) && ! empty($_POST['coupon_code']) ) {
WC()->cart->add_discount( wc_format_coupon_code( wp_unslash( $_POST['coupon_code'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
} else {
wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
}
wc_print_notices();
wp_die();
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.