# fluent-cart/1.5.0/app/Services/Renderer/Receipt/TaxSummaryHelper.php

FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler, version 1.5.0. 398 lines.

- Page: https://pluginprobe.com/plugins/fluent-cart/1.5.0/code/app/Services/Renderer/Receipt/TaxSummaryHelper.php
- Raw: https://pluginprobe.com/plugins/fluent-cart/1.5.0/raw/app/Services/Renderer/Receipt/TaxSummaryHelper.php
- Modified: 2026-06-11T12:21:58+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-cart/1.5.0/code/app/Services/Renderer/Receipt/TaxSummaryHelper.php#L10-L20`.

```php
<?php

namespace FluentCart\App\Services\Renderer\Receipt;

use FluentCart\App\Models\Order;
use FluentCart\Framework\Support\Arr;

class TaxSummaryHelper
{
    /**
     * Compute inclusive/exclusive tax split from order data.
     *
     * Returns ['shouldRender' => false] when there is nothing to show.
     * Otherwise returns shouldRender, isReverseCharge, inclusiveTax, exclusiveTax,
     * shippingTax, payableTax, totalOrderTax — all amounts in cents.
     */
    public static function computeTaxSummary(Order $order)
    {
        $order->loadMissing(['orderTaxRates']);

        $isReverseCharge = $order->isReverseChargeTaxOrder();
        $inclusiveTax    = 0;
        $exclusiveTax    = 0;

        // Per-fee tax breakdown — stored at order placement, empty for old/fee-less orders.
        $feeTaxLines     = (array) $order->getMeta('fee_tax_lines', []);
        $inclusiveFeeTax = 0;
        $exclusiveFeeTax = 0;
        foreach ($feeTaxLines as $ftl) {
            if (!empty($ftl['inclusive'])) {
                $inclusiveFeeTax += (int) Arr::get($ftl, 'tax_amount', 0);
            } else {
                $exclusiveFeeTax += (int) Arr::get($ftl, 'tax_amount', 0);
            }
        }
        // Backward-compat: old orders persist fee_tax as a scalar with no fee_tax_lines array.
        if (empty($feeTaxLines)) {
            $legacyFeeTax = (int) $order->getMeta('fee_tax', 0);
            if ($legacyFeeTax > 0) {
                $exclusiveFeeTax = $legacyFeeTax;
                $feeTaxLines     = [
                    [
                        'label'      => __('Fee', 'fluent-cart'),
                        'tax_amount' => $legacyFeeTax,
                        'inclusive'  => false,
                    ],
                ];
            }
        }
        $totalFeeTax = $inclusiveFeeTax + $exclusiveFeeTax;

        if ($order->orderTaxRates && $order->orderTaxRates->count()) {
            foreach ($order->orderTaxRates as $rate) {
                $meta             = is_array($rate->meta) ? $rate->meta : [];
                $isMixedInclusive = (bool) Arr::get($meta, 'is_mixed_inclusive', false);
                if ($isMixedInclusive) {
                    list($rateIncl, $rateExcl) = self::splitMixedRateTax($order, (int) $rate->tax_rate_id);
                    if ($rateIncl === 0 && $rateExcl === 0 && (int) $rate->order_tax > 0) {
                        // No per-item breakdown (legacy order) — fall back to rate meta or tax_behavior.
                        if (isset($meta['inclusive'])) {
                            if ((bool) $meta['inclusive']) {
                                $inclusiveTax += (int) $rate->order_tax;
                            } else {
                                $exclusiveTax += (int) $rate->order_tax;
                            }
                        } elseif ((int) $order->tax_behavior === 2) {
                            $inclusiveTax += (int) $rate->order_tax;
                        } else {
                            $exclusiveTax += (int) $rate->order_tax;
                        }
                    } else {
                        $inclusiveTax += $rateIncl;
                        $exclusiveTax += $rateExcl;
                    }
                } elseif (isset($meta['inclusive'])) {
                    if ((bool) $meta['inclusive']) {
                        $inclusiveTax += (int) $rate->order_tax;
                    } else {
                        $exclusiveTax += (int) $rate->order_tax;
                    }
                } else {
                    if ((int) $order->tax_behavior === 2) {
                        $inclusiveTax += (int) $rate->order_tax;
                    } else {
                        $exclusiveTax += (int) $rate->order_tax;
                    }
                }
            }

            // Guard for orders where fct_order_tax_rate.order_tax was stored
            // incorrectly by an old bug. order.tax_total is the authoritative
            // value; if the rate-row sum differs, reset so the item-level
            // fallback below sums from order_items.tax_amount instead.
            // For mixed-inclusive orders, splitMixedRateTax() returns product-only
            // tax (fee items have empty line_meta and are skipped). Accept the sum
            // as valid when it equals order.tax_total minus the known fee tax.
            $orderTaxTotal       = (int) $order->tax_total;
            $productTaxFromRates = $inclusiveTax + $exclusiveTax;
            $productTaxExpected  = $orderTaxTotal - $totalFeeTax;
            if ($orderTaxTotal > 0
                && $productTaxFromRates !== $orderTaxTotal
                && $productTaxFromRates !== $productTaxExpected
            ) {
                $inclusiveTax = 0;
                $exclusiveTax = 0;
            }
            // For non-mixed rate rows the full order_tax (product+fee) is in exclusiveTax
            // or inclusiveTax — strip the fee portion so product tax is isolated.
            if ($exclusiveFeeTax > 0 && $exclusiveTax >= $exclusiveFeeTax) {
                $exclusiveTax -= $exclusiveFeeTax;
            }
            if ($inclusiveFeeTax > 0 && $inclusiveTax >= $inclusiveFeeTax) {
                $inclusiveTax -= $inclusiveFeeTax;
            }
        } else {
            $isInclusive   = (int) $order->tax_behavior === 2;
            $orderTaxTotal = max(0, (int) $order->tax_total - $totalFeeTax);
            $inclusiveTax  = $isInclusive ? $orderTaxTotal : 0;
            $exclusiveTax  = $isInclusive ? 0 : $orderTaxTotal;
        }

        // Fallback: sum item-level tax_amount when order.tax_total was never written (e.g. admin-created orders).
        if ($inclusiveTax === 0 && $exclusiveTax === 0) {
            $order->loadMissing(['order_items']);
            if ($order->order_items) {
                $taxBehavior = (int) $order->tax_behavior;
                foreach ($order->order_items as $item) {
                    if ($item->payment_type === 'fee') {
                        continue;
                    }
                    $itemTax = (int) round($item->tax_amount);
                    if ($itemTax <= 0) {
                        continue;
                    }
                    if ($taxBehavior === 3) {
                        $lineMeta      = $item->line_meta;
                        $lineInclusive = (bool) Arr::get($lineMeta, 'tax_config.inclusive', false);
                        if ($lineInclusive) {
                            $inclusiveTax += $itemTax;
                        } else {
                            $exclusiveTax += $itemTax;
                        }
                    } elseif ($taxBehavior === 1) {
                        $exclusiveTax += $itemTax;
                    } else {
                        $inclusiveTax += $itemTax;
                    }
                }
            }
        }

        $shippingTax         = (int) $order->shipping_tax;
        $isShippingInclusive = self::isShippingTaxInclusive($order);
        $payableTax          = $exclusiveTax + $exclusiveFeeTax + ($isShippingInclusive ? 0 : $shippingTax);
        $totalOrderTax       = $inclusiveTax + $inclusiveFeeTax + ($isShippingInclusive ? $shippingTax : 0) + $payableTax;

        if ($inclusiveTax === 0 && $inclusiveFeeTax === 0 && $payableTax === 0 && !$isReverseCharge) {
            return ['shouldRender' => false];
        }

        $shouldRender = apply_filters('fluent_cart/tax_summary_should_render', true, $order);

        $reversedTaxTotal    = 0;
        $reversedShippingTax = 0;
        $rcPriceMode          = '';
        $rcShippingAdjustment = 0;
        $shippingNetStored    = false;
        if ($isReverseCharge) {
            $primaryRate = $order->orderTaxRates ? $order->orderTaxRates->first() : null;
            if ($primaryRate) {
                $meta = is_array($primaryRate->meta) ? $primaryRate->meta : [];
                $reversedTaxTotal    = (int) Arr::get($meta, 'reverse_charge_original_tax_total', 0);
                $reversedShippingTax = (int) Arr::get($meta, 'reverse_charge_original_shipping_tax', 0);
                $rcPriceMode         = (string) Arr::get($meta, 'reverse_charge_price_mode', 'fixed');
                $shippingNetStored   = !empty($meta['shipping_net_stored']);
            }
            // Only apply the display adjustment for orders where shipping_total in DB is still gross.
            // New orders (shipping_net_stored = true) already have net shipping in DB — no adjustment.
            if ($rcPriceMode === 'dynamic' && $isShippingInclusive && $reversedShippingTax > 0 && !$shippingNetStored) {
                $rcShippingAdjustment = $reversedShippingTax;
            }
        }
        // Show RC shipping strikethrough row only for exclusive shipping tax that was reversed.
        // For inclusive shipping it either already reduced the price (dynamic) or didn't change
        // it at all (fixed), so the strikethrough is misleading in both cases.
        $showRcShippingRow = $isReverseCharge && !$isShippingInclusive && $reversedShippingTax > 0;

        return [
            'shouldRender'        => (bool) $shouldRender,
            'isReverseCharge'     => $isReverseCharge,
            'inclusiveTax'        => $inclusiveTax,
            'exclusiveTax'        => $exclusiveTax,
            'feeTaxLines'         => $feeTaxLines,
            'feeTaxLineRows'      => self::buildFeeTaxLineRows($feeTaxLines),
            'inclusiveFeeTax'     => $inclusiveFeeTax,
            'shippingTax'         => $shippingTax,
            'shippingTaxLines'    => $order->getDisplayShippingTaxLines(),
            'payableTax'          => $payableTax,
            'totalOrderTax'       => $totalOrderTax,
            'isShippingInclusive' => $isShippingInclusive,
            'reversedTaxTotal'     => $reversedTaxTotal,
            'reversedShippingTax'  => $reversedShippingTax,
            'rcPriceMode'          => $rcPriceMode,
            'rcShippingAdjustment' => $rcShippingAdjustment,
            'rcTotalAdjustment'    => $rcShippingAdjustment,
            'showRcShippingRow'    => $showRcShippingRow,
        ];
    }

    /**
     * For a mixed-inclusive rate (same rate used inclusively on some items and exclusively on others),
     * read each order item's line_meta to produce the correct inclusive/exclusive split.
     * Handles both item shapes:
     *   - Current (all item types): line_meta.tax_config.rates[] + line_meta.tax_config.inclusive
     *   - Legacy signup-fee: line_meta.rates[] + line_meta.inclusive (no tax_config wrapper)
     * Returns [inclusiveTax, exclusiveTax] in cents.
     */
    private static function splitMixedRateTax(Order $order, $rateId)
    {
        $order->loadMissing(['order_items']);
        $incl = 0;
        $excl = 0;
        if (!$order->order_items) {
            return [0, 0];
        }
        foreach ($order->order_items as $item) {
            $lineMeta  = $item->line_meta;
            $taxConfig = Arr::get($lineMeta, 'tax_config');
            if (is_array($taxConfig)) {
                $rates         = Arr::get($taxConfig, 'rates', []);
                $lineInclusive = (bool) Arr::get($taxConfig, 'inclusive', false);
            } else {
                $rates         = Arr::get($lineMeta, 'rates', []);
                $lineInclusive = (bool) Arr::get($lineMeta, 'inclusive', false);
            }
            foreach ($rates as $rate) {
                if ((int) Arr::get($rate, 'rate_id', 0) !== $rateId) {
                    continue;
                }
                $taxAmount = (int) Arr::get($rate, 'tax_amount', 0);
                if ($taxAmount <= 0) {
                    continue;
                }
                if ($lineInclusive) {
                    $incl += $taxAmount;
                } else {
                    $excl += $taxAmount;
                }
            }
        }
        return [$incl, $excl];
    }

    /**
     * Extract per-rate tax breakdown from a single order item.
     *
     * Reads `line_meta.tax_config.rates`, filters out zero-amount entries, and
     * returns a flat array of rate rows. Returns [] for old items without
     * tax_config — callers must check for empty before looping.
     */
    public static function getItemTaxRates(array $item)
    {
        // Current items (all types): line_meta.tax_config.rates
        // Legacy signup_fee items: line_meta.rates (no tax_config wrapper — set directly from signup_fee_tax_config)
        $taxConfig = Arr::get($item, 'line_meta.tax_config');
        if (is_array($taxConfig)) {
            $rates     = Arr::get($taxConfig, 'rates', []);
            $inclusive = (bool) Arr::get($taxConfig, 'inclusive', false);
        } else {
            $rates     = Arr::get($item, 'line_meta.rates', []);
            $inclusive = (bool) Arr::get($item, 'line_meta.inclusive', false);
        }

        if (empty($rates)) {
            return [];
        }

        $result = [];
        foreach ($rates as $rate) {
            $taxAmount = (int) Arr::get($rate, 'tax_amount', 0);
            if ($taxAmount <= 0) {
                continue;
            }
            $result[] = [
                'label'        => Arr::get($rate, 'label') ?: __('Tax', 'fluent-cart'),
                'tax_amount'   => $taxAmount,
                'rate_percent' => max(0.0, (float) Arr::get($rate, 'rate_percent', 0)),
                'inclusive'    => $inclusive,
            ];
        }

        return $result;
    }

    /**
     * Determine whether the primary tax type for an order is inclusive.
     * Used for per-item pill display (fct_order_tax_rate has no order_item_id).
     */
    public static function isPrimaryTaxInclusive(Order $order)
    {
        $order->loadMissing(['orderTaxRates']);

        if ($order->orderTaxRates && $order->orderTaxRates->count() === 1) {
            $rate = $order->orderTaxRates->first();
            $meta = is_array($rate->meta) ? $rate->meta : [];
            if (isset($meta['inclusive'])) {
                return (bool) $meta['inclusive'];
            }
        }

        return (int) $order->tax_behavior === 2;
    }

    /**
     * Determine whether the shipping tax on an order was charged inclusive of the
     * shipping price (vs. added on top). Reads `meta.shipping_inclusive` (written
     * from the store-level tax mode at order placement) from each rate row that
     * contributed shipping_tax. Falls back to $order->tax_behavior === 2.
     *
     * Shipping always follows the store-level tax mode — per-product `meta.inclusive`
     * is intentionally NOT consulted here.
     */
    public static function isShippingTaxInclusive(Order $order)
    {
        $order->loadMissing(['orderTaxRates']);

        if ($order->orderTaxRates && $order->orderTaxRates->count()) {
            $shippingRatesInclusive = null;
            foreach ($order->orderTaxRates as $rate) {
                $meta = is_array($rate->meta) ? $rate->meta : [];
                // On reverse-charge orders shipping_tax is zeroed; detect via the
                // pre-zeroed snapshot stored in meta before falling back.
                $hasShippingContrib = (int) $rate->shipping_tax > 0
                    || (int) Arr::get($meta, 'reverse_charge_original_shipping_tax', 0) > 0;
                if (!$hasShippingContrib) {
                    continue;
                }
                if (!isset($meta['shipping_inclusive'])) {
                    continue;
                }
                $rateInclusive = (bool) $meta['shipping_inclusive'];
                if ($shippingRatesInclusive === null) {
                    $shippingRatesInclusive = $rateInclusive;
                } elseif ($shippingRatesInclusive !== $rateInclusive) {
                    return (int) $order->tax_behavior === 2;
                }
            }
            if ($shippingRatesInclusive !== null) {
                return $shippingRatesInclusive;
            }
        }

        return (int) $order->tax_behavior === 2;
    }

    /**
     * Returns display-ready fee tax line rows, filtering out zero-amount entries
     * and pre-computing the translated label for each surface to render.
     * Each entry: ['label' => string, 'tax_amount' => int, 'inclusive' => bool, 'display_label' => string]
     */
    public static function buildFeeTaxLineRows(array $feeTaxLines)
    {
        $rows = [];
        foreach ($feeTaxLines as $ftl) {
            $taxAmount = (int) Arr::get($ftl, 'tax_amount', 0);
            if ($taxAmount <= 0) {
                continue;
            }
            $inclusive  = !empty($ftl['inclusive']);
            $feeLabel   = Arr::get($ftl, 'label', __('fee', 'fluent-cart'));
            /* translators: %1$s: fee label */
            $displayLabel = $inclusive
                ? sprintf(__('Included in %1$s', 'fluent-cart'), $feeLabel)
                : sprintf(__('Added on %1$s', 'fluent-cart'), $feeLabel);
            $rows[] = [
                'label'         => $feeLabel,
                'tax_amount'    => $taxAmount,
                'inclusive'     => $inclusive,
                'display_label' => $displayLabel,
            ];
        }
        return $rows;
    }

    /**
     * Checkout-side variant: determines whether the shipping tax is inclusive of the
     * shipping price. Shipping always follows the store-level tax mode, so this returns
     * true only when store_tax_behavior === 2 (inclusive). Per-product inclusive flags
     * are intentionally NOT consulted — they do not govern shipping.
     */
    public static function isShippingTaxInclusiveFromTaxData(array $taxData)
    {
        $storeBehavior = (int) Arr::get($taxData, 'store_tax_behavior', Arr::get($taxData, 'tax_behavior', 2));

        return $storeBehavior === 2;
    }
}

```
