Skip to main content
Login Join
Snippet · PHP

Inline Collapsible Coupon At Checkout

Shared by Vaibhav Singh Web · June 15, 2026 · @woocommerce_review_order_before_payment

24 views
1 upvote
Back to Snippets

Inline Collapsible Coupon At Checkout

The toggle button collapses/expands the input field.

 

Quick setup checklist:

<?php // Remove If Already Exists
/**
 *  Inline Collapsible Coupon At Checkout
 * -------------------------------------------------------------
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/* -------------------------------------------------------------
 * 1. Remove WooCommerce's default Coupon
 * ----------------------------------------------------------- */
add_action( 'init', function () {
	remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form', 10 );
} );

/* -------------------------------------------------------------
 * 2. Custom coupon box inside the order review.
 *    Auto re-renders on update_checkout => stays in sync.
 * ----------------------------------------------------------- */
add_action( 'woocommerce_review_order_before_payment', 'sscoupon_render_box', 10 );
function sscoupon_render_box() {
	if ( ! wc_coupons_enabled() ) {
		return;
	}
	?>
	<div class="sscoupon-wrap">
		<div class="sscoupon-form">
			<input type="text" id="sscoupon-code" class="sscoupon-input"
				placeholder="<?php esc_attr_e( 'Discount code', 'woocommerce' ); ?>"
				autocomplete="off" />
			<button type="button" id="sscoupon-apply" class="sscoupon-btn">
				<?php esc_html_e( 'Apply', 'woocommerce' ); ?>
			</button>
		</div>

		<div class="sscoupon-message" aria-live="polite"></div>

		<div class="sscoupon-active">
			<?php sscoupon_render_active(); ?>
		</div>
	</div>
	<?php
}

/* -------------------------------------------------------------
 * 3. Applied coupons as removable chips
 * ----------------------------------------------------------- */
function sscoupon_render_active() {
	$applied = WC()->cart->get_applied_coupons();
	if ( empty( $applied ) ) {
		return;
	}
	foreach ( $applied as $code ) {
		$amount = WC()->cart->get_coupon_discount_amount( $code, WC()->cart->display_cart_ex_tax );
		?>
		<div class="sscoupon-chip">
			<span class="sscoupon-chip-tag">
				<svg viewBox="0 0 20 20" width="13" height="13" aria-hidden="true">
					<path fill="currentColor" d="M9.05 2H4a2 2 0 0 0-2 2v5.05c0 .53.21 1.04.59 1.41l6.95 6.95a2 2 0 0 0 2.82 0l5.05-5.05a2 2 0 0 0 0-2.82l-6.95-6.95A2 2 0 0 0 9.05 2ZM6 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
				</svg>
				<?php echo esc_html( strtoupper( $code ) ); ?>
			</span>
			<span class="sscoupon-chip-amt">−<?php echo wp_kses_post( wc_price( $amount ) ); ?></span>
			<a href="#" class="sscoupon-remove" data-code="<?php echo esc_attr( $code ); ?>"
				aria-label="<?php esc_attr_e( 'Remove coupon', 'woocommerce' ); ?>">×</a>
		</div>
		<?php
	}
}

/* -------------------------------------------------------------
 * 4. AVAILABLE COUPONS list (all active/published coupons)
 *    Rendered right under the box; re-renders on update_checkout.
 * ----------------------------------------------------------- */
add_action( 'woocommerce_review_order_before_payment', 'sscoupon_render_available', 15 );
function sscoupon_render_available() {
	if ( ! wc_coupons_enabled() ) {
		return;
	}

	$coupons = sscoupon_get_active_coupons();
	if ( empty( $coupons ) ) {
		return;
	}
	?>
	<div class="sscoupon-list">
		<div class="sscoupon-list-head"><?php esc_html_e( 'Available coupons', 'woocommerce' ); ?></div>

		<?php foreach ( $coupons as $coupon ) :
			$code = $coupon->get_code();
			// Skip coupons that are already applied (they show as chips above).
			if ( WC()->cart->has_discount( $code ) ) {
				continue;
			}
			?>
			<div class="sscoupon-card">
				<div class="sscoupon-card-ico" aria-hidden="true">
					<svg viewBox="0 0 24 24" width="20" height="20">
						<path fill="currentColor" d="M12 1.5l2.1 1.6 2.6-.3 1 2.4 2.4 1-.3 2.6L21.5 12l-1.6 2.1.3 2.6-2.4 1-1 2.4-2.6-.3L12 22.5l-2.1-1.6-2.6.3-1-2.4-2.4-1 .3-2.6L2.5 12l1.6-2.1-.3-2.6 2.4-1 1-2.4 2.6.3L12 1.5z"/>
						<text x="12" y="15.5" text-anchor="middle" font-size="9" font-weight="700" fill="#fff" font-family="Arial">%</text>
					</svg>
				</div>

				<div class="sscoupon-card-body">
					<span class="sscoupon-card-code"><?php echo esc_html( strtoupper( $code ) ); ?></span>
					<p class="sscoupon-card-desc"><?php echo esc_html( sscoupon_describe( $coupon ) ); ?></p>

					<a href="#" class="sscoupon-knowmore"><?php esc_html_e( 'Know more', 'woocommerce' ); ?></a>
					<div class="sscoupon-details" hidden>
						<?php echo wp_kses_post( sscoupon_details( $coupon ) ); ?>
					</div>
				</div>

				<button type="button" class="sscoupon-card-apply" data-code="<?php echo esc_attr( $code ); ?>">
					<?php esc_html_e( 'Apply', 'woocommerce' ); ?>
				</button>
			</div>
		<?php endforeach; ?>
	</div>
	<?php
}

/**
 * Fetch active (published, non-expired) coupons.
 * Filter 'sscoupon_active_coupons' lets you customise the list.
 *
 * @return WC_Coupon[]
 */
function sscoupon_get_active_coupons() {
	$ids = get_posts( array(
		'post_type'      => 'shop_coupon',
		'post_status'    => 'publish',
		'posts_per_page' => 20,
		'orderby'        => 'menu_order title',
		'order'          => 'ASC',
		'fields'         => 'ids',
	) );

	$now     = time();
	$coupons = array();

	foreach ( $ids as $id ) {
		$coupon  = new WC_Coupon( $id );
		$expires = $coupon->get_date_expires();

		// "Active" = not expired. (No min-spend / eligibility checks.)
		if ( $expires && $expires->getTimestamp() < $now ) {
			continue;
		}
		$coupons[] = $coupon;
	}

	return apply_filters( 'sscoupon_active_coupons', $coupons );
}

/** Short description shown on the card. Uses the coupon's own description, else a fallback. */
function sscoupon_describe( $coupon ) {
	$desc = trim( $coupon->get_description() );
	if ( '' !== $desc ) {
		return $desc;
	}
	$amount = $coupon->get_amount();
	if ( 'percent' === $coupon->get_discount_type() ) {
		return sprintf( esc_html__( 'FLAT %s%% off', 'woocommerce' ), wc_format_decimal( $amount, '' ) );
	}
	return sprintf( esc_html__( '%s off', 'woocommerce' ), wp_strip_all_tags( wc_price( $amount ) ) );
}

/** Extra info revealed by "Know more". */
function sscoupon_details( $coupon ) {
	$lines = array();

	if ( $coupon->get_minimum_amount() > 0 ) {
		$lines[] = sprintf( esc_html__( 'Minimum order value: %s', 'woocommerce' ), wp_strip_all_tags( wc_price( $coupon->get_minimum_amount() ) ) );
	}
	if ( $coupon->get_maximum_amount() > 0 ) {
		$lines[] = sprintf( esc_html__( 'Maximum order value: %s', 'woocommerce' ), wp_strip_all_tags( wc_price( $coupon->get_maximum_amount() ) ) );
	}
	if ( $coupon->get_date_expires() ) {
		$lines[] = sprintf( esc_html__( 'Valid till %s', 'woocommerce' ), esc_html( date_i18n( get_option( 'date_format' ), $coupon->get_date_expires()->getTimestamp() ) ) );
	}
	if ( $coupon->get_free_shipping() ) {
		$lines[] = esc_html__( 'Free shipping included', 'woocommerce' );
	}
	if ( empty( $lines ) ) {
		$lines[] = esc_html__( 'Apply at checkout to avail this offer.', 'woocommerce' );
	}
	return '<ul><li>' . implode( '</li><li>', $lines ) . '</li></ul>';
}

/* -------------------------------------------------------------
 * 5. AJAX: apply coupon
 * ----------------------------------------------------------- */
add_action( 'wp_ajax_sscoupon_apply', 'sscoupon_apply' );
add_action( 'wp_ajax_nopriv_sscoupon_apply', 'sscoupon_apply' );
function sscoupon_apply() {
	check_ajax_referer( 'sscoupon-nonce', 'nonce' );
	$code = isset( $_POST['code'] ) ? wc_format_coupon_code( wp_unslash( $_POST['code'] ) ) : '';

	if ( '' === $code ) {
		wp_send_json_error( array( 'message' => __( 'Please enter a discount code.', 'woocommerce' ) ) );
	}

	$applied = WC()->cart->apply_coupon( $code );
	WC()->cart->calculate_totals();
	$messages = sscoupon_pull_notices();

	if ( $applied ) {
		wp_send_json_success( array( 'message' => $messages ? $messages : __( 'Discount applied.', 'woocommerce' ) ) );
	}
	wp_send_json_error( array( 'message' => $messages ? $messages : __( 'This discount code is not valid.', 'woocommerce' ) ) );
}

/* -------------------------------------------------------------
 * 6. AJAX: remove coupon
 * ----------------------------------------------------------- */
add_action( 'wp_ajax_sscoupon_remove', 'sscoupon_remove' );
add_action( 'wp_ajax_nopriv_sscoupon_remove', 'sscoupon_remove' );
function sscoupon_remove() {
	check_ajax_referer( 'sscoupon-nonce', 'nonce' );
	$code = isset( $_POST['code'] ) ? wc_format_coupon_code( wp_unslash( $_POST['code'] ) ) : '';
	if ( '' !== $code ) {
		WC()->cart->remove_coupon( $code );
		WC()->cart->calculate_totals();
	}
	wc_clear_notices();
	wp_send_json_success();
}

/** Helper: collect & clear WooCommerce notices into one string */
function sscoupon_pull_notices() {
	$out     = array();
	$notices = wc_get_notices();
	foreach ( array( 'error', 'success', 'notice' ) as $type ) {
		if ( ! empty( $notices[ $type ] ) ) {
			foreach ( $notices[ $type ] as $n ) {
				$out[] = is_array( $n ) ? $n['notice'] : $n;
			}
		}
	}
	wc_clear_notices();
	return implode( ' ', array_map( 'wp_strip_all_tags', $out ) );
}

/* -------------------------------------------------------------
 * 7. CSS
 * ----------------------------------------------------------- */
add_action( 'wp_head', 'sscoupon_css' );
function sscoupon_css() {
	if ( ! function_exists( 'is_checkout' ) || ! is_checkout() ) {
		return;
	}
	?>
	<style>
	/* ---- Coupon input box ---- */
	.sscoupon-wrap{ margin:18px 0 14px; }
	.sscoupon-form{ display:flex; gap:12px; align-items:stretch; }
	.sscoupon-input{
		flex:1 1 auto; padding:14px 16px; border:1px solid #d6d6d6; border-radius:8px;
		font-size:14px; line-height:1.2; color:#2b2b2b; background:#fff; outline:none;
		transition:border-color .15s ease, box-shadow .15s ease;
	}
	.sscoupon-input::placeholder{ color:#9a9a9a; }
	.sscoupon-input:focus{ border-color:#1a1a1a; box-shadow:0 0 0 1px #1a1a1a; }
	.sscoupon-btn{
		flex:0 0 auto; min-width:84px; padding:0 22px; border:1px solid #e2e2e2;
		border-radius:8px; background:#f2f2f2; color:#6f6f6f; font-size:14px; font-weight:500;
		cursor:pointer; transition:background .15s ease, color .15s ease;
	}
	.sscoupon-btn:hover{ background:#e7e7e7; color:#3d3d3d; }
	.sscoupon-btn.is-loading{ opacity:.6; pointer-events:none; }

	.sscoupon-message{ font-size:13px; margin-top:8px; }
	.sscoupon-message.is-error{ color:#c0392b; }
	.sscoupon-message.is-success{ color:#1e7e34; }

	/* ---- Applied chips ---- */
	.sscoupon-active{ margin-top:12px; display:flex; flex-direction:column; gap:8px; }
	.sscoupon-chip{
		display:flex; align-items:center; gap:8px; padding:8px 12px; background:#f6f6f6;
		border:1px solid #ececec; border-radius:8px; font-size:13px;
	}
	.sscoupon-chip-tag{ display:inline-flex; align-items:center; gap:6px; font-weight:600; color:#1a1a1a; }
	.sscoupon-chip-amt{ color:#1e7e34; font-weight:600; }
	.sscoupon-remove{
		margin-left:auto; text-decoration:none; color:#9a9a9a; font-size:18px; line-height:1;
		width:20px; height:20px; display:inline-flex; align-items:center; justify-content:center;
		border-radius:50%; transition:background .15s ease, color .15s ease;
	}
	.sscoupon-remove:hover{ background:#e2e2e2; color:#1a1a1a; }

	/* ---- Available coupons list ---- */
	.sscoupon-list{ margin-top:6px; display:flex; flex-direction:column; gap:12px; }
	.sscoupon-list-head{ font-size:13px; font-weight:600; color:#444; margin:6px 0 2px; }

	.sscoupon-card{
		display:flex; align-items:flex-start; gap:12px;
		padding:14px 16px; background:#fafafa; border:1px solid #ededed; border-radius:12px;
	}
	.sscoupon-card-ico{ flex:0 0 auto; color:#d6336c; margin-top:2px; }

	.sscoupon-card-body{ flex:1 1 auto; min-width:0; }
	.sscoupon-card-code{
		display:inline-block; position:relative; padding:3px 12px; margin-bottom:6px;
		font-size:14px; font-weight:700; letter-spacing:.04em; color:#1a1a1a;
		border:1px dashed #c7c7c7; border-radius:4px; background:#fff;
	}
	/* little ticket notches */
	.sscoupon-card-code::before,
	.sscoupon-card-code::after{
		content:""; position:absolute; top:50%; width:7px; height:7px;
		background:#fafafa; border:1px dashed #c7c7c7; border-radius:50%; transform:translateY(-50%);
	}
	.sscoupon-card-code::before{ left:-4px; }
	.sscoupon-card-code::after{ right:-4px; }

	.sscoupon-card-desc{ margin:2px 0 4px; font-size:13px; color:#555; line-height:1.4; }
	.sscoupon-knowmore{ font-size:12px; color:#3b82f6; text-decoration:none; }
	.sscoupon-knowmore:hover{ text-decoration:underline; }
	.sscoupon-details{ margin-top:6px; font-size:12px; color:#666; }
	.sscoupon-details ul{ margin:0; padding-left:16px; }
	.sscoupon-details li{ margin:2px 0; }

	.sscoupon-card-apply{
		flex:0 0 auto; align-self:center; min-width:74px; padding:9px 18px;
		border:1px solid #cfcfcf; border-radius:8px; background:#fff; color:#333;
		font-size:13px; font-weight:600; cursor:pointer;
		transition:background .15s ease, border-color .15s ease, color .15s ease;
	}
	.sscoupon-card-apply:hover{ background:#1a1a1a; border-color:#1a1a1a; color:#fff; }
	.sscoupon-card-apply.is-loading{ opacity:.6; pointer-events:none; }

	@media (max-width:480px){
		.sscoupon-card{ flex-wrap:wrap; }
		.sscoupon-card-apply{ align-self:flex-start; }
	}
	</style>
	<?php
}

/* -------------------------------------------------------------
 * 8. JS
 * ----------------------------------------------------------- */
add_action( 'wp_footer', 'sscoupon_js' );
function sscoupon_js() {
	if ( ! function_exists( 'is_checkout' ) || ! is_checkout() ) {
		return;
	}
	$ajax  = admin_url( 'admin-ajax.php' );
	$nonce = wp_create_nonce( 'sscoupon-nonce' );
	?>
	<script>
	(function ($) {
		var AJAX  = '<?php echo esc_js( $ajax ); ?>';
		var NONCE = '<?php echo esc_js( $nonce ); ?>';
		var pendingMsg = null;

		function showMsg(text, type) {
			$('.sscoupon-message')
				.removeClass('is-error is-success')
				.addClass(type === 'error' ? 'is-error' : 'is-success')
				.text(text || '');
		}

		function applyCode(code, $btn) {
			code = (code || '').trim();
			showMsg('', '');
			if ($btn) { $btn.addClass('is-loading'); }

			$.post(AJAX, { action: 'sscoupon_apply', code: code, nonce: NONCE })
				.done(function (res) {
					if (res && res.success) {
						pendingMsg = res.data.message;
						$('#sscoupon-code').val('');
						$(document.body).trigger('update_checkout'); // refresh totals, chips & list
					} else {
						showMsg(res && res.data ? res.data.message : 'Something went wrong.', 'error');
					}
				})
				.fail(function () { showMsg('Network error. Please try again.', 'error'); })
				.always(function () { if ($btn) { $btn.removeClass('is-loading'); } });
		}

		function removeCode(code) {
			$.post(AJAX, { action: 'sscoupon_remove', code: code, nonce: NONCE })
				.done(function () { $(document.body).trigger('update_checkout'); });
		}

		$(document.body).on('updated_checkout', function () {
			if (pendingMsg) { showMsg(pendingMsg, 'success'); pendingMsg = null; }
		});

		// Main box apply
		$(document).on('click', '#sscoupon-apply', function () {
			applyCode($('#sscoupon-code').val(), $(this));
		});
		$(document).on('keypress', '#sscoupon-code', function (e) {
			if (e.which === 13) { e.preventDefault(); applyCode($(this).val(), $('#sscoupon-apply')); }
		});

		// Card apply
		$(document).on('click', '.sscoupon-card-apply', function () {
			applyCode($(this).data('code'), $(this));
		});

		// Remove chip
		$(document).on('click', '.sscoupon-remove', function (e) {
			e.preventDefault();
			removeCode($(this).data('code'));
		});

		// Know more toggle
		$(document).on('click', '.sscoupon-knowmore', function (e) {
			e.preventDefault();
			var $d = $(this).siblings('.sscoupon-details');
			var open = !$d.prop('hidden');
			$d.prop('hidden', open);
			$(this).text(open ? '<?php echo esc_js( __( 'Know more', 'woocommerce' ) ); ?>'
			                   : '<?php echo esc_js( __( 'Hide details', 'woocommerce' ) ); ?>');
		});
	})(jQuery);
	</script>
	<?php
}

0 comments