PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
← All changes | app/Services/ShortCodeParser/Parsers/OrderParser.php +171 -28 1.5.0 → 1.6.5 View file →
@@ -9,8 +9,9 @@
9 9 use FluentCart\App\Models\Order;
10 10 use FluentCart\App\Models\Subscription;
11 11 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
12 12 use FluentCart\App\Services\DateTime\DateTime;
13 +use FluentCart\App\Services\DateTime\DateFormatter;
13 14 use FluentCart\App\Services\Payments\PaymentHelper;
14 15 use FluentCart\App\Services\Payments\PaymentReceipt;
15 16 use FluentCart\App\Services\TemplateService;
16 17 use FluentCart\App\Services\Renderer\Receipt\TaxSummaryHelper;
@@ -104,14 +105,12 @@
104 105 if (in_array($accessor, ['updated_at', 'created_at'])) {
105 106 $date = Arr::get($this->data, $this->attributeMap[$accessor]);
106 107 $timestamp = DateTime::anyTimeToGmt($date)->getTimestamp();
107 108
108 - $date = wp_date(
109 - get_option('date_format'),
110 - $timestamp,
111 - new \DateTimeZone($this->orderTz)
112 - );
113 -
109 + // Through DateFormatter so the store's format and timezone settings
110 + // reach this too; orderTz is only consulted on the Browser source.
111 + $date = DateFormatter::format($timestamp, false, $this->orderTz);
112 +
114 113 return Helper::translateNumber($date);
115 114 }
116 115
117 116 // Handle _formatted suffix for cent columns (e.g. total_amount_formatted)
@@ -547,29 +546,130 @@
547 546 $normalStyle = 'padding:3px 0;font-size:11px;font-weight:600;color:#525866;';
548 547 $totalStyle = 'padding:6px 0 3px;font-size:11px;font-weight:700;color:#0E121B;border-top:1px solid #e2e8f0;';
549 548 $valueStyle = 'text-align:right;';
550 549
551 - $rows = '<tr><td colspan="2" style="' . $headingStyle . '">'
552 - . esc_html__('TAX SUMMARY', 'fluent-cart')
553 - . '</td></tr>';
550 + $rows = '';
554 551
555 - if ($summary['isReverseCharge']) {
556 - $rcReversedTotal = isset($summary['reversedTaxTotal']) ? (int) $summary['reversedTaxTotal'] : 0;
557 - $rcReversedValue = $rcReversedTotal > 0
558 - ? esc_html(Helper::toDecimal($rcReversedTotal))
559 - : esc_html__('Charge reversed', 'fluent-cart');
552 + $foldedRateLines = Arr::get($summary, 'foldedRateLines', []);
553 + $isRcOrder = !empty($summary['isReverseCharge']);
554 + $rcReversedTotal = isset($summary['reversedTaxTotal']) ? (int) $summary['reversedTaxTotal'] : 0;
555 + $rcReversedValue = $rcReversedTotal > 0
556 + ? esc_html(Helper::toDecimal($rcReversedTotal))
557 + : esc_html__('Charge reversed', 'fluent-cart');
558 + $includedInPrices = (int) Arr::get($summary, 'includedInPrices', 0);
559 + $opFeeRows = Arr::get($summary, 'feeTaxLineRows', []);
560 + $taxRateLines = Arr::get($summary, 'taxRateLines', []);
561 + $productTaxRowCount = !empty($taxRateLines)
562 + ? count($taxRateLines)
563 + : (int) ($summary['inclusiveTax'] > 0) + (int) ($summary['exclusiveTax'] > 0);
564 + $rowCount = $productTaxRowCount + count($opFeeRows) + (int) ($summary['shippingTax'] > 0);
565 + $shippingTaxLines = Arr::get($summary, 'shippingTaxLines', []);
566 + $shouldShowBreakdown = !empty($taxRateLines)
567 + || !empty($shippingTaxLines)
568 + || $rowCount >= 2
569 + || ($rowCount === 1 && !($summary['payableTax'] > 0 || $summary['inclusiveTax'] > 0 || (int) Arr::get($summary, 'inclusiveFeeTax', 0) > 0));
570 +
571 + $isSimplifiedMode = ($summary['displayMode'] ?? '') === 'simplified';
572 +
573 + if ($isSimplifiedMode && !empty($summary['simpleLine'])) {
560 574 $rows .= '<tr>'
575 + . '<td style="' . $normalStyle . '">' . esc_html((string) $summary['simpleLine']['label']) . '</td>'
576 + . '<td style="' . $normalStyle . $valueStyle . '">' . esc_html((string) $summary['simpleLine']['value']) . '</td>'
577 + . '</tr>';
578 +
579 + // Simplified mode: single tax line only, skip the detailed breakdown below.
580 + return $this->wrapTaxBreakdownBox($rows);
581 + }
582 +
583 + if (!empty($foldedRateLines)) {
584 + $colHeadStyle = 'width:58%;padding:3px 8px 3px 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:#64748b;vertical-align:top;';
585 + $colHeadBaseStyle = 'width:24%;padding:3px 8px 3px 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:#64748b;text-align:right;white-space:nowrap;vertical-align:top;';
586 + $colHeadTaxStyle = 'width:18%;padding:3px 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:#64748b;text-align:right;white-space:nowrap;vertical-align:top;';
587 +
588 + $rows .= '<tr><td colspan="2" style="padding:5px 0 2px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:#64748b;">'
589 + . esc_html__('Tax breakdown by rate', 'fluent-cart')
590 + . '</td></tr>';
591 +
592 + $nestedTable = '<table width="100%" cellpadding="0" cellspacing="0" style="border:none;border-collapse:collapse;table-layout:fixed;">';
593 + $nestedTable .= '<tr>'
594 + . '<td style="' . $colHeadStyle . '">' . esc_html__('Rate', 'fluent-cart') . '</td>'
595 + . '<td style="' . $colHeadBaseStyle . '">' . esc_html__('Taxable base', 'fluent-cart') . '</td>'
596 + . '<td style="' . $colHeadTaxStyle . '">' . esc_html__('Tax', 'fluent-cart') . '</td>'
597 + . '</tr>';
598 +
599 + foreach ($foldedRateLines as $foldedRow) {
600 + $foldedStyle = !empty($foldedRow['inclusive']) ? $mutedStyle : $normalStyle;
601 + $foldedStyleR = $foldedStyle . $valueStyle;
602 + $nestedTable .= '<tr>'
603 + . '<td style="width:58%;' . $foldedStyle . 'padding-right:8px;white-space:normal;word-break:break-word;overflow-wrap:break-word;vertical-align:top;">' . esc_html((string) $foldedRow['label']) . '</td>'
604 + . '<td style="width:24%;' . $foldedStyleR . 'padding-right:8px;white-space:nowrap;vertical-align:top;">' . CurrencySettings::getPriceHtml((int) $foldedRow['base'], $currency) . '</td>'
605 + . '<td style="width:18%;' . $foldedStyleR . 'white-space:nowrap;vertical-align:top;">' . CurrencySettings::getPriceHtml((int) $foldedRow['tax'], $currency) . '</td>'
606 + . '</tr>';
607 + }
608 + $nestedTable .= '</table>';
609 +
610 + $rows .= '<tr><td colspan="2" style="padding:2px 0 0 0;">' . $nestedTable . '</td></tr>';
611 +
612 + if ($isRcOrder) {
613 + $rcReversedTotalValue = $rcReversedTotal > 0
614 + ? CurrencySettings::getPriceHtml($rcReversedTotal, $currency)
615 + : esc_html__('Charge reversed', 'fluent-cart');
616 + $rows .= '<tr>'
617 + . '<td style="' . $totalStyle . '">' . esc_html__('VAT reversed', 'fluent-cart') . '</td>'
618 + . '<td style="' . $totalStyle . $valueStyle . '">' . $rcReversedTotalValue . '</td>'
619 + . '</tr>';
620 + return $this->wrapTaxBreakdownBox($rows);
621 + }
622 +
623 + $rows .= '<tr>'
624 + . '<td style="' . $totalStyle . '">' . esc_html__('Total tax', 'fluent-cart') . '</td>'
625 + . '<td style="' . $totalStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml((int) $summary['totalOrderTax'], $currency) . '</td>'
626 + . '</tr>';
627 +
628 + if ($includedInPrices > 0) {
629 + $rows .= '<tr>'
630 + . '<td style="' . $mutedStyle . '">' . esc_html__('of which included in prices', 'fluent-cart') . '</td>'
631 + . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($includedInPrices, $currency) . '</td>'
632 + . '</tr>';
633 + }
634 +
635 + if ($summary['payableTax'] > 0 && $includedInPrices > 0) {
636 + $payableStyle = $totalStyle;
637 + $rows .= '<tr>'
638 + . '<td style="' . $payableStyle . '">' . esc_html__('Payable now (added)', 'fluent-cart') . '</td>'
639 + . '<td style="' . $payableStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml((int) $summary['payableTax'], $currency) . '</td>'
640 + . '</tr>';
641 + }
642 +
643 + return $this->wrapTaxBreakdownBox($rows);
644 + }
645 +
646 + if ($isRcOrder) {
647 + $rows .= '<tr><td colspan="2" style="' . $headingStyle . '">'
648 + . esc_html__('TAX SUMMARY', 'fluent-cart')
649 + . '</td></tr>';
650 + $rows .= '<tr>'
561 651 . '<td style="' . $totalStyle . '">' . esc_html__('Tax reversed', 'fluent-cart') . '</td>'
562 652 . '<td style="' . $totalStyle . $valueStyle . '">' . $rcReversedValue . '</td>'
563 653 . '</tr>';
564 - return $rows;
654 + return $this->wrapTaxBreakdownBox($rows);
565 655 }
566 656
567 - $opFeeRows = Arr::get($summary, 'feeTaxLineRows', []);
568 - $rowCount = (int) ($summary['inclusiveTax'] > 0) + (int) ($summary['exclusiveTax'] > 0) + count($opFeeRows) + (int) ($summary['shippingTax'] > 0);
569 - $shouldShowBreakdown = $rowCount >= 2 || ($rowCount === 1 && !($summary['payableTax'] > 0 || $summary['inclusiveTax'] > 0 || (int) Arr::get($summary, 'inclusiveFeeTax', 0) > 0));
657 + $rows .= '<tr><td colspan="2" style="' . $headingStyle . '">'
658 + . esc_html__('TAX SUMMARY', 'fluent-cart')
659 + . '</td></tr>';
570 660
571 - if ($summary['inclusiveTax'] > 0 && $shouldShowBreakdown) {
661 + if (!empty($taxRateLines) && $shouldShowBreakdown) {
662 + foreach ($taxRateLines as $taxLine) {
663 + $taxLineStyle = !empty($taxLine['inclusive']) ? $mutedStyle : $normalStyle;
664 + $rows .= '<tr>'
665 + . '<td style="' . $taxLineStyle . '">' . esc_html($taxLine['label']) . '</td>'
666 + . '<td style="' . $taxLineStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($taxLine['order_tax'], $currency) . '</td>'
667 + . '</tr>';
668 + }
669 + }
670 +
671 + if (empty($taxRateLines) && $summary['inclusiveTax'] > 0 && $shouldShowBreakdown) {
572 672 $rows .= '<tr>'
573 673 . '<td style="' . $mutedStyle . '">' . esc_html__('Included in item prices', 'fluent-cart') . '</td>'
574 674 . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['inclusiveTax'], $currency) . '</td>'
575 675 . '</tr>';
@@ -574,9 +674,9 @@
574 674 . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['inclusiveTax'], $currency) . '</td>'
575 675 . '</tr>';
576 676 }
577 677
578 - if ($summary['exclusiveTax'] > 0 && $shouldShowBreakdown) {
678 + if (empty($taxRateLines) && $summary['exclusiveTax'] > 0 && $shouldShowBreakdown) {
579 679 $rows .= '<tr>'
580 680 . '<td style="' . $normalStyle . '">' . esc_html__('Added on products', 'fluent-cart') . '</td>'
581 681 . '<td style="' . $normalStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['exclusiveTax'], $currency) . '</td>'
582 682 . '</tr>';
@@ -594,15 +694,24 @@
594 694
595 695 if ($summary['shippingTax'] > 0 && $shouldShowBreakdown) {
596 696 $isShippingInclusive = TaxSummaryHelper::isShippingTaxInclusive($this->order);
597 697 $shippingRowStyle = $isShippingInclusive ? $mutedStyle : $normalStyle;
598 - $shippingRowLabel = $isShippingInclusive
599 - ? esc_html__('Included in shipping prices', 'fluent-cart')
600 - : esc_html__('Added on shipping', 'fluent-cart');
601 - $rows .= '<tr>'
602 - . '<td style="' . $shippingRowStyle . '">' . $shippingRowLabel . '</td>'
603 - . '<td style="' . $shippingRowStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['shippingTax'], $currency) . '</td>'
604 - . '</tr>';
698 + if (!empty($shippingTaxLines)) {
699 + foreach ($shippingTaxLines as $shippingTaxLine) {
700 + $rows .= '<tr>'
701 + . '<td style="' . $shippingRowStyle . '">' . esc_html($shippingTaxLine['label']) . '</td>'
702 + . '<td style="' . $shippingRowStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($shippingTaxLine['shipping_tax'], $currency) . '</td>'
703 + . '</tr>';
704 + }
705 + } else {
706 + $shippingRowLabel = $isShippingInclusive
707 + ? esc_html__('Included in shipping prices', 'fluent-cart')
708 + : esc_html__('Added on shipping', 'fluent-cart');
709 + $rows .= '<tr>'
710 + . '<td style="' . $shippingRowStyle . '">' . $shippingRowLabel . '</td>'
711 + . '<td style="' . $shippingRowStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['shippingTax'], $currency) . '</td>'
712 + . '</tr>';
713 + }
605 714 }
606 715
607 716 if ($summary['payableTax'] > 0) {
608 717 $rows .= '<tr>'
@@ -617,9 +726,43 @@
617 726 . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['totalOrderTax'], $currency) . '</td>'
618 727 . '</tr>';
619 728 }
620 729
621 - return $rows;
730 + return $this->wrapTaxBreakdownBox($rows);
731 + }
732 +
733 + /**
734 + * Wrap the accumulated tax-breakdown rows in their own grey rounded box,
735 + * right-aligned to a fixed 400px column.
736 + *
737 + * The tax breakdown is the only thing that lives inside the grey box; the
738 + * surrounding Subtotal/Discount/Shipping/Fees rows (above) and Refund/Total/
739 + * Payment rows (below) are rendered as plain rows by the summary table and
740 + * stay outside this wrapper. A table-based 400px right-aligned wrapper is used
741 + * (not a div) so PDF renderers (mPDF/Dompdf) and email clients honour the
742 + * width/alignment, matching the boxed treatment on the thank-you/customer
743 + * surfaces. Consumed by the PDF receipt templates via {{order.tax_breakdown}}.
744 + *
745 + * @param string $rows Inner <tr> rows for the tax breakdown.
746 + * @return string A single <tr><td colspan="2"> cell carrying the grey box.
747 + */
748 + private function wrapTaxBreakdownBox(string $rows): string
749 + {
750 + if ($rows === '') {
751 + return '';
752 + }
753 +
754 + $boxStyle = 'background-color:rgb(249,250,251);padding:16px;border-radius:8px;';
755 +
756 + $nestedTable = '<table width="100%" cellpadding="0" cellspacing="0" style="width:100%;border:none;border-collapse:collapse;">'
757 + . $rows
758 + . '</table>';
759 +
760 + return '<tr><td colspan="2" style="padding:8px 0 0 0;">'
761 + . '<table align="right" width="400" cellpadding="0" cellspacing="0" style="width:400px;max-width:100%;border:none;border-collapse:collapse;margin:0 0 0 auto;">'
762 + . '<tr><td style="' . $boxStyle . '">' . $nestedTable . '</td></tr>'
763 + . '</table>'
764 + . '</td></tr>';
622 765 }
623 766
624 767 private function resolveTaxRateLabel($orderTaxRate): string
625 768 {