PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.4.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.4.1
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
fluent-cart / app / Services / ShortCodeParser / Parsers / OrderParser.php

OrderParser.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.4.1, at app/Services/ShortCodeParser/Parsers/OrderParser.php

812 lines 27.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\ShortCodeParser\Parsers;
4
5 use FluentCart\Api\CurrencySettings;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\Helpers\Helper;
8 use FluentCart\App\Helpers\Status;
9 use FluentCart\App\Models\Order;
10 use FluentCart\App\Models\Subscription;
11 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
12 use FluentCart\App\Services\DateTime\DateTime;
13 use FluentCart\App\Services\Payments\PaymentReceipt;
14 use FluentCart\App\Services\TemplateService;
15 use FluentCart\App\Services\Renderer\Receipt\TaxSummaryHelper;
16 use FluentCart\Framework\Support\Arr;
17 use FluentCart\Framework\Support\Str;
18 use FluentCartPro\App\Modules\Licensing\Models\License;
19
20 class OrderParser extends BaseParser
21 {
22 private StoreSettings $storeSettings;
23 private $order;
24 private $orderTz;
25
26 private $licenses;
27
28 private bool $licenseLoaded = false;
29
30 private $subscriptions;
31
32 private bool $subscriptionLoaded = false;
33
34 public function __construct($data)
35 {
36 $this->storeSettings = new StoreSettings();
37 $this->order = Arr::get($data, 'order');
38 $config = Arr::wrap(
39 Arr::get($this->order, 'config')
40 );
41 $rawTz = Arr::get($config, 'user_tz', 'UTC');
42 $this->orderTz = (@timezone_open($rawTz) !== false) ? $rawTz : 'UTC';
43 $orderId = Arr::get($this->order, 'id');
44
45
46 parent::__construct($data);
47 }
48
49 // protected array $methodMap = [
50 // 'customer_dashboard_link' => 'getCustomerDashboardLink',
51 // 'payment_summary' => 'getPaymentSummary',
52 // 'payment_receipt' => 'getPaymentReceipt',
53 // ];
54
55 protected array $methodMap = [
56 'item_count' => 'getItemCount',
57 'is_digital' => 'getIsDigital',
58 'store_vat_display' => 'getStoreVatDisplay',
59 'store_company_name' => 'getStoreCompanyName',
60 'store_company_display' => 'getStoreCompanyDisplay',
61 'store_legal_registration_id' => 'getStoreLegalRegistrationId',
62 'store_legal_registration_display' => 'getStoreLegalRegistrationDisplay',
63 'store_seller_vat_id' => 'getStoreSellerVatId',
64 'store_seller_vat_display' => 'getStoreSellerVatDisplay',
65 'store_seller_tax_id' => 'getStoreSellerTaxId',
66 'store_tax_display' => 'getStoreTaxDisplay',
67 'buyer_vat_display' => 'getBuyerVatDisplay',
68 'buyer_company_name' => 'getBuyerCompanyName',
69 'buyer_legal_registration_id' => 'getBuyerLegalRegistrationId',
70 'buyer_reverse_charge_declaration' => 'getBuyerReverseChargeDeclaration',
71 'tax_breakdown' => 'getTaxBreakdown',
72 'fee_lines' => 'getFeeLines',
73 ];
74
75 protected array $attributeMap = [
76 'id' => 'order.id',
77 'status' => 'order.status',
78 'created_at' => 'order.created_at',
79 'updated_at' => 'order.updated_at',
80 ];
81
82 protected array $centColumns = [
83 'total_amount',
84 'subtotal',
85 'discount_tax',
86 'manual_discount_total',
87 'coupon_discount_total',
88 'shipping_tax',
89 'shipping_total',
90 'fee_total',
91 'tax_total',
92 'total_paid',
93 'total_refund'
94 ];
95
96 public function parse($accessor = '', $code = '', $transformer = null): ?string
97 {
98
99 if ($this->shouldParseAddress($accessor)) {
100 return $this->parseAddressFields($accessor);
101 }
102
103 if (in_array($accessor, ['updated_at', 'created_at'])) {
104 $date = Arr::get($this->data, $this->attributeMap[$accessor]);
105 $timestamp = DateTime::anyTimeToGmt($date)->getTimestamp();
106
107 $date = wp_date(
108 get_option('date_format'),
109 $timestamp,
110 new \DateTimeZone($this->orderTz)
111 );
112
113 return Helper::translateNumber($date);
114 }
115
116 // Handle _formatted suffix for cent columns (e.g. total_amount_formatted)
117 $formattedSuffix = '_formatted';
118 if (Str::endsWith($accessor, $formattedSuffix)) {
119 $baseAccessor = substr($accessor, 0, -strlen($formattedSuffix));
120 if (in_array($baseAccessor, $this->centColumns)) {
121 $amount = Arr::get($this->order, $baseAccessor);
122 if (!is_numeric($amount)) {
123 return (string) $amount;
124 }
125 return CurrencySettings::getPriceHtml($amount, $this->order['currency']);
126 }
127 }
128
129 if (in_array($accessor, $this->centColumns)) {
130 $amount = Arr::get($this->order, $accessor);
131 if (!is_numeric($amount)) {
132 return (string) $amount;
133 }
134 return (string) ($amount / 100);
135 }
136
137 // $html parsers
138 $htmlParsers = [
139 'order.download_details',
140 'order.items_table',
141 'order.payment_summary',
142 'order.payment_receipt',
143 'order.subscription_details',
144 'order.license_details',
145 'order.address_details',
146 ];
147
148 if (in_array($code, $htmlParsers)) {
149
150 $order = $this->order;
151
152 if ($code == 'order.items_table') {
153 return \FluentCart\App\App::make('view')->make('emails.parts.items_table', [
154 'order' => $order,
155 'formattedItems' => $order->order_items,
156 'heading' => __('Order Summary', 'fluent-cart'),
157 ]);
158 }
159
160 if ($code === 'order.subscription_details') {
161 if ($order->subscriptions && $order->subscriptions->count() > 0) {
162 return \FluentCart\App\App::make('view')->make('invoice.parts.subscription_items', [
163 'subscriptions' => $order->subscriptions,
164 'order' => $order
165 ]);
166 }
167 return '';
168 }
169
170 if ($code === 'order.license_details') {
171 $licenses = $order->getLicenses();
172 if ($licenses && $licenses->count() > 0) {
173 return \FluentCart\App\App::make('view')->make('emails.parts.licenses', [
174 'licenses' => $licenses,
175 'heading' => _n('License', 'Licenses', $licenses->count(), 'fluent-cart'),
176 'show_notice' => false
177 ]);
178 }
179 return '';
180 }
181
182 if ($code === 'order.download_details') {
183 $downloads = $order->getDownloads();
184 if ($downloads) {
185 return \FluentCart\App\App::make('view')->make('emails.parts.downloads', [
186 'order' => $order,
187 'heading' => _n('Download', 'Downloads', count($downloads), 'fluent-cart'),
188 'downloadItems' => $downloads,
189 ]);
190 }
191 return '';
192 }
193
194 if ($code === 'order.address_details') {
195 return \FluentCart\App\App::make('view')->make('emails.parts.addresses', [
196 'order' => $order,
197 ]);
198 }
199
200 if ($code == 'order.payment_summary') {
201 return $this->getPaymentSummary();
202 }
203 if ($code == 'order.payment_receipt') {
204 return $this->getPaymentReceipt();
205 }
206 }
207
208
209 return $this->get($accessor, $code);
210 }
211
212 public function shouldParseAddress($accessor): bool
213 {
214 return Str::startsWith($accessor, 'billing.') || Str::startsWith($accessor, 'shipping.');
215 }
216
217 public function parseAddressFields($accessor)
218 {
219 list($addressType, $accessorsKey) = $this->resolveAddressFieldKeys($accessor);
220 return $this->getAddressData($addressType, $accessorsKey);
221 }
222
223 public function resolveAddressFieldKeys($accessor): array
224 {
225 $exploded = explode('.', $accessor);
226 $addressType = $exploded[0];
227 $accessorsKey = implode('.', array_slice($exploded, 1));
228 return [$addressType, $accessorsKey];
229 }
230
231 public function getAddressData($addressAccessor, $accessor = null)
232 {
233 $address = Arr::get($this->order, $addressAccessor . '_address');
234
235 if (empty($address)) {
236 return "";
237 }
238
239 $formattedFields = ['city', 'state', 'country'];
240 if (in_array($accessor, $formattedFields) && method_exists($address, 'getFormattedAddress')) {
241 $formatted = $address->getFormattedAddress();
242 return Arr::get($formatted, $accessor) ?: '';
243 }
244
245 return Arr::get($address, $accessor) ?: '';
246 }
247
248 public function getPaymentSummary()
249 {
250 $order = $this->order;
251
252 return \FluentCart\App\App::make('view')->make('emails.parts.items_table', [
253 'order' => $order,
254 'formattedItems' => $order->order_items,
255 'heading' => '',
256 ]);
257 }
258
259 public function getPaymentReceipt()
260 {
261 $order = $this->order;
262
263 ob_start();
264
265 \FluentCart\App\App::make('view')->render('emails.parts.items_table', [
266 'order' => $order,
267 'formattedItems' => $order->order_items,
268 'heading' => __('Order Summary', 'fluent-cart'),
269 ]);
270
271
272 if ($order->subscriptions && $order->subscriptions->count() > 0) {
273 \FluentCart\App\App::make('view')->render('invoice.parts.subscription_items', [
274 'subscriptions' => $order->subscriptions,
275 'order' => $order
276 ]);
277 }
278
279 $licenses = $order->getLicenses();
280 if ($licenses && $licenses->count() > 0) {
281 \FluentCart\App\App::make('view')->render('emails.parts.licenses', [
282 'licenses' => $licenses,
283 'heading' => __('Licenses', 'fluent-cart'),
284 'show_notice' => false
285 ]);
286 }
287
288 $downloads = $order->getDownloads();
289 if ($downloads) {
290 \FluentCart\App\App::make('view')->render('emails.parts.downloads', [
291 'order' => $order,
292 'heading' => __('Downloads', 'fluent-cart'),
293 'downloadItems' => $downloads,
294 ]);
295 }
296
297 echo '<hr />';
298
299 \FluentCart\App\App::make('view')->render('emails.parts.addresses', [
300 'order' => $order,
301 ]);
302
303 return ob_get_clean();
304
305
306 }
307
308 public function getDiscountTotal(): string
309 {
310 return (string) ($this->getDiscountTotalInCents() / 100);
311 }
312
313 public function getDiscountTotalFormatted(): string
314 {
315 return CurrencySettings::getPriceHtml($this->getDiscountTotalInCents(), $this->order['currency']);
316 }
317
318 private function getDiscountTotalInCents(): int
319 {
320 return (int) Arr::get($this->order, 'coupon_discount_total', 0)
321 + (int) Arr::get($this->order, 'manual_discount_total', 0);
322 }
323
324 public function getOrderRef(): string
325 {
326 $invoiceNo = Arr::get($this->order, 'invoice_no');
327
328 if (!empty($invoiceNo)) {
329 return (string) $invoiceNo;
330 }
331
332 return (string) Arr::get($this->order, 'id');
333 }
334
335 public function getCustomerDashboardAnchorLink($accessor, $code = null, $conditions = [])
336 {
337 $defaultValue = Arr::get($conditions, 'default_value') ?? Arr::get($this->order, 'invoice_no');
338 if (empty($this->order)) {
339 return $code;
340 }
341
342 $profilePage = $this->storeSettings->getCustomerProfilePage();
343
344
345 if (!empty($profilePage)) {
346 return "<a style='color: #017EF3; text-decoration: none;' href='" . "$profilePage#/order/" . Arr::get($this->order, 'uuid') . "'>" . $defaultValue . "</a>";
347 } else {
348 return Arr::get($this->order, 'invoice_no');
349 }
350
351 }
352
353 public function getCustomerDashboardLink($accessor, $code = null)
354 {
355 if (empty($this->order)) {
356 return $code;
357 }
358
359 $orderLink = TemplateService::getCustomerProfileUrl('order/' . Arr::get($this->order, 'uuid'));
360
361 return is_user_logged_in() ? $orderLink : wp_login_url($orderLink);
362 }
363
364 public function getAdminOrderLink($accessor, $code = null)
365 {
366 if (empty($this->order)) {
367 return $code;
368 }
369 return admin_url('admin.php?page=fluent-cart#/orders/' . Arr::get($this->order, 'id') . '/view');
370 }
371
372 public function getAdminOrderAnchorLink($accessor, $code = null, $conditions = [])
373 {
374 $defaultValue = Arr::get($conditions, 'default_value');
375 if (empty($this->order)) {
376 return $code;
377 }
378
379 $url = admin_url('admin.php?page=fluent-cart#/orders/' . Arr::get($this->order, 'id') . '/view');
380
381 if (!empty($defaultValue)) {
382 return "<a style='color: #017EF3; text-decoration: none;' href='" . $url . "'>" . $defaultValue . "</a>";
383 }
384
385 return $url;
386 }
387
388 public function getCustomerOrderLink($accessor, $code = null)
389 {
390 if (empty($this->order)) {
391 return $code;
392 }
393
394 $customerProfilePage = $this->storeSettings->getCustomerProfilePage();
395 $orderLink = $customerProfilePage . '#/order/' . Arr::get($this->order, 'uuid');
396
397 return is_user_logged_in() ? $orderLink : wp_login_url($orderLink);
398 }
399
400 public function getTotalAmount()
401 {
402 $total = ($this->order['total_amount'] / 100);
403 $currency_sign = $this->order['currency'];
404 return $total . $currency_sign;
405 }
406
407 public function getDownloads()
408 {
409 $order = $this->order;
410
411 $downloads = $order->getDownloads();
412 if ($downloads) {
413 return (string)\FluentCart\App\App::make('view')->make('emails.parts.downloads', [
414 'order' => $order,
415 'heading' => '',
416 'downloadItems' => $downloads
417 ]);
418 }
419
420 return '';
421
422 }
423
424 public function getLicenses()
425 {
426 $order = $this->order;
427 $licenses = $order->getLicenses();
428 if ($licenses && $licenses->count() > 0) {
429 return (string)\FluentCart\App\App::make('view')->make('emails.parts.licenses', [
430 'licenses' => $licenses,
431 'heading' => __('Licenses', 'fluent-cart'),
432 'show_notice' => false
433 ]);
434 }
435
436 return '';
437 }
438
439 public function getLicenseCount(): string
440 {
441 return (string)$this->licenses->count();
442 }
443
444 public function getIsDigital(): string
445 {
446 if (!$this->order) {
447 return 'no';
448 }
449
450 $fulfillmentType = Arr::get($this->order, 'fulfillment_type');
451
452 return $fulfillmentType === 'digital' ? 'yes' : 'no';
453 }
454
455 public function getItemCount(): string
456 {
457 $orderItems = $this->order ? $this->order->order_items : null;
458
459 if ($orderItems) {
460 return (string)$orderItems->count();
461 }
462
463 return '0';
464 }
465
466 public function getPaymentMethodTitle(): string
467 {
468 if (!$this->order) {
469 return '';
470 }
471
472 $title = (string) Arr::get($this->order, 'payment_method_title', '');
473 if ($title !== '') {
474 return $title;
475 }
476
477 $slug = (string) Arr::get($this->order, 'payment_method', '');
478 if ($slug === '') {
479 return '';
480 }
481
482 if (class_exists(GatewayManager::class)) {
483 $gateway = GatewayManager::getInstance($slug);
484 if ($gateway) {
485 $gatewayTitle = (string) $gateway->getMeta('title');
486 if ($gatewayTitle !== '') {
487 return $gatewayTitle;
488 }
489 }
490 }
491
492 return ucwords(str_replace(['_', '-'], ' ', $slug));
493 }
494
495 public function getFeeLines(): string
496 {
497 if (!$this->order) {
498 return '';
499 }
500
501 $currency = (string) Arr::get($this->order, 'currency', '');
502 $feeItems = $this->order->feeItems()->get();
503
504 if ($feeItems->isEmpty()) {
505 return '';
506 }
507
508 $rowStyle = 'padding:3px 0;font-size:11px;font-weight:600;color:#525866;';
509 $valueStyle = 'text-align:right;';
510 $rows = '';
511
512 foreach ($feeItems as $feeItem) {
513 $rows .= '<tr>'
514 . '<td style="' . $rowStyle . '">' . esc_html((string) $feeItem->title) . '</td>'
515 . '<td style="' . $rowStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml((int) $feeItem->subtotal, $currency) . '</td>'
516 . '</tr>';
517 }
518
519 return $rows;
520 }
521
522 public function getTaxBreakdown(): string
523 {
524 if (!$this->order) {
525 return '';
526 }
527
528 $currency = (string) Arr::get($this->order, 'currency', '');
529 $summary = TaxSummaryHelper::computeTaxSummary($this->order);
530
531 if (!$summary['shouldRender']) {
532 return '';
533 }
534
535 $headingStyle = 'padding:8px 0 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:#94a3b8;';
536 $mutedStyle = 'padding:3px 0;font-size:11px;color:#94a3b8;';
537 $normalStyle = 'padding:3px 0;font-size:11px;font-weight:600;color:#525866;';
538 $totalStyle = 'padding:6px 0 3px;font-size:11px;font-weight:700;color:#0E121B;border-top:1px solid #e2e8f0;';
539 $valueStyle = 'text-align:right;';
540
541 $rows = '<tr><td colspan="2" style="' . $headingStyle . '">'
542 . esc_html__('TAX SUMMARY', 'fluent-cart')
543 . '</td></tr>';
544
545 if ($summary['isReverseCharge']) {
546 $rcReversedTotal = isset($summary['reversedTaxTotal']) ? (int) $summary['reversedTaxTotal'] : 0;
547 $rcReversedValue = $rcReversedTotal > 0
548 ? esc_html(Helper::toDecimal($rcReversedTotal))
549 : esc_html__('Charge reversed', 'fluent-cart');
550 $rows .= '<tr>'
551 . '<td style="' . $totalStyle . '">' . esc_html__('Tax reversed', 'fluent-cart') . '</td>'
552 . '<td style="' . $totalStyle . $valueStyle . '">' . $rcReversedValue . '</td>'
553 . '</tr>';
554 return $rows;
555 }
556
557 $opFeeRows = Arr::get($summary, 'feeTaxLineRows', []);
558 $rowCount = (int) ($summary['inclusiveTax'] > 0) + (int) ($summary['exclusiveTax'] > 0) + count($opFeeRows) + (int) ($summary['shippingTax'] > 0);
559 $shouldShowBreakdown = $rowCount >= 2 || ($rowCount === 1 && !($summary['payableTax'] > 0 || $summary['inclusiveTax'] > 0 || (int) Arr::get($summary, 'inclusiveFeeTax', 0) > 0));
560
561 if ($summary['inclusiveTax'] > 0 && $shouldShowBreakdown) {
562 $rows .= '<tr>'
563 . '<td style="' . $mutedStyle . '">' . esc_html__('Included in item prices', 'fluent-cart') . '</td>'
564 . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['inclusiveTax'], $currency) . '</td>'
565 . '</tr>';
566 }
567
568 if ($summary['exclusiveTax'] > 0 && $shouldShowBreakdown) {
569 $rows .= '<tr>'
570 . '<td style="' . $normalStyle . '">' . esc_html__('Added on products', 'fluent-cart') . '</td>'
571 . '<td style="' . $normalStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['exclusiveTax'], $currency) . '</td>'
572 . '</tr>';
573 }
574
575 if ($shouldShowBreakdown) {
576 foreach ($opFeeRows as $feeRow) {
577 $feeLineStyle = $feeRow['inclusive'] ? $mutedStyle : $normalStyle;
578 $rows .= '<tr>'
579 . '<td style="' . $feeLineStyle . '">' . esc_html($feeRow['display_label']) . '</td>'
580 . '<td style="' . $feeLineStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($feeRow['tax_amount'], $currency) . '</td>'
581 . '</tr>';
582 }
583 }
584
585 if ($summary['shippingTax'] > 0 && $shouldShowBreakdown) {
586 $isShippingInclusive = TaxSummaryHelper::isShippingTaxInclusive($this->order);
587 $shippingRowStyle = $isShippingInclusive ? $mutedStyle : $normalStyle;
588 $shippingRowLabel = $isShippingInclusive
589 ? esc_html__('Included in shipping prices', 'fluent-cart')
590 : esc_html__('Added on shipping', 'fluent-cart');
591 $rows .= '<tr>'
592 . '<td style="' . $shippingRowStyle . '">' . $shippingRowLabel . '</td>'
593 . '<td style="' . $shippingRowStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['shippingTax'], $currency) . '</td>'
594 . '</tr>';
595 }
596
597 if ($summary['payableTax'] > 0) {
598 $rows .= '<tr>'
599 . '<td style="' . $totalStyle . '">' . esc_html__('Total payable tax', 'fluent-cart') . '</td>'
600 . '<td style="' . $totalStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['payableTax'], $currency) . '</td>'
601 . '</tr>';
602 }
603
604 if ($summary['inclusiveTax'] > 0 || !empty($summary['inclusiveFeeTax'])) {
605 $rows .= '<tr>'
606 . '<td style="' . $mutedStyle . '">' . esc_html__('Total tax in this order', 'fluent-cart') . '</td>'
607 . '<td style="' . $mutedStyle . $valueStyle . '">' . CurrencySettings::getPriceHtml($summary['totalOrderTax'], $currency) . '</td>'
608 . '</tr>';
609 }
610
611 return $rows;
612 }
613
614 private function resolveTaxRateLabel($orderTaxRate): string
615 {
616 $taxRate = $orderTaxRate->tax_rate ?? null;
617 if ($taxRate) {
618 $name = (string) Arr::get($taxRate, 'name', '');
619 $rate = Arr::get($taxRate, 'rate');
620 if ($name !== '' && is_numeric($rate)) {
621 $rate = (string) $rate;
622
623 if (strpos($rate, '.') !== false) {
624 $rate = rtrim(rtrim($rate, '0'), '.');
625 }
626
627 return $name . ' (' . $rate . '%)';
628 }
629 if ($name !== '') {
630 return $name;
631 }
632 }
633
634 return __('Tax', 'fluent-cart');
635 }
636
637 public function getSubscriptions()
638 {
639 return '';
640 }
641
642 /**
643 * Returns formatted store VAT display string, e.g. "VAT: NL123456789B01".
644 * Returns empty string if no store VAT is configured for this order.
645 */
646 public function getStoreVatDisplay(): string
647 {
648 if (!$this->order) {
649 return '';
650 }
651
652 $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null;
653
654 if (!$orderTaxRate) {
655 return '';
656 }
657
658 $storeVatNumber = Arr::get($orderTaxRate->meta ?? [], 'store_vat_number', '');
659
660 if (empty($storeVatNumber)) {
661 return '';
662 }
663
664 $taxCountry = Arr::get($orderTaxRate->meta ?? [], 'tax_country', '');
665 $label = \FluentCart\App\Modules\Tax\TaxModule::getCountryTaxTitle($taxCountry);
666
667 return esc_html($label) . ': ' . esc_html($storeVatNumber);
668 }
669
670 /**
671 * Returns formatted buyer VAT display string, e.g. "VAT/Tax ID: XX123456".
672 * Checks business_info first, then falls back to legacy VAT storage.
673 */
674 public function getBuyerVatDisplay(): string
675 {
676 if (!$this->order) {
677 return '';
678 }
679
680 $vatNumber = $this->order->getCustomerTaxNumber();
681 if (!empty($vatNumber)) {
682 $label = __('VAT/Tax ID', 'fluent-cart');
683 return esc_html($label) . ': ' . esc_html($vatNumber);
684 }
685
686 return '';
687 }
688
689 /**
690 * Returns buyer company name from billing address meta or VAT reverse charge data.
691 */
692 public function getBuyerCompanyName(): string
693 {
694 if (!$this->order) {
695 return '';
696 }
697
698 // Check billing address meta first
699 if ($this->order->billing_address) {
700 $companyName = Arr::get($this->order->billing_address->meta ?? [], 'other_data.company_name', '');
701 if (!empty($companyName)) {
702 return esc_html($companyName);
703 }
704 }
705
706 return esc_html($this->order->getCustomerTaxName());
707 }
708
709 public function getBuyerLegalRegistrationId(): string
710 {
711 if (!$this->order) {
712 return '';
713 }
714
715 if ($this->order->billing_address) {
716 $regId = Arr::get($this->order->billing_address->meta ?? [], 'other_data.legal_registration_id', '');
717 if (!empty($regId)) {
718 return esc_html($regId);
719 }
720 }
721
722 return esc_html(Arr::get($this->order->getBusinessInfo(), 'legal_registration_id', ''));
723 }
724
725 public function getBuyerReverseChargeDeclaration(): string
726 {
727 if (!$this->order) {
728 return '';
729 }
730
731 return esc_html(Arr::get($this->order->getBusinessInfo(), 'reverse_charge_declaration', ''));
732 }
733
734 // -------------------------------------------------------------------------
735 // Store business info — reads from snapshotted fct_order_meta[store_business_info]
736 // with live StoreSettings fallback for orders placed before this feature.
737 // -------------------------------------------------------------------------
738
739 private function getStoreBusinessField(string $field): string
740 {
741 if (!$this->order) {
742 return '';
743 }
744 $snapshot = $this->order->getMeta('store_business_info', false);
745 if ($snapshot !== false && isset($snapshot[$field])) {
746 return (string) $snapshot[$field];
747 }
748 return (string) $this->storeSettings->get($field, '');
749 }
750
751 public function getStoreCompanyName(): string
752 {
753 return esc_html($this->getStoreBusinessField('company_name'));
754 }
755
756 public function getStoreCompanyDisplay(): string
757 {
758 $val = $this->getStoreBusinessField('company_name');
759 if (empty($val)) {
760 return '';
761 }
762 /* translators: %1$s: store company name */
763 return esc_html(sprintf(__('Company: %1$s', 'fluent-cart'), $val));
764 }
765
766 public function getStoreLegalRegistrationId(): string
767 {
768 return esc_html($this->getStoreBusinessField('legal_registration_id'));
769 }
770
771 public function getStoreLegalRegistrationDisplay(): string
772 {
773 $val = $this->getStoreBusinessField('legal_registration_id');
774 if (empty($val)) {
775 return '';
776 }
777 /* translators: %1$s: store legal registration ID */
778 return esc_html(sprintf(__('Reg. ID: %1$s', 'fluent-cart'), $val));
779 }
780
781 public function getStoreSellerVatId(): string
782 {
783 return esc_html($this->getStoreBusinessField('seller_vat_id'));
784 }
785
786 public function getStoreSellerVatDisplay(): string
787 {
788 $val = $this->getStoreBusinessField('seller_vat_id');
789 if (empty($val)) {
790 return '';
791 }
792 /* translators: %1$s: store seller VAT ID */
793 return esc_html(sprintf(__('VAT ID: %1$s', 'fluent-cart'), $val));
794 }
795
796 public function getStoreSellerTaxId(): string
797 {
798 return esc_html($this->getStoreBusinessField('seller_tax_id'));
799 }
800
801 public function getStoreTaxDisplay(): string
802 {
803 $val = $this->getStoreBusinessField('seller_tax_id');
804 if (empty($val)) {
805 return '';
806 }
807 /* translators: %1$s: store seller tax ID */
808 return esc_html(sprintf(__('Tax ID: %1$s', 'fluent-cart'), $val));
809 }
810
811 }
812