Shortcode – (Subscriptions) Viser prorata og næste betaling

Denne shortcode bruges til at der i kurvn og Popup kurv kan vises prorata og næste måned betaling for WooCommerce produkter.

Visningen er således:
Første betaling, indeværende måned: 00,00 kr.
Herefter pris, pr. md.: 00,00 kr.

Teksten kan manuelt ændres i koden.
Styling skal laves under Tilpasset CSS

Shortcoden fungere KUN på sider med udelukkende abonnementsprodukter.

Ønsker du at nogle elementer i kurven/popup kurven skal skjules hvis der ingen produkter er i kurven kan du i funktionen auxo_prorata_toggle_selectors() tilføje classes eller ID’er så vil de elementer blive skjult. For at tilføje flere skal du blot gøre således:
‘.elementor-element-ca12467’,
‘.elementor-element-ca123’,
‘3elementor-element-ca9123’,

Brug shortcode: [auxo_cart_prorata]

Indsættes i Snippets:

/**
 * Konfiguration: elementer der skal toggles
 * Skriv dine selectors her (ID'er, classes, eller andre CSS selectors)
 */
function auxo_prorata_toggle_selectors() {
    return [
        '.elementor-element-ca96345',
    ];
}

/**
 * Beregn og cache state pr. request (så vi ikke løber cart flere gange)
 */
function auxo_prorata_state() {
    static $cache = null;
    if ($cache !== null) return $cache;

    $zero_html = function_exists('wc_price') ? wc_price(0) : '0';

    $state = [
        'empty'  => true,
        'totals' => [
            'found'     => false,
            'now_raw'   => 0,
            'next_raw'  => 0,
            'now_html'  => $zero_html,
            'next_html' => $zero_html,
        ],
    ];

    if (!function_exists('WC') || !WC()->cart || !class_exists('WC_Subscriptions_Product')) {
        return $cache = $state;
    }

    $totals = auxo_calculate_cart_prorata_and_next();

    if (!empty($totals['found']) && (float) ($totals['next_raw'] ?? 0) > 0) {
        $state['empty']  = false;
        $state['totals'] = $totals;
    }

    return $cache = $state;
}

/**
 * Shortcode: [auxo_cart_prorata]
 * Returnerer "0" som element (kan toggles), og opdaterer indhold via fragments.
 */
add_shortcode('auxo_cart_prorata', 'auxo_cart_prorata_shortcode');
function auxo_cart_prorata_shortcode() {
    $state = auxo_prorata_state();

    // 0 som et element (kan toggles)
    $out  = '<span class="auxo-prorata-zero"' . ($state['empty'] ? '' : ' style="display:none;"') . '>0</span>';

    // Content container (altid til stede, så fragments kan erstatte den)
    if ($state['empty']) {
        $out .= '<div id="auxo-cart-prorata" class="auxo-cart-prorata" style="display:none;"></div>';
    } else {
        $out .= auxo_render_cart_prorata_markup($state['totals']);
    }

    // Hook + JS
    $out .= auxo_prorata_hook_span($state['empty']);
    $out .= auxo_prorata_toggle_js_once();

    return $out;
}

/**
 * Fragments: opdater kun hook-span + selve content div (2 fragments)
 */
add_filter('woocommerce_add_to_cart_fragments', 'auxo_prorata_fragments');
function auxo_prorata_fragments($fragments) {
    $state = auxo_prorata_state();

    // Hook-span (til toggling)
    $fragments['span.auxo-prorata-hook'] = auxo_prorata_hook_span($state['empty']);

    // Content (prorata + næste måned)
    if ($state['empty']) {
        $fragments['div#auxo-cart-prorata'] = '<div id="auxo-cart-prorata" class="auxo-cart-prorata" style="display:none;"></div>';
    } else {
        $fragments['div#auxo-cart-prorata'] = auxo_render_cart_prorata_markup($state['totals']);
    }

    return $fragments;
}

/**
 * Hook-span med selectors + empty flag (opdateres via fragments)
 */
function auxo_prorata_hook_span($is_empty) {
    $selectors = auxo_prorata_toggle_selectors();
    $selectors = is_array($selectors) ? array_values(array_filter($selectors)) : [];
    $selectors_csv = implode(',', $selectors);

    return '<span class="auxo-prorata-hook" data-auxo-empty="' . ($is_empty ? '1' : '0') . '" data-auxo-toggle="' . esc_attr($selectors_csv) . '" style="display:none;"></span>';
}

/**
 * JS toggler (indsættes kun én gang per side) + debounce for færre reflows
 */
function auxo_prorata_toggle_js_once() {
    static $printed = false;
    if ($printed) return '';
    $printed = true;

    ob_start(); ?>
    <script>
    jQuery(document).ready(function ($) {
        var auxoTimer = null;

        function auxoApplyToggleNow() {
            var $hook = $('span.auxo-prorata-hook').last();
            if (!$hook.length) return;

            var empty = String($hook.data('auxo-empty')) === '1';

            // Toggle "0"
            if (empty) {
                $('span.auxo-prorata-zero').show();
            } else {
                $('span.auxo-prorata-zero').hide();
            }

            var selectors = ($hook.data('auxo-toggle') || '').toString().trim();
            if (!selectors.length) return;

            selectors.split(',').forEach(function (sel) {
                sel = sel.trim();
                if (!sel) return;

                if (empty) {
                    $(sel).hide();
                } else {
                    $(sel).show();
                }
            });
        }

        function auxoApplyToggle() {
            clearTimeout(auxoTimer);
            auxoTimer = setTimeout(auxoApplyToggleNow, 80);
        }

        // ved load
        auxoApplyToggleNow();

        // reager primært på end-state events
        $(document.body).on('wc_fragments_refreshed updated_wc_div', auxoApplyToggle);

        // fallback events
        $(document.body).on('added_to_cart removed_from_cart updated_cart_totals wc_fragments_loaded', auxoApplyToggle);
    });
    </script>
    <?php
    return ob_get_clean();
}

/**
 * Render markup (kun når der er indhold)
 */
function auxo_render_cart_prorata_markup($totals) {
    $now  = $totals['now_html']  ?? (function_exists('wc_price') ? wc_price(0) : '0');
    $next = $totals['next_html'] ?? (function_exists('wc_price') ? wc_price(0) : '0');

    ob_start(); ?>
    <div id="auxo-cart-prorata" class="auxo-cart-prorata">
        <div class="auxo-row auxo-now">
            <strong>Første betaling, indeværende måned:</strong>
            <span class="auxo-value auxo-now-value"><?php echo wp_kses_post($now); ?></span>
        </div>
        <div class="auxo-row auxo-next">
            <strong>Herefter pris, pr. md.:</strong>
            <span class="auxo-value auxo-next-value"><?php echo wp_kses_post($next); ?></span>
        </div>
    </div>
    <?php
    return ob_get_clean();
}

/** Beregning (din eksisterende) */
function auxo_calculate_cart_prorata_and_next() {
    $now_total  = 0.0;
    $next_total = 0.0;
    $found = false;

    if (!function_exists('WC') || !WC()->cart || !class_exists('WC_Subscriptions_Product')) {
        return [
            'found'    => false,
            'now_raw'  => 0,
            'next_raw' => 0,
            'now_html' => function_exists('wc_price') ? wc_price(0) : '0',
            'next_html'=> function_exists('wc_price') ? wc_price(0) : '0',
        ];
    }

    $tz = wp_timezone();
    $today = new DateTime('now', $tz);

    $days_in_month  = (int) $today->format('t');
    $today_day      = (int) $today->format('j');
    $days_remaining = max(0, $days_in_month - $today_day + 1);

    foreach (WC()->cart->get_cart() as $cart_item) {
        $product = $cart_item['data'] ?? null;
        if (!$product || !is_a($product, 'WC_Product')) continue;

        if (!WC_Subscriptions_Product::is_subscription($product)) continue;

        $found = true;

        $qty = isset($cart_item['quantity']) ? (int) $cart_item['quantity'] : 1;

        $base = (float) WC_Subscriptions_Product::get_price($product);
        $base_display = (float) wc_get_price_to_display($product, ['price' => $base]);

        $period   = WC_Subscriptions_Product::get_period($product);
        $interval = (int) WC_Subscriptions_Product::get_interval($product);

        if ($period === 'month' && $interval === 1) {
            $next_total += ($base_display * $qty);

            $daily = $base_display / max(1, $days_in_month);
            $prorata = $daily * $days_remaining;

            $now_total += ($prorata * $qty);
        } else {
            $next_total += ($base_display * $qty);
        }
    }

    $decimals = (int) wc_get_price_decimals();
    $now_total  = round($now_total, $decimals);
    $next_total = round($next_total, $decimals);

    if (!$found) {
        return [
            'found'    => false,
            'now_raw'  => 0,
            'next_raw' => 0,
            'now_html' => wc_price(0),
            'next_html'=> wc_price(0),
        ];
    }

    return [
        'found'    => true,
        'now_raw'  => $now_total,
        'next_raw' => $next_total,
        'now_html' => wc_price($now_total),
        'next_html'=> wc_price($next_total),
    ];
}