← 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 | { |