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.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 trunk All 48 releases
← All changes | app/Services/ShortCodeParser/Parsers/OrderParser.php +517 -9 1.3.21 → 1.6.5 View file →
@@ -7,11 +7,15 @@
7 7 use FluentCart\App\Helpers\Helper;
8 8 use FluentCart\App\Helpers\Status;
9 9 use FluentCart\App\Models\Order;
10 10 use FluentCart\App\Models\Subscription;
11 +use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
11 12 use FluentCart\App\Services\DateTime\DateTime;
13 +use FluentCart\App\Services\DateTime\DateFormatter;
14 +use FluentCart\App\Services\Payments\PaymentHelper;
12 15 use FluentCart\App\Services\Payments\PaymentReceipt;
13 16 use FluentCart\App\Services\TemplateService;
17 +use FluentCart\App\Services\Renderer\Receipt\TaxSummaryHelper;
14 18 use FluentCart\Framework\Support\Arr;
15 19 use FluentCart\Framework\Support\Str;
16 20 use FluentCartPro\App\Modules\Licensing\Models\License;
17 21
@@ -50,10 +54,25 @@
50 54 // 'payment_receipt' => 'getPaymentReceipt',
51 55 // ];
52 56
53 57 protected array $methodMap = [
54 - 'item_count' => 'getItemCount',
55 - 'is_digital' => 'getIsDigital',
58 + 'item_count' => 'getItemCount',
59 + 'is_digital' => 'getIsDigital',
60 + 'store_vat_display' => 'getStoreVatDisplay',
61 + 'store_company_name' => 'getStoreCompanyName',
62 + 'store_company_display' => 'getStoreCompanyDisplay',
63 + 'store_legal_registration_id' => 'getStoreLegalRegistrationId',
64 + 'store_legal_registration_display' => 'getStoreLegalRegistrationDisplay',
65 + 'store_seller_vat_id' => 'getStoreSellerVatId',
66 + 'store_seller_vat_display' => 'getStoreSellerVatDisplay',
67 + 'store_seller_tax_id' => 'getStoreSellerTaxId',
68 + 'store_tax_display' => 'getStoreTaxDisplay',
69 + 'buyer_vat_display' => 'getBuyerVatDisplay',
70 + 'buyer_company_name' => 'getBuyerCompanyName',
71 + 'buyer_legal_registration_id' => 'getBuyerLegalRegistrationId',
72 + 'buyer_reverse_charge_declaration' => 'getBuyerReverseChargeDeclaration',
73 + 'tax_breakdown' => 'getTaxBreakdown',
74 + 'fee_lines' => 'getFeeLines',
56 75 ];
57 76
58 77 protected array $attributeMap = [
59 78 'id' => 'order.id',
@@ -86,14 +105,12 @@
86 105 if (in_array($accessor, ['updated_at', 'created_at'])) {
87 106 $date = Arr::get($this->data, $this->attributeMap[$accessor]);
88 107 $timestamp = DateTime::anyTimeToGmt($date)->getTimestamp();
89 108
90 - $date = wp_date(
91 - get_option('date_format'),
92 - $timestamp,
93 - new \DateTimeZone($this->orderTz)
94 - );
95 -
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 +
96 113 return Helper::translateNumber($date);
97 114 }
98 115
99 116 // Handle _formatted suffix for cent columns (e.g. total_amount_formatted)
@@ -343,8 +360,17 @@
343 360
344 361 return is_user_logged_in() ? $orderLink : wp_login_url($orderLink);
345 362 }
346 363
364 + public function getPaymentLink($accessor = null, $code = null)
365 + {
366 + if (empty($this->order)) {
367 + return $code;
368 + }
369 +
370 + return PaymentHelper::getCustomPaymentLink(Arr::get($this->order, 'uuid'));
371 + }
372 +
347 373 public function getAdminOrderLink($accessor, $code = null)
348 374 {
349 375 if (empty($this->order)) {
350 376 return $code;
@@ -445,12 +471,494 @@
445 471
446 472 return '0';
447 473 }
448 474
475 + public function getPaymentMethodTitle(): string
476 + {
477 + if (!$this->order) {
478 + return '';
479 + }
480 +
481 + $title = (string) Arr::get($this->order, 'payment_method_title', '');
482 + if ($title !== '') {
483 + return $title;
484 + }
485 +
486 + $slug = (string) Arr::get($this->order, 'payment_method', '');
487 + if ($slug === '') {
488 + return '';
489 + }
490 +
491 + if (class_exists(GatewayManager::class)) {
492 + $gateway = GatewayManager::getInstance($slug);
493 + if ($gateway) {
494 + $gatewayTitle = (string) $gateway->getMeta('title');
495 + if ($gatewayTitle !== '') {
496 + return $gatewayTitle;
497 + }
498 + }
499 + }
500 +
501 + return ucwords(str_replace(['_', '-'], ' ', $slug));
502 + }
503 +
504 + public function getFeeLines(): string
505 + {
506 + if (!$this->order) {
507 + return '';
508 + }
509 +
510 + $currency = (string) Arr::get($this->order, 'currency', '');
511 + $feeItems = $this->order->feeItems()->get();
512 +
513 + if ($feeItems->isEmpty()) {
514 + return '';
515 + }
516 +
517 + $rowStyle = 'padding:3px 0;font-size:11px;font-weight:600;color:#525866;';
518 + $valueStyle = 'text-align:right;';
519 + $rows = '';
520 +
521 + foreach ($feeItems as $feeItem) {
522 + $rows .= '<tr>'
523 + . '<td style="' . $rowStyle . '">' . esc_html((string) $feeItem->title) . '</td>'
524 + . '<td style="' . $rowStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml((int) $feeItem->subtotal, $currency) . '</td>'
525 + . '</tr>';
526 + }
527 +
528 + return $rows;
529 + }
530 +
531 + public function getTaxBreakdown(): string
532 + {
533 + if (!$this->order) {
534 + return '';
535 + }
536 +
537 + $currency = (string) Arr::get($this->order, 'currency', '');
538 + $summary = TaxSummaryHelper::computeTaxSummary($this->order);
539 +
540 + if (!$summary['shouldRender']) {
541 + return '';
542 + }
543 +
544 + $headingStyle = 'padding:8px 0 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:#94a3b8;';
545 + $mutedStyle = 'padding:3px 0;font-size:11px;color:#94a3b8;';
546 + $normalStyle = 'padding:3px 0;font-size:11px;font-weight:600;color:#525866;';
547 + $totalStyle = 'padding:6px 0 3px;font-size:11px;font-weight:700;color:#0E121B;border-top:1px solid #e2e8f0;';
548 + $valueStyle = 'text-align:right;';
549 +
550 + $rows = '';
551 +
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'])) {
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>'
651 + . '<td style="' . $totalStyle . '">' . esc_html__('Tax reversed', 'fluent-cart') . '</td>'
652 + . '<td style="' . $totalStyle . $valueStyle . '">' . $rcReversedValue . '</td>'
653 + . '</tr>';
654 + return $this->wrapTaxBreakdownBox($rows);
655 + }
656 +
657 + $rows .= '<tr><td colspan="2" style="' . $headingStyle . '">'
658 + . esc_html__('TAX SUMMARY', 'fluent-cart')
659 + . '</td></tr>';
660 +
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) {
672 + $rows .= '<tr>'
673 + . '<td style="' . $mutedStyle . '">' . esc_html__('Included in item prices', 'fluent-cart') . '</td>'
674 + . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['inclusiveTax'], $currency) . '</td>'
675 + . '</tr>';
676 + }
677 +
678 + if (empty($taxRateLines) && $summary['exclusiveTax'] > 0 && $shouldShowBreakdown) {
679 + $rows .= '<tr>'
680 + . '<td style="' . $normalStyle . '">' . esc_html__('Added on products', 'fluent-cart') . '</td>'
681 + . '<td style="' . $normalStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['exclusiveTax'], $currency) . '</td>'
682 + . '</tr>';
683 + }
684 +
685 + if ($shouldShowBreakdown) {
686 + foreach ($opFeeRows as $feeRow) {
687 + $feeLineStyle = $feeRow['inclusive'] ? $mutedStyle : $normalStyle;
688 + $rows .= '<tr>'
689 + . '<td style="' . $feeLineStyle . '">' . esc_html($feeRow['display_label']) . '</td>'
690 + . '<td style="' . $feeLineStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($feeRow['tax_amount'], $currency) . '</td>'
691 + . '</tr>';
692 + }
693 + }
694 +
695 + if ($summary['shippingTax'] > 0 && $shouldShowBreakdown) {
696 + $isShippingInclusive = TaxSummaryHelper::isShippingTaxInclusive($this->order);
697 + $shippingRowStyle = $isShippingInclusive ? $mutedStyle : $normalStyle;
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 + }
714 + }
715 +
716 + if ($summary['payableTax'] > 0) {
717 + $rows .= '<tr>'
718 + . '<td style="' . $totalStyle . '">' . esc_html__('Total payable tax', 'fluent-cart') . '</td>'
719 + . '<td style="' . $totalStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['payableTax'], $currency) . '</td>'
720 + . '</tr>';
721 + }
722 +
723 + if ($summary['inclusiveTax'] > 0 || !empty($summary['inclusiveFeeTax'])) {
724 + $rows .= '<tr>'
725 + . '<td style="' . $mutedStyle . '">' . esc_html__('Total tax in this order', 'fluent-cart') . '</td>'
726 + . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['totalOrderTax'], $currency) . '</td>'
727 + . '</tr>';
728 + }
729 +
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>';
765 + }
766 +
767 + private function resolveTaxRateLabel($orderTaxRate): string
768 + {
769 + $taxRate = $orderTaxRate->tax_rate ?? null;
770 + if ($taxRate) {
771 + $name = (string) Arr::get($taxRate, 'name', '');
772 + $rate = Arr::get($taxRate, 'rate');
773 + if ($name !== '' && is_numeric($rate)) {
774 + $rate = (string) $rate;
775 +
776 + if (strpos($rate, '.') !== false) {
777 + $rate = rtrim(rtrim($rate, '0'), '.');
778 + }
779 +
780 + return $name . ' (' . $rate . '%)';
781 + }
782 + if ($name !== '') {
783 + return $name;
784 + }
785 + }
786 +
787 + return __('Tax', 'fluent-cart');
788 + }
789 +
449 790 public function getSubscriptions()
450 791 {
451 792 return '';
452 793 }
453 794
454 -}
795 + /**
796 + * Returns formatted store VAT display string, e.g. "VAT: NL123456789B01".
797 + * Returns empty string if no store VAT is configured for this order.
798 + */
799 + public function getStoreVatDisplay(): string
800 + {
801 + if (!$this->order) {
802 + return '';
803 + }
455 804
805 + $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null;
456 806
807 + if (!$orderTaxRate) {
808 + return '';
809 + }
810 +
811 + $storeVatNumber = Arr::get($orderTaxRate->meta ?? [], 'store_vat_number', '');
812 +
813 + if (empty($storeVatNumber)) {
814 + return '';
815 + }
816 +
817 + $taxCountry = Arr::get($orderTaxRate->meta ?? [], 'tax_country', '');
818 + $label = \FluentCart\App\Modules\Tax\TaxModule::getCountryTaxTitle($taxCountry);
819 +
820 + return esc_html($label) . ': ' . esc_html($storeVatNumber);
821 + }
822 +
823 + /**
824 + * Returns formatted buyer VAT display string, e.g. "VAT/Tax ID: XX123456".
825 + * Checks business_info first, then falls back to legacy VAT storage.
826 + */
827 + public function getBuyerVatDisplay(): string
828 + {
829 + if (!$this->order) {
830 + return '';
831 + }
832 +
833 + $vatNumber = $this->order->getCustomerTaxNumber();
834 + if (!empty($vatNumber)) {
835 + $label = __('VAT/Tax ID', 'fluent-cart');
836 + return esc_html($label) . ': ' . esc_html($vatNumber);
837 + }
838 +
839 + return '';
840 + }
841 +
842 + /**
843 + * Returns buyer company name from billing address meta or VAT reverse charge data.
844 + */
845 + public function getBuyerCompanyName(): string
846 + {
847 + if (!$this->order) {
848 + return '';
849 + }
850 +
851 + // Check billing address meta first
852 + if ($this->order->billing_address) {
853 + $companyName = Arr::get($this->order->billing_address->meta ?? [], 'other_data.company_name', '');
854 + if (!empty($companyName)) {
855 + return esc_html($companyName);
856 + }
857 + }
858 +
859 + return esc_html($this->order->getCustomerTaxName());
860 + }
861 +
862 + public function getBuyerLegalRegistrationId(): string
863 + {
864 + if (!$this->order) {
865 + return '';
866 + }
867 +
868 + if ($this->order->billing_address) {
869 + $regId = Arr::get($this->order->billing_address->meta ?? [], 'other_data.legal_registration_id', '');
870 + if (!empty($regId)) {
871 + return esc_html($regId);
872 + }
873 + }
874 +
875 + return esc_html(Arr::get($this->order->getBusinessInfo(), 'legal_registration_id', ''));
876 + }
877 +
878 + public function getBuyerReverseChargeDeclaration(): string
879 + {
880 + if (!$this->order) {
881 + return '';
882 + }
883 +
884 + return esc_html(Arr::get($this->order->getBusinessInfo(), 'reverse_charge_declaration', ''));
885 + }
886 +
887 + // -------------------------------------------------------------------------
888 + // Store business info — reads from snapshotted fct_order_meta[store_business_info]
889 + // with live StoreSettings fallback for orders placed before this feature.
890 + // -------------------------------------------------------------------------
891 +
892 + private function getStoreBusinessField(string $field): string
893 + {
894 + if (!$this->order) {
895 + return '';
896 + }
897 + $snapshot = $this->order->getMeta('store_business_info', false);
898 + if ($snapshot !== false && isset($snapshot[$field])) {
899 + return (string) $snapshot[$field];
900 + }
901 + return (string) $this->storeSettings->get($field, '');
902 + }
903 +
904 + public function getStoreCompanyName(): string
905 + {
906 + return esc_html($this->getStoreBusinessField('company_name'));
907 + }
908 +
909 + public function getStoreCompanyDisplay(): string
910 + {
911 + $val = $this->getStoreBusinessField('company_name');
912 + if (empty($val)) {
913 + return '';
914 + }
915 + /* translators: %1$s: store company name */
916 + return esc_html(sprintf(__('Company: %1$s', 'fluent-cart'), $val));
917 + }
918 +
919 + public function getStoreLegalRegistrationId(): string
920 + {
921 + return esc_html($this->getStoreBusinessField('legal_registration_id'));
922 + }
923 +
924 + public function getStoreLegalRegistrationDisplay(): string
925 + {
926 + $val = $this->getStoreBusinessField('legal_registration_id');
927 + if (empty($val)) {
928 + return '';
929 + }
930 + /* translators: %1$s: store legal registration ID */
931 + return esc_html(sprintf(__('Reg. ID: %1$s', 'fluent-cart'), $val));
932 + }
933 +
934 + public function getStoreSellerVatId(): string
935 + {
936 + return esc_html($this->getStoreBusinessField('seller_vat_id'));
937 + }
938 +
939 + public function getStoreSellerVatDisplay(): string
940 + {
941 + $val = $this->getStoreBusinessField('seller_vat_id');
942 + if (empty($val)) {
943 + return '';
944 + }
945 + /* translators: %1$s: store seller VAT ID */
946 + return esc_html(sprintf(__('VAT ID: %1$s', 'fluent-cart'), $val));
947 + }
948 +
949 + public function getStoreSellerTaxId(): string
950 + {
951 + return esc_html($this->getStoreBusinessField('seller_tax_id'));
952 + }
953 +
954 + public function getStoreTaxDisplay(): string
955 + {
956 + $val = $this->getStoreBusinessField('seller_tax_id');
957 + if (empty($val)) {
958 + return '';
959 + }
960 + /* translators: %1$s: store seller tax ID */
961 + return esc_html(sprintf(__('Tax ID: %1$s', 'fluent-cart'), $val));
962 + }
963 +
964 +}