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

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

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