Cart Dynamic Discount

Adds a progressive discount based on the shopping cart.

Codingbunny cart dynamic discount
PHPPHP
<?php
/**
 * Snippet Name:     Cart Dynamic Discount
 * Snippet Author:   coding-bunny.com
 * Description:      Adds a progressive discount based on the shopping cart.
 */

// === ADD DISCOUNT HERE ===
function cbpds_get_discount_tiers() {
    return [
        50  => 5,   // Example: €50 = 5% discount
150 => 10,
250 => 15,
500 => 25
];
}

add_action('woocommerce_cart_calculate_fees', 'cbpds_apply_cart_discount');
function cbpds_apply_cart_discount() {
if (is_admin() && !defined('DOING_AJAX')) return;
if (!WC()->cart) return;

    $cart_total = WC()->cart->get_subtotal();
$discount_percentage = cbpds_get_discount_percentage($cart_total);

    if ($discount_percentage > 0) {
$discount_amount = ($cart_total * $discount_percentage) / 100;
$discount_label = sprintf('Discount %d%% (-€%.2f)', $discount_percentage, $discount_amount);

        WC()->cart->add_fee($discount_label, -$discount_amount);
}
}

function cbpds_get_discount_percentage($total) {
$discount = 0;
foreach (cbpds_get_discount_tiers() as $threshold => $percentage) {
if ($total >= $threshold) {
$discount = $percentage;
}
}
return $discount;
}

function cbpds_calculate_progress($total) {
$tiers = cbpds_get_discount_tiers();
$tier_keys = array_keys($tiers);
$current_tier = 0;
$current_discount = 0;
$next_tier = $tier_keys[0];
$progress_percentage = 0;

    foreach ($tier_keys as $tier) {
if ($total >= $tier) {
$current_tier = $tier;
$current_discount = $tiers[$tier];
}
}

    foreach ($tier_keys as $tier) {
if ($total < $tier) {
            $next_tier = $tier;
            break;
        }
    }

    if ($total >= max($tier_keys)) {
$progress_percentage = 100;
$next_tier = null;
} else {
$previous_tier = 0;
foreach ($tier_keys as $tier) {
if ($tier < $next_tier) {
                $previous_tier = $tier;
            }
        }

        if ($next_tier > $previous_tier) {
$progress_percentage = (($total - $previous_tier) / ($next_tier - $previous_tier)) * 100;
}
}

    return [
'current_tier' => $current_tier,
'current_discount' => $current_discount,
'next_tier' => $next_tier,
'progress_percentage' => min(100, max(0, $progress_percentage)),
'remaining_amount' => $next_tier ? max(0, $next_tier - $total) : 0
];
}

function cbpds_render_progress_markup($cart_total) {
$progress_data = cbpds_calculate_progress($cart_total);
$tiers = cbpds_get_discount_tiers();

    $steps_html = '';
foreach ($tiers as $tier => $discount) {
$is_achieved = $cart_total >= $tier;
$is_current = !$is_achieved && $progress_data['next_tier'] == $tier;
$step_class = $is_achieved ? 'achieved' : ($is_current ? 'current' : '');
$check_html = $is_achieved ? '</p>
<div class="cbpds-minimal-check">✓</div>
<p>' : '';

        $steps_html .= sprintf(
'</p>
<div class="cbpds-minimal-step %s">
<div class="cbpds-spend-row"><span class="cbpds-label">Spend</span><span class="cbpds-amount">€%d</span></div>
<div class="cbpds-discount-row"><span class="cbpds-label">Discount</span><span class="cbpds-percent">%d%%</span></div>
<p>%s</p></div>
<p>',
$step_class,
$tier,
$discount,
$check_html
);
}

    if ($progress_data['next_tier']) {
$remaining_html = '';
if ($progress_data['remaining_amount'] > 0) {
$remaining_html = sprintf(
'€%s remaining for %d%% discount',
number_format($progress_data['remaining_amount'], 2),
$tiers[$progress_data['next_tier']]
);
}
$progress_html = sprintf(
'</p>
<div class="cbpds-minimal-progress">
<div class="cbpds-minimal-bar">
<div class="cbpds-minimal-fill" style="width: %s%%"></div>
</div>
<div class="cbpds-minimal-text">%s</div>
</div>
<p>',
$progress_data['progress_percentage'],
$remaining_html
);
} else {
$progress_html = '</p>
<div class="cbpds-minimal-complete">Maximum discount reached</div>
<p>';
}

    return '</p>
<div class="cbpds-minimal-steps">' . $steps_html . '</div>
<p>' . $progress_html;
}

add_action('wp_ajax_cbpds_update_progress', 'cbpds_ajax_update_progress');
add_action('wp_ajax_nopriv_cbpds_update_progress', 'cbpds_ajax_update_progress');

function cbpds_ajax_update_progress() {
if (!check_ajax_referer('cbpds_nonce', 'nonce', false)) {
wp_send_json_error(['message' => 'Security check failed'], 403);
}

    $cart_total = WC()->cart ? WC()->cart->get_subtotal() : 0;
$progress_data = cbpds_calculate_progress($cart_total);

    wp_send_json_success([
'html' => cbpds_render_progress_markup($cart_total),
'cart_total' => $cart_total,
'current_discount' => $progress_data['current_discount']
]);
}

// === ADD HERE WHERE TO SHOW IT ===
add_action('woocommerce_after_add_to_cart_button', 'cbpds_show_minimal_progress_bar', 25);
add_action('woocommerce_before_cart_table', 'cbpds_show_minimal_progress_bar');
add_action('woocommerce_review_order_before_payment', 'cbpds_show_minimal_progress_bar');

function cbpds_show_minimal_progress_bar() {
static $assets_printed = false;

    $cart_total = WC()->cart ? WC()->cart->get_subtotal() : 0;

    echo '</p>
<div class="cbpds-minimal-container cbpds-progress-container">' . cbpds_render_progress_markup($cart_total) . '</div>
<p>';

    if ($assets_printed) {
return;
}
$assets_printed = true;

    $css = <<<'CSS'


<style>
.cbpds-minimal-container {
    background: #f8f9fa;
    border: 1px solid #e9ecef;
    border-radius: 6px;
    padding: 16px;
    margin: 16px 0;
    font-size: 14px;
    color: #495057;
}

.cbpds-minimal-steps {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}

.cbpds-minimal-step {
    flex: 1;
    min-width: 100px;
    text-align: center;
    padding: 12px 8px;
    border-radius: 4px;
    position: relative;
    transition: all 0.2s ease;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.cbpds-minimal-step:not(.achieved):not(.current) {
    background: #f8f9fa;
    border: 1px solid #dee2e6;
    color: #6c757d;
}

.cbpds-minimal-step.current {
    background: #fff3cd;
    border: 1px solid #ffeaa7;
    color: #856404;
}

.cbpds-minimal-step.achieved {
    background: #d4edda;
    border: 1px solid #c3e6cb;
    color: #155724;
}

.cbpds-spend-row,
.cbpds-discount-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}

.cbpds-label {
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    opacity: 0.7;
    white-space: nowrap;
}

.cbpds-amount,
.cbpds-percent {
    font-size: 12px;
    font-weight: 700;
}

.cbpds-minimal-check {
    position: absolute;
    top: -2px;
    right: -2px;
    background: #28a745;
    color: white;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    font-size: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.cbpds-minimal-progress {
    margin-top: 12px;
}

.cbpds-minimal-bar {
    background: #e9ecef;
    border-radius: 3px;
    height: 6px;
    overflow: hidden;
    margin-bottom: 6px;
}

.cbpds-minimal-fill {
    background: #28a745;
    height: 100%;
    border-radius: 3px;
    transition: width 0.3s ease;
}

.cbpds-minimal-text {
    text-align: center;
    font-size: 12px;
    color: #6c757d;
    font-weight: 500;
}

.cbpds-minimal-complete {
    background: #d4edda;
    color: #155724;
    padding: 10px 12px;
    border-radius: 4px;
    text-align: center;
    font-weight: 500;
    border: 1px solid #c3e6cb;
    font-size: 13px;
}

@media (max-width: 768px) {
    .cbpds-minimal-container {
        padding: 12px;
        font-size: 13px;
    }

    .cbpds-minimal-steps {
        flex-direction: column;
        gap: 6px;
    }

    .cbpds-minimal-step {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
        padding: 12px;
        text-align: left;
        min-width: auto;
        gap: 8px;
    }

    .cbpds-spend-row,
    .cbpds-discount-row {
        justify-content: flex-start;
    }

    .cbpds-spend-row {
        order: 1;
    }

    .cbpds-discount-row {
        order: 2;
    }

    .cbpds-minimal-check {
        position: static;
        order: 3;
        margin-left: auto;
    }
}

@media (max-width: 480px) {
    .cbpds-minimal-container {
        margin: 12px 0;
        padding: 10px;
    }

    .cbpds-amount {
        font-size: 12px;
    }

    .cbpds-percent {
        font-size: 14px;
    }

    .cbpds-label {
        font-size: 9px;
    }
}
</style>
<p>CSS;

    $js_template = <<<'JS'
<script>
jQuery(document).ready(function($) {
    var cbpds_nonce = '__CBPDS_NONCE__';
    var cbpds_ajax_url = '__CBPDS_AJAX_URL__';
    var cbpds_updating = false;

    function cbpds_update_progress() {
        if (cbpds_updating) return;
        cbpds_updating = true;

        $.post(cbpds_ajax_url, {
            action: 'cbpds_update_progress',
            nonce: cbpds_nonce
        })
        .done(function(response) {
            if (response.success) {
                $('.cbpds-progress-container').html(response.data.html);
                cbpds_bind_hover_events();
            }
        })
        .fail(function() {
            console.log('CBPDS: Error updating progress bar');
        })
        .always(function() {
            cbpds_updating = false;
        });
    }

    function cbpds_bind_hover_events() {
        $('.cbpds-minimal-step').off('mouseenter mouseleave').hover(
            function() {
                if (!$(this).hasClass('achieved')) {
                    $(this).css('transform', 'translateY(-1px)');
                }
            },
            function() {
                $(this).css('transform', 'translateY(0)');
            }
        );
    }

    cbpds_bind_hover_events();

    $(document.body).on('added_to_cart removed_from_cart updated_cart_totals updated_checkout', function() {
        setTimeout(cbpds_update_progress, 100);
    });

    $(document.body).on('wc_fragments_refreshed wc_fragments_loaded', function() {
        setTimeout(cbpds_update_progress, 100);
    });

    $(document).on('change', '.qty', function() {
        setTimeout(cbpds_update_progress, 500);
    });

    $('form.cart').on('submit', function() {
        setTimeout(cbpds_update_progress, 1000);
    });

    if ($('body').hasClass('woocommerce-cart') || $('body').hasClass('woocommerce-checkout')) {
        setInterval(cbpds_update_progress, 3000);
    }
});
</script>
JS;

    $js = str_replace(
['__CBPDS_NONCE__', '__CBPDS_AJAX_URL__'],
[wp_create_nonce('cbpds_nonce'), admin_url('admin-ajax.php')],
$js_template
);

    echo $css;
echo $js;
}