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

544 lines 16.8 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\OrderMeta;
11 use FluentCart\App\Models\Subscription;
12 use FluentCart\App\Services\DateTime\DateTime;
13 use FluentCart\App\Services\Payments\PaymentReceipt;
14 use FluentCart\App\Services\TemplateService;
15 use FluentCart\Framework\Support\Arr;
16 use FluentCart\Framework\Support\Str;
17 use FluentCartPro\App\Modules\Licensing\Models\License;
18
19 class OrderParser extends BaseParser
20 {
21 private StoreSettings $storeSettings;
22 private $order;
23 private $orderTz;
24
25 private $licenses;
26
27 private bool $licenseLoaded = false;
28
29 private $subscriptions;
30
31 private bool $subscriptionLoaded = false;
32
33 public function __construct($data)
34 {
35 $this->storeSettings = new StoreSettings();
36 $this->order = Arr::get($data, 'order');
37 $config = Arr::wrap(
38 Arr::get($this->order, 'config')
39 );
40 $rawTz = Arr::get($config, 'user_tz', 'UTC');
41 $this->orderTz = (@timezone_open($rawTz) !== false) ? $rawTz : 'UTC';
42 $orderId = Arr::get($this->order, 'id');
43
44
45 parent::__construct($data);
46 }
47
48 // protected array $methodMap = [
49 // 'customer_dashboard_link' => 'getCustomerDashboardLink',
50 // 'payment_summary' => 'getPaymentSummary',
51 // 'payment_receipt' => 'getPaymentReceipt',
52 // ];
53
54 protected array $methodMap = [
55 'item_count' => 'getItemCount',
56 'is_digital' => 'getIsDigital',
57 'store_vat_display' => 'getStoreVatDisplay',
58 'buyer_vat_display' => 'getBuyerVatDisplay',
59 'buyer_company_name' => 'getBuyerCompanyName',
60 ];
61
62 protected array $attributeMap = [
63 'id' => 'order.id',
64 'status' => 'order.status',
65 'created_at' => 'order.created_at',
66 'updated_at' => 'order.updated_at',
67 ];
68
69 protected array $centColumns = [
70 'total_amount',
71 'subtotal',
72 'discount_tax',
73 'manual_discount_total',
74 'coupon_discount_total',
75 'shipping_tax',
76 'shipping_total',
77 'fee_total',
78 'tax_total',
79 'total_paid',
80 'total_refund'
81 ];
82
83 public function parse($accessor = '', $code = '', $transformer = null): ?string
84 {
85
86 if ($this->shouldParseAddress($accessor)) {
87 return $this->parseAddressFields($accessor);
88 }
89
90 if (in_array($accessor, ['updated_at', 'created_at'])) {
91 $date = Arr::get($this->data, $this->attributeMap[$accessor]);
92 $timestamp = DateTime::anyTimeToGmt($date)->getTimestamp();
93
94 $date = wp_date(
95 get_option('date_format'),
96 $timestamp,
97 new \DateTimeZone($this->orderTz)
98 );
99
100 return Helper::translateNumber($date);
101 }
102
103 // Handle _formatted suffix for cent columns (e.g. total_amount_formatted)
104 $formattedSuffix = '_formatted';
105 if (Str::endsWith($accessor, $formattedSuffix)) {
106 $baseAccessor = substr($accessor, 0, -strlen($formattedSuffix));
107 if (in_array($baseAccessor, $this->centColumns)) {
108 $amount = Arr::get($this->order, $baseAccessor);
109 if (!is_numeric($amount)) {
110 return (string) $amount;
111 }
112 return CurrencySettings::getPriceHtml($amount, $this->order['currency']);
113 }
114 }
115
116 if (in_array($accessor, $this->centColumns)) {
117 $amount = Arr::get($this->order, $accessor);
118 if (!is_numeric($amount)) {
119 return (string) $amount;
120 }
121 return (string) ($amount / 100);
122 }
123
124 // $html parsers
125 $htmlParsers = [
126 'order.download_details',
127 'order.items_table',
128 'order.payment_summary',
129 'order.payment_receipt',
130 'order.subscription_details',
131 'order.license_details',
132 'order.address_details',
133 ];
134
135 if (in_array($code, $htmlParsers)) {
136
137 $order = $this->order;
138
139 if ($code == 'order.items_table') {
140 return \FluentCart\App\App::make('view')->make('emails.parts.items_table', [
141 'order' => $order,
142 'formattedItems' => $order->order_items,
143 'heading' => __('Order Summary', 'fluent-cart'),
144 ]);
145 }
146
147 if ($code === 'order.subscription_details') {
148 if ($order->subscriptions && $order->subscriptions->count() > 0) {
149 return \FluentCart\App\App::make('view')->make('invoice.parts.subscription_items', [
150 'subscriptions' => $order->subscriptions,
151 'order' => $order
152 ]);
153 }
154 return '';
155 }
156
157 if ($code === 'order.license_details') {
158 $licenses = $order->getLicenses();
159 if ($licenses && $licenses->count() > 0) {
160 return \FluentCart\App\App::make('view')->make('emails.parts.licenses', [
161 'licenses' => $licenses,
162 'heading' => _n('License', 'Licenses', $licenses->count(), 'fluent-cart'),
163 'show_notice' => false
164 ]);
165 }
166 return '';
167 }
168
169 if ($code === 'order.download_details') {
170 $downloads = $order->getDownloads();
171 if ($downloads) {
172 return \FluentCart\App\App::make('view')->make('emails.parts.downloads', [
173 'order' => $order,
174 'heading' => _n('Download', 'Downloads', count($downloads), 'fluent-cart'),
175 'downloadItems' => $downloads,
176 ]);
177 }
178 return '';
179 }
180
181 if ($code === 'order.address_details') {
182 return \FluentCart\App\App::make('view')->make('emails.parts.addresses', [
183 'order' => $order,
184 ]);
185 }
186
187 if ($code == 'order.payment_summary') {
188 return $this->getPaymentSummary();
189 }
190 if ($code == 'order.payment_receipt') {
191 return $this->getPaymentReceipt();
192 }
193 }
194
195
196 return $this->get($accessor, $code);
197 }
198
199 public function shouldParseAddress($accessor): bool
200 {
201 return Str::startsWith($accessor, 'billing.') || Str::startsWith($accessor, 'shipping.');
202 }
203
204 public function parseAddressFields($accessor)
205 {
206 list($addressType, $accessorsKey) = $this->resolveAddressFieldKeys($accessor);
207 return $this->getAddressData($addressType, $accessorsKey);
208 }
209
210 public function resolveAddressFieldKeys($accessor): array
211 {
212 $exploded = explode('.', $accessor);
213 $addressType = $exploded[0];
214 $accessorsKey = implode('.', array_slice($exploded, 1));
215 return [$addressType, $accessorsKey];
216 }
217
218 public function getAddressData($addressAccessor, $accessor = null)
219 {
220 $address = Arr::get($this->order, $addressAccessor . '_address');
221
222 if (empty($address)) {
223 return "";
224 }
225
226 $formattedFields = ['city', 'state', 'country'];
227 if (in_array($accessor, $formattedFields) && method_exists($address, 'getFormattedAddress')) {
228 $formatted = $address->getFormattedAddress();
229 return Arr::get($formatted, $accessor) ?: '';
230 }
231
232 return Arr::get($address, $accessor) ?: '';
233 }
234
235 public function getPaymentSummary()
236 {
237 $order = $this->order;
238
239 return \FluentCart\App\App::make('view')->make('emails.parts.items_table', [
240 'order' => $order,
241 'formattedItems' => $order->order_items,
242 'heading' => '',
243 ]);
244 }
245
246 public function getPaymentReceipt()
247 {
248 $order = $this->order;
249
250 ob_start();
251
252 \FluentCart\App\App::make('view')->render('emails.parts.items_table', [
253 'order' => $order,
254 'formattedItems' => $order->order_items,
255 'heading' => __('Order Summary', 'fluent-cart'),
256 ]);
257
258
259 if ($order->subscriptions && $order->subscriptions->count() > 0) {
260 \FluentCart\App\App::make('view')->render('invoice.parts.subscription_items', [
261 'subscriptions' => $order->subscriptions,
262 'order' => $order
263 ]);
264 }
265
266 $licenses = $order->getLicenses();
267 if ($licenses && $licenses->count() > 0) {
268 \FluentCart\App\App::make('view')->render('emails.parts.licenses', [
269 'licenses' => $licenses,
270 'heading' => __('Licenses', 'fluent-cart'),
271 'show_notice' => false
272 ]);
273 }
274
275 $downloads = $order->getDownloads();
276 if ($downloads) {
277 \FluentCart\App\App::make('view')->render('emails.parts.downloads', [
278 'order' => $order,
279 'heading' => __('Downloads', 'fluent-cart'),
280 'downloadItems' => $downloads,
281 ]);
282 }
283
284 echo '<hr />';
285
286 \FluentCart\App\App::make('view')->render('emails.parts.addresses', [
287 'order' => $order,
288 ]);
289
290 return ob_get_clean();
291
292
293 }
294
295 public function getDiscountTotal(): string
296 {
297 return (string) ($this->getDiscountTotalInCents() / 100);
298 }
299
300 public function getDiscountTotalFormatted(): string
301 {
302 return CurrencySettings::getPriceHtml($this->getDiscountTotalInCents(), $this->order['currency']);
303 }
304
305 private function getDiscountTotalInCents(): int
306 {
307 return (int) Arr::get($this->order, 'coupon_discount_total', 0)
308 + (int) Arr::get($this->order, 'manual_discount_total', 0);
309 }
310
311 public function getOrderRef(): string
312 {
313 $invoiceNo = Arr::get($this->order, 'invoice_no');
314
315 if (!empty($invoiceNo)) {
316 return (string) $invoiceNo;
317 }
318
319 return (string) Arr::get($this->order, 'id');
320 }
321
322 public function getCustomerDashboardAnchorLink($accessor, $code = null, $conditions = [])
323 {
324 $defaultValue = Arr::get($conditions, 'default_value') ?? Arr::get($this->order, 'invoice_no');
325 if (empty($this->order)) {
326 return $code;
327 }
328
329 $profilePage = $this->storeSettings->getCustomerProfilePage();
330
331
332 if (!empty($profilePage)) {
333 return "<a style='color: #017EF3; text-decoration: none;' href='" . "$profilePage#/order/" . Arr::get($this->order, 'uuid') . "'>" . $defaultValue . "</a>";
334 } else {
335 return Arr::get($this->order, 'invoice_no');
336 }
337
338 }
339
340 public function getCustomerDashboardLink($accessor, $code = null)
341 {
342 if (empty($this->order)) {
343 return $code;
344 }
345
346 $orderLink = TemplateService::getCustomerProfileUrl('order/' . Arr::get($this->order, 'uuid'));
347
348 return is_user_logged_in() ? $orderLink : wp_login_url($orderLink);
349 }
350
351 public function getAdminOrderLink($accessor, $code = null)
352 {
353 if (empty($this->order)) {
354 return $code;
355 }
356 return admin_url('admin.php?page=fluent-cart#/orders/' . Arr::get($this->order, 'id') . '/view');
357 }
358
359 public function getAdminOrderAnchorLink($accessor, $code = null, $conditions = [])
360 {
361 $defaultValue = Arr::get($conditions, 'default_value');
362 if (empty($this->order)) {
363 return $code;
364 }
365
366 $url = admin_url('admin.php?page=fluent-cart#/orders/' . Arr::get($this->order, 'id') . '/view');
367
368 if (!empty($defaultValue)) {
369 return "<a style='color: #017EF3; text-decoration: none;' href='" . $url . "'>" . $defaultValue . "</a>";
370 }
371
372 return $url;
373 }
374
375 public function getCustomerOrderLink($accessor, $code = null)
376 {
377 if (empty($this->order)) {
378 return $code;
379 }
380
381 $customerProfilePage = $this->storeSettings->getCustomerProfilePage();
382 $orderLink = $customerProfilePage . '#/order/' . Arr::get($this->order, 'uuid');
383
384 return is_user_logged_in() ? $orderLink : wp_login_url($orderLink);
385 }
386
387 public function getTotalAmount()
388 {
389 $total = ($this->order['total_amount'] / 100);
390 $currency_sign = $this->order['currency'];
391 return $total . $currency_sign;
392 }
393
394 public function getDownloads()
395 {
396 $order = $this->order;
397
398 $downloads = $order->getDownloads();
399 if ($downloads) {
400 return (string)\FluentCart\App\App::make('view')->make('emails.parts.downloads', [
401 'order' => $order,
402 'heading' => '',
403 'downloadItems' => $downloads
404 ]);
405 }
406
407 return '';
408
409 }
410
411 public function getLicenses()
412 {
413 $order = $this->order;
414 $licenses = $order->getLicenses();
415 if ($licenses && $licenses->count() > 0) {
416 return (string)\FluentCart\App\App::make('view')->make('emails.parts.licenses', [
417 'licenses' => $licenses,
418 'heading' => __('Licenses', 'fluent-cart'),
419 'show_notice' => false
420 ]);
421 }
422
423 return '';
424 }
425
426 public function getLicenseCount(): string
427 {
428 return (string)$this->licenses->count();
429 }
430
431 public function getIsDigital(): string
432 {
433 if (!$this->order) {
434 return 'no';
435 }
436
437 $fulfillmentType = Arr::get($this->order, 'fulfillment_type');
438
439 return $fulfillmentType === 'digital' ? 'yes' : 'no';
440 }
441
442 public function getItemCount(): string
443 {
444 $orderItems = $this->order ? $this->order->order_items : null;
445
446 if ($orderItems) {
447 return (string)$orderItems->count();
448 }
449
450 return '0';
451 }
452
453 public function getSubscriptions()
454 {
455 return '';
456 }
457
458 /**
459 * Returns formatted store VAT display string, e.g. "VAT: NL123456789B01".
460 * Returns empty string if no store VAT is configured for this order.
461 */
462 public function getStoreVatDisplay(): string
463 {
464 if (!$this->order) {
465 return '';
466 }
467
468 $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null;
469
470 if (!$orderTaxRate) {
471 return '';
472 }
473
474 $storeVatNumber = Arr::get($orderTaxRate->meta ?? [], 'store_vat_number', '');
475
476 if (empty($storeVatNumber)) {
477 return '';
478 }
479
480 $taxCountry = Arr::get($orderTaxRate->meta ?? [], 'tax_country', '');
481 $label = \FluentCart\App\Modules\Tax\TaxModule::getCountryTaxTitle($taxCountry);
482
483 return esc_html($label) . ': ' . esc_html($storeVatNumber);
484 }
485
486 /**
487 * Returns formatted buyer VAT display string, e.g. "VAT/Tax ID: XX123456".
488 * Checks OrderMeta vat_tax_id first, then falls back to EU VAT reverse charge number.
489 */
490 public function getBuyerVatDisplay(): string
491 {
492 if (!$this->order) {
493 return '';
494 }
495
496 // Check OrderMeta vat_tax_id first (simple VAT/Tax ID)
497 $vatMeta = OrderMeta::query()
498 ->where('order_id', $this->order->id)
499 ->where('meta_key', 'vat_tax_id')
500 ->orderBy('id', 'DESC')
501 ->first();
502
503 if ($vatMeta && !empty($vatMeta->meta_value)) {
504 return esc_html(__('VAT/Tax ID', 'fluent-cart')) . ': ' . esc_html($vatMeta->meta_value);
505 }
506
507 // Fall back to EU VAT reverse charge number
508 $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null;
509 $vatNumber = Arr::get($orderTaxRate->meta ?? [], 'vat_reverse.vat_number', '');
510
511 if (!empty($vatNumber)) {
512 return esc_html(__('EU VAT', 'fluent-cart')) . ': ' . esc_html($vatNumber);
513 }
514
515 return '';
516 }
517
518 /**
519 * Returns buyer company name from billing address meta or VAT reverse charge data.
520 */
521 public function getBuyerCompanyName(): string
522 {
523 if (!$this->order) {
524 return '';
525 }
526
527 // Check billing address meta first
528 if ($this->order->billing_address) {
529 $companyName = Arr::get($this->order->billing_address->meta ?? [], 'other_data.company_name', '');
530 if (!empty($companyName)) {
531 return esc_html($companyName);
532 }
533 }
534
535 // Fall back to VAT reverse charge name
536 $orderTaxRate = $this->order->orderTaxRates ? $this->order->orderTaxRates->first() : null;
537
538 return esc_html(Arr::get($orderTaxRate->meta ?? [], 'vat_reverse.name', ''));
539 }
540
541 }
542
543
544