@fleetbase/storefront-engine 0.4.7 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/addon/components/storefront-order-summary.hbs +69 -68
  2. package/addon/components/storefront-order-summary.js +10 -1
  3. package/addon/controllers/base-controller.js +2 -22
  4. package/addon/controllers/networks/index/network.js +25 -0
  5. package/addon/controllers/promotions/push-notifications.js +64 -0
  6. package/addon/controllers/promotions.js +16 -0
  7. package/addon/engine.js +1 -31
  8. package/addon/extension.js +35 -0
  9. package/addon/routes/customers/index.js +0 -9
  10. package/addon/routes/networks/index/network/customers.js +0 -9
  11. package/addon/routes/networks/index.js +0 -9
  12. package/addon/routes/orders/index.js +0 -9
  13. package/addon/routes/products/index.js +2 -8
  14. package/addon/routes/promotions/push-notifications.js +19 -0
  15. package/addon/routes/promotions.js +16 -0
  16. package/addon/routes.js +3 -1
  17. package/addon/styles/storefront-engine.css +4 -0
  18. package/addon/templates/application.hbs +7 -0
  19. package/addon/templates/networks/index/network/index.hbs +30 -8
  20. package/addon/templates/networks/index/network.hbs +5 -26
  21. package/addon/templates/promotions/push-notifications.hbs +85 -0
  22. package/addon/templates/promotions.hbs +8 -0
  23. package/addon/templates/settings/index.hbs +19 -0
  24. package/app/controllers/promotions/push-notifications.js +1 -0
  25. package/app/controllers/promotions.js +1 -0
  26. package/app/routes/promotions/push-notifications.js +1 -0
  27. package/app/routes/promotions.js +1 -0
  28. package/app/templates/promotions/push-notifications.hbs +1 -0
  29. package/app/templates/promotions.hbs +1 -0
  30. package/composer.json +1 -1
  31. package/config/environment.js +1 -1
  32. package/extension.json +1 -1
  33. package/package.json +4 -4
  34. package/server/src/Http/Controllers/ActionController.php +61 -0
  35. package/server/src/Http/Controllers/v1/CheckoutController.php +25 -9
  36. package/server/src/Models/Cart.php +2 -1
  37. package/server/src/Models/Network.php +25 -0
  38. package/server/src/Models/Store.php +25 -0
  39. package/server/src/Notifications/PromotionalPushNotification.php +166 -0
  40. package/server/src/Support/QPay.php +475 -3
  41. package/server/src/routes.php +1 -0
  42. package/translations/en-us.yaml +28 -0
@@ -5,19 +5,107 @@ namespace Fleetbase\Storefront\Support;
5
5
  use Fleetbase\FleetOps\Models\ServiceQuote;
6
6
  use Fleetbase\Storefront\Models\Cart;
7
7
  use Fleetbase\Storefront\Models\Checkout;
8
- use Fleetbase\Storefront\Support\Storefront;
8
+ use Fleetbase\Storefront\Models\Product;
9
9
  use Fleetbase\Support\Utils;
10
10
  use GuzzleHttp\Client;
11
11
  use Illuminate\Support\Str;
12
12
 
13
+ /**
14
+ * QPay Payment Gateway Integration Class.
15
+ *
16
+ * This class provides a comprehensive interface for integrating with the QPay payment gateway,
17
+ * supporting both simple and eBarimt (Mongolian electronic receipt) invoice creation, payment
18
+ * processing, and transaction management. It handles authentication, API communication, and
19
+ * provides utilities for tax calculations and classification code validation.
20
+ *
21
+ * @author Fleetbase Pte Ltd
22
+ */
13
23
  class QPay
14
24
  {
15
- private string $host = 'https://merchant.qpay.mn/';
25
+ /**
26
+ * The base URL for the QPay merchant API.
27
+ */
28
+ private string $host = 'https://merchant.qpay.mn/';
29
+
30
+ /**
31
+ * The API version namespace.
32
+ */
16
33
  private string $namespace = 'v2';
34
+
35
+ /**
36
+ * The callback URL for payment notifications.
37
+ */
17
38
  private ?string $callbackUrl;
39
+
40
+ /**
41
+ * HTTP request options for the Guzzle client.
42
+ */
18
43
  private array $requestOptions = [];
44
+
45
+ /**
46
+ * The Guzzle HTTP client instance.
47
+ */
19
48
  private Client $client;
20
49
 
50
+ /**
51
+ * Classification codes that are exempt from tax in Mongolia.
52
+ *
53
+ * These codes represent various categories of goods and services that are
54
+ * subject to zero tax rate under Mongolian tax regulations.
55
+ */
56
+ public static array $zeroTaxClassificationCodes = [
57
+ '2111100',
58
+ '2111300',
59
+ '2111500',
60
+ '2111600',
61
+ '2112100',
62
+ '2112200',
63
+ '2112300',
64
+ '2113100',
65
+ '2113300',
66
+ '2113500',
67
+ '2113600',
68
+ '2113700',
69
+ '2113800',
70
+ '2113900',
71
+ '2114100',
72
+ '2114200',
73
+ '2114300',
74
+ '2114400',
75
+ '2115100',
76
+ '2115200',
77
+ '2115300',
78
+ '2115500',
79
+ '2115600',
80
+ '2115910',
81
+ '2115920',
82
+ '2115930',
83
+ '2115940',
84
+ '2115990',
85
+ '2116000',
86
+ '2117100',
87
+ '2117210',
88
+ '2117290',
89
+ '2117300',
90
+ '2117410',
91
+ '2117490',
92
+ '2117500',
93
+ '2117600',
94
+ '2117900',
95
+ '2118000',
96
+ '2119000',
97
+ ];
98
+
99
+ /**
100
+ * QPay constructor.
101
+ *
102
+ * Initializes the QPay client with authentication credentials and sets up
103
+ * the HTTP client with appropriate headers and base URI.
104
+ *
105
+ * @param string|null $username QPay merchant username
106
+ * @param string|null $password QPay merchant password
107
+ * @param string|null $callbackUrl URL to receive payment notifications
108
+ */
21
109
  public function __construct(?string $username = null, ?string $password = null, ?string $callbackUrl = null)
22
110
  {
23
111
  $this->callbackUrl = $callbackUrl ?? Utils::apiUrl('storefront/v1/checkouts/process-qpay');
@@ -32,17 +120,37 @@ class QPay
32
120
  $this->client = new Client($this->requestOptions);
33
121
  }
34
122
 
123
+ /**
124
+ * Update a specific request option and reinitialize the HTTP client.
125
+ *
126
+ * @param string $key The option key to update
127
+ * @param mixed $value The new value for the option
128
+ *
129
+ * @return void
130
+ */
35
131
  public function updateRequestOption($key, $value)
36
132
  {
37
133
  $this->requestOptions[$key] = $value;
38
134
  $this->client = new Client($this->requestOptions);
39
135
  }
40
136
 
137
+ /**
138
+ * Get the current Guzzle HTTP client instance.
139
+ *
140
+ * @return Client The HTTP client instance
141
+ */
41
142
  public function getClient()
42
143
  {
43
144
  return $this->client;
44
145
  }
45
146
 
147
+ /**
148
+ * Set the callback URL for payment notifications.
149
+ *
150
+ * @param string $url The callback URL
151
+ *
152
+ * @return $this
153
+ */
46
154
  public function setCallback(string $url)
47
155
  {
48
156
  $this->callbackUrl = $url;
@@ -50,6 +158,11 @@ class QPay
50
158
  return $this;
51
159
  }
52
160
 
161
+ /**
162
+ * Set the API namespace/version and update the base URI.
163
+ *
164
+ * @param string $namespace The API version namespace (e.g., 'v2')
165
+ */
53
166
  public function setNamespace(string $namespace): ?QPay
54
167
  {
55
168
  $this->namespace = $namespace;
@@ -58,6 +171,13 @@ class QPay
58
171
  return $this;
59
172
  }
60
173
 
174
+ /**
175
+ * Build the complete request URL from host, namespace, and path.
176
+ *
177
+ * @param string $path Optional path to append to the base URL
178
+ *
179
+ * @return string The complete request URL
180
+ */
61
181
  private function buildRequestUrl(string $path = ''): string
62
182
  {
63
183
  $url = trim($this->host . $this->namespace . '/' . $path);
@@ -65,6 +185,11 @@ class QPay
65
185
  return $url;
66
186
  }
67
187
 
188
+ /**
189
+ * Switch to using the QPay sandbox environment for testing.
190
+ *
191
+ * @return $this
192
+ */
68
193
  public function useSandbox()
69
194
  {
70
195
  $this->host = 'https://merchant-sandbox.qpay.mn/';
@@ -73,16 +198,39 @@ class QPay
73
198
  return $this;
74
199
  }
75
200
 
201
+ /**
202
+ * Create a new QPay instance (static factory method).
203
+ *
204
+ * @param string|null $username QPay merchant username
205
+ * @param string|null $password QPay merchant password
206
+ * @param string|null $callbackUrl URL to receive payment notifications
207
+ *
208
+ * @return QPay A new QPay instance
209
+ */
76
210
  public static function instance(?string $username = null, ?string $password = null, ?string $callbackUrl = null): QPay
77
211
  {
78
212
  return new static($username, $password, $callbackUrl);
79
213
  }
80
214
 
215
+ /**
216
+ * Check if a callback URL has been set.
217
+ *
218
+ * @return bool True if callback URL is set, false otherwise
219
+ */
81
220
  private function hasCallbackUrl()
82
221
  {
83
222
  return isset($this->callbackUrl);
84
223
  }
85
224
 
225
+ /**
226
+ * Make an HTTP request to the QPay API.
227
+ *
228
+ * @param string $method HTTP method (GET, POST, DELETE, etc.)
229
+ * @param string $path API endpoint path
230
+ * @param array $options Additional request options
231
+ *
232
+ * @return object|null Decoded JSON response
233
+ */
86
234
  private function request(string $method, string $path, array $options = [])
87
235
  {
88
236
  $options['http_errors'] = false;
@@ -95,6 +243,15 @@ class QPay
95
243
  return $json;
96
244
  }
97
245
 
246
+ /**
247
+ * Make a POST request to the QPay API.
248
+ *
249
+ * @param string $path API endpoint path
250
+ * @param array $params Request parameters
251
+ * @param array $options Additional request options
252
+ *
253
+ * @return object|null Decoded JSON response
254
+ */
98
255
  public function post(string $path, array $params = [], array $options = [])
99
256
  {
100
257
  $options = ['json' => $params];
@@ -102,6 +259,15 @@ class QPay
102
259
  return $this->request('POST', $path, $options);
103
260
  }
104
261
 
262
+ /**
263
+ * Make a DELETE request to the QPay API.
264
+ *
265
+ * @param string $path API endpoint path
266
+ * @param array $params Request parameters
267
+ * @param array $options Additional request options
268
+ *
269
+ * @return object|null Decoded JSON response
270
+ */
105
271
  public function delete(string $path, array $params = [], array $options = [])
106
272
  {
107
273
  $options = ['json' => $params];
@@ -109,21 +275,44 @@ class QPay
109
275
  return $this->request('DELETE', $path, $options);
110
276
  }
111
277
 
278
+ /**
279
+ * Make a GET request to the QPay API.
280
+ *
281
+ * @param string $path API endpoint path
282
+ * @param array $options Additional request options
283
+ *
284
+ * @return object|null Decoded JSON response
285
+ */
112
286
  public function get(string $path, array $options = [])
113
287
  {
114
288
  return $this->request('GET', $path, $options);
115
289
  }
116
290
 
291
+ /**
292
+ * Obtain an authentication token from QPay.
293
+ *
294
+ * @return object|null Response containing access token
295
+ */
117
296
  public function getAuthToken()
118
297
  {
119
298
  return $this->post('auth/token');
120
299
  }
121
300
 
301
+ /**
302
+ * Refresh the current authentication token.
303
+ *
304
+ * @return object|null Response containing refreshed access token
305
+ */
122
306
  public function refreshAuthToken()
123
307
  {
124
308
  return $this->post('auth/refresh');
125
309
  }
126
310
 
311
+ /**
312
+ * Configure the client to use Bearer token authentication.
313
+ *
314
+ * @param string $token The Bearer token
315
+ */
127
316
  private function useBearerToken(string $token): QPay
128
317
  {
129
318
  unset($this->requestOptions['auth']);
@@ -133,6 +322,13 @@ class QPay
133
322
  return $this;
134
323
  }
135
324
 
325
+ /**
326
+ * Set the authentication token for API requests.
327
+ *
328
+ * If no token is provided, automatically obtains one using credentials.
329
+ *
330
+ * @param string|null $accessToken Optional access token to use
331
+ */
136
332
  public function setAuthToken(?string $accessToken = null): QPay
137
333
  {
138
334
  if ($accessToken) {
@@ -149,6 +345,18 @@ class QPay
149
345
  return $this;
150
346
  }
151
347
 
348
+ /**
349
+ * Create a simple invoice without eBarimt (electronic receipt).
350
+ *
351
+ * @param int $amount Invoice amount
352
+ * @param string|null $invoiceCode Unique invoice code
353
+ * @param string|null $invoiceDescription Description of the invoice
354
+ * @param string|null $invoiceReceiverCode Receiver identification code
355
+ * @param string|null $senderInvoiceNo Sender's invoice number
356
+ * @param string|null $callbackUrl URL for payment notifications
357
+ *
358
+ * @return object|null Created invoice response
359
+ */
152
360
  public function createSimpleInvoice(int $amount, ?string $invoiceCode = '', ?string $invoiceDescription = '', ?string $invoiceReceiverCode = '', ?string $senderInvoiceNo = '', ?string $callbackUrl = null)
153
361
  {
154
362
  if (!$callbackUrl && $this->hasCallbackUrl()) {
@@ -167,6 +375,21 @@ class QPay
167
375
  return $this->createQPayInvoice($params);
168
376
  }
169
377
 
378
+ /**
379
+ * Create an eBarimt invoice (Mongolian electronic receipt).
380
+ *
381
+ * @param string|null $invoiceCode Unique invoice code
382
+ * @param string|null $senderInvoiceNo Sender's invoice number
383
+ * @param string|null $invoiceReceiverCode Receiver identification code
384
+ * @param array $invoiceReceiverData Receiver details (name, register number, etc.)
385
+ * @param string|null $invoiceDescription Description of the invoice
386
+ * @param string|null $taxType Tax type code (default: '1')
387
+ * @param string|null $districtCode District code for tax purposes
388
+ * @param array $lines Invoice line items with classification codes and taxes
389
+ * @param string|null $callbackUrl URL for payment notifications
390
+ *
391
+ * @return object|null Created invoice response
392
+ */
170
393
  public function createEbarimtInvoice(?string $invoiceCode = '', ?string $senderInvoiceNo = '', ?string $invoiceReceiverCode = '', array $invoiceReceiverData = [], ?string $invoiceDescription = '', ?string $taxType = '1', ?string $districtCode = '', array $lines = [], ?string $callbackUrl = null)
171
394
  {
172
395
  if (!$callbackUrl && $this->hasCallbackUrl()) {
@@ -188,6 +411,14 @@ class QPay
188
411
  return $this->createQPayInvoice($params);
189
412
  }
190
413
 
414
+ /**
415
+ * Create a QPay invoice with custom parameters.
416
+ *
417
+ * @param array $params Invoice parameters
418
+ * @param array $options Additional request options
419
+ *
420
+ * @return object|null Created invoice response
421
+ */
191
422
  public function createQPayInvoice(array $params = [], $options = [])
192
423
  {
193
424
  if (!isset($params['callback_url']) && $this->hasCallbackUrl()) {
@@ -197,11 +428,26 @@ class QPay
197
428
  return $this->post('invoice', $params, $options);
198
429
  }
199
430
 
431
+ /**
432
+ * Get payment information by payment ID.
433
+ *
434
+ * @param string $paymentId The payment ID
435
+ *
436
+ * @return object|null Payment information
437
+ */
200
438
  public function paymentGet(string $paymentId)
201
439
  {
202
440
  return $this->get('payment/' . $paymentId);
203
441
  }
204
442
 
443
+ /**
444
+ * Check the payment status for an invoice.
445
+ *
446
+ * @param string $invoiceId The invoice ID
447
+ * @param array $options Additional request options
448
+ *
449
+ * @return object|null Payment check response
450
+ */
205
451
  public function paymentCheck(string $invoiceId, $options = [])
206
452
  {
207
453
  $params = [
@@ -212,6 +458,13 @@ class QPay
212
458
  return $this->post('payment/check', $params, $options);
213
459
  }
214
460
 
461
+ /**
462
+ * Get the first payment record for an invoice.
463
+ *
464
+ * @param string $invoiceId The invoice ID
465
+ *
466
+ * @return object|null First payment record or null if none found
467
+ */
215
468
  public function getPayment(string $invoiceId)
216
469
  {
217
470
  $paymentCheck = $this->paymentCheck($invoiceId);
@@ -220,6 +473,14 @@ class QPay
220
473
  return $paymentCheck && is_array($rows) && count($rows) ? $rows[0] : null;
221
474
  }
222
475
 
476
+ /**
477
+ * Cancel a payment for an invoice.
478
+ *
479
+ * @param string $invoiceId The invoice ID
480
+ * @param array $options Additional request options
481
+ *
482
+ * @return object|null Cancellation response
483
+ */
223
484
  public function paymentCancel(string $invoiceId, $options = [])
224
485
  {
225
486
  $params = [
@@ -229,6 +490,14 @@ class QPay
229
490
  return $this->delete('payment/cancel', $params, $options);
230
491
  }
231
492
 
493
+ /**
494
+ * Refund a payment for an invoice.
495
+ *
496
+ * @param string $invoiceId The invoice ID
497
+ * @param array $options Additional request options
498
+ *
499
+ * @return object|null Refund response
500
+ */
232
501
  public function paymentRefund(string $invoiceId, $options = [])
233
502
  {
234
503
  $params = [
@@ -238,16 +507,39 @@ class QPay
238
507
  return $this->delete('payment/refund', $params, $options);
239
508
  }
240
509
 
510
+ /**
511
+ * Create an invoice using static method (convenience wrapper).
512
+ *
513
+ * @param string $username QPay merchant username
514
+ * @param string $password QPay merchant password
515
+ * @param array $invoiceParams Invoice parameters
516
+ *
517
+ * @return object|null Created invoice response
518
+ */
241
519
  public static function createInvoice(string $username, string $password, array $invoiceParams = [])
242
520
  {
243
521
  return static::instance($username, $password)->setAuthToken()->createQPayInvoice($invoiceParams);
244
522
  }
245
523
 
524
+ /**
525
+ * Generate a unique code based on current date and ID.
526
+ *
527
+ * @param string $id Identifier to append to date
528
+ *
529
+ * @return string Generated code in format YYYYMMDD{id}
530
+ */
246
531
  public static function generateCode(string $id)
247
532
  {
248
533
  return date('Ymd') . $id;
249
534
  }
250
535
 
536
+ /**
537
+ * Clean a code by removing spaces and special characters.
538
+ *
539
+ * @param string $code The code to clean
540
+ *
541
+ * @return string Cleaned code containing only alphanumeric characters and hyphens
542
+ */
251
543
  public static function cleanCode(string $code)
252
544
  {
253
545
  $code = str_replace(' ', '-', $code);
@@ -255,6 +547,13 @@ class QPay
255
547
  return preg_replace('/[^A-Za-z0-9\-]/', '', $code);
256
548
  }
257
549
 
550
+ /**
551
+ * Create test payment data from a checkout object for testing purposes.
552
+ *
553
+ * @param Checkout $checkout The checkout object
554
+ *
555
+ * @return array Mock payment data structure
556
+ */
258
557
  public static function createTestPaymentDataFromCheckout(Checkout $checkout): array
259
558
  {
260
559
  return [
@@ -274,6 +573,11 @@ class QPay
274
573
  ];
275
574
  }
276
575
 
576
+ /**
577
+ * Generate a mock eBarimt response for testing purposes.
578
+ *
579
+ * @return array Mock eBarimt response data structure
580
+ */
277
581
  public static function mockEbarimtResponse(): array
278
582
  {
279
583
  return [
@@ -284,7 +588,6 @@ class QPay
284
588
  'ebarimt_receiver_type' => 'CITIZEN',
285
589
  'ebarimt_receiver' => '88614450',
286
590
  'ebarimt_district_code' => '3505',
287
- 'ebarimt_bill_type' => '1',
288
591
  'g_merchant_id' => 'KKTT',
289
592
  'merchant_branch_code' => 'BRANCH1',
290
593
  'merchant_terminal_code' => null,
@@ -315,6 +618,16 @@ class QPay
315
618
  ];
316
619
  }
317
620
 
621
+ /**
622
+ * Calculate VAT (Value Added Tax) from a total amount.
623
+ *
624
+ * Assumes 10% VAT rate is included in the amount. Calculates the VAT portion
625
+ * by dividing by 1.1 and multiplying by 0.10, then truncates to 4 decimal places.
626
+ *
627
+ * @param float|int $amount The total amount including VAT
628
+ *
629
+ * @return float The calculated VAT amount truncated to 4 decimal places
630
+ */
318
631
  public static function calculateTax($amount): float
319
632
  {
320
633
  $result = ((float) $amount / 1.1) * 0.10;
@@ -323,6 +636,18 @@ class QPay
323
636
  return $truncated;
324
637
  }
325
638
 
639
+ /**
640
+ * Create initial invoice lines for QPay from cart and service quote.
641
+ *
642
+ * Generates line items for tips, delivery tips, and delivery fees with proper
643
+ * classification codes and tax calculations for eBarimt invoices.
644
+ *
645
+ * @param Cart $cart The shopping cart
646
+ * @param ServiceQuote|null $serviceQuote The delivery service quote
647
+ * @param array|object $checkoutOptions Checkout options including tips and pickup flag
648
+ *
649
+ * @return array Array of line items with descriptions, quantities, prices, and taxes
650
+ */
326
651
  public static function createQpayInitialLines(Cart $cart, ?ServiceQuote $serviceQuote, $checkoutOptions): array
327
652
  {
328
653
  // Prepare dependencies
@@ -344,6 +669,7 @@ class QPay
344
669
  'line_unit_price' => number_format($tipAmount, 2, '.', ''),
345
670
  'note' => 'Tip',
346
671
  'classification_code' => '6511100',
672
+ 'tax_product_code' => '319',
347
673
  'taxes' => [
348
674
  [
349
675
  'tax_code' => 'VAT',
@@ -363,6 +689,7 @@ class QPay
363
689
  'line_unit_price' => number_format($deliveryTipAmount, 2, '.', ''),
364
690
  'note' => 'Delivery Tip',
365
691
  'classification_code' => '6511100',
692
+ 'tax_product_code' => '319',
366
693
  'taxes' => [
367
694
  [
368
695
  'tax_code' => 'VAT',
@@ -382,6 +709,7 @@ class QPay
382
709
  'line_unit_price' => number_format($serviceQuoteAmount, 2, '.', ''),
383
710
  'note' => 'Delivery Fee',
384
711
  'classification_code' => '6511100',
712
+ 'tax_product_code' => '319',
385
713
  'taxes' => [
386
714
  [
387
715
  'tax_code' => 'VAT',
@@ -395,4 +723,148 @@ class QPay
395
723
 
396
724
  return $lines;
397
725
  }
726
+
727
+ /**
728
+ * Validate if a classification code is in the correct format.
729
+ *
730
+ * Classification codes must be exactly 7 digits for Mongolian tax purposes.
731
+ *
732
+ * @param mixed $classificationCode The code to validate
733
+ *
734
+ * @return bool True if valid (exactly 7 digits), false otherwise
735
+ */
736
+ public static function isValidClassificationCode($classificationCode): bool
737
+ {
738
+ if ($classificationCode === null) {
739
+ return false;
740
+ }
741
+
742
+ $classificationCode = (string) $classificationCode;
743
+
744
+ // Must be exactly 7 digits
745
+ return preg_match('/^\d{7}$/', $classificationCode) === 1;
746
+ }
747
+
748
+ /**
749
+ * Validate if a tax product code is in the correct format.
750
+ *
751
+ * Tax product codes must be exactly 7 digits for Mongolian tax purposes.
752
+ *
753
+ * @param mixed $taxProductCode The code to validate
754
+ *
755
+ * @return bool True if valid (exactly 3 digits), false otherwise
756
+ */
757
+ public static function isValidTaxProductCode($taxProductCode): bool
758
+ {
759
+ if ($taxProductCode === null) {
760
+ return false;
761
+ }
762
+
763
+ $taxProductCode = (string) $taxProductCode;
764
+
765
+ // Must be exactly 3 digits
766
+ return preg_match('/^\d{3}$/', $taxProductCode) === 1;
767
+ }
768
+
769
+ /**
770
+ * Check if a classification code is tax-free (zero tax rate).
771
+ *
772
+ * @param mixed $classificationCode The code to check
773
+ *
774
+ * @return bool True if the code is in the zero tax list, false otherwise
775
+ */
776
+ public static function isTaxFreeClassificationCode($classificationCode): bool
777
+ {
778
+ if (!self::isValidClassificationCode($classificationCode)) {
779
+ return false;
780
+ }
781
+
782
+ return in_array((string) $classificationCode, self::$zeroTaxClassificationCodes, true);
783
+ }
784
+
785
+ /**
786
+ * Get the classification code for a cart item.
787
+ *
788
+ * Attempts to retrieve the classification code from the item's meta data first,
789
+ * then falls back to the product's meta data, and finally uses a default code.
790
+ *
791
+ * @param object $item The cart item
792
+ *
793
+ * @return string The classification code (7 digits)
794
+ */
795
+ public static function getCartItemClassificationCode($item): string
796
+ {
797
+ $classificationCode = '6511100';
798
+
799
+ // Try item meta
800
+ if (isset($item->meta)) {
801
+ $meta = is_string($item->meta)
802
+ ? json_decode($item->meta, true) // decode into array
803
+ : (array) $item->meta;
804
+
805
+ $metaCode = data_get($meta, 'classification_code');
806
+
807
+ if (self::isValidClassificationCode($metaCode)) {
808
+ return $metaCode;
809
+ }
810
+ }
811
+
812
+ // Try product meta fallback
813
+ if ($item->product_id) {
814
+ $product = Product::where('public_id', $item->product_id)->first();
815
+
816
+ if ($product && $product->hasMeta('classification_code')) {
817
+ $productCode = $product->getMeta('classification_code');
818
+ if (self::isValidClassificationCode($productCode)) {
819
+ return $productCode;
820
+ }
821
+ }
822
+ }
823
+
824
+ // Final fallback
825
+ return $classificationCode;
826
+ }
827
+
828
+ /**
829
+ * Get the tax_product_code for a cart item.
830
+ *
831
+ * Attempts to retrieve the tax product code from the item's meta data first,
832
+ * then falls back to the product's meta data, and finally uses a default code.
833
+ *
834
+ * @param object $item The cart item
835
+ *
836
+ * @return string The tax_product_code code (7 digits)
837
+ */
838
+ public static function getCartItemTaxProductCode($item): string
839
+ {
840
+ $taxProductCode = '319';
841
+
842
+ // Try item meta
843
+ if (isset($item->meta)) {
844
+ $meta = is_string($item->meta)
845
+ ? json_decode($item->meta, true) // decode into array
846
+ : (array) $item->meta;
847
+
848
+ $metaCode = data_get($meta, 'tax_product_code');
849
+
850
+ if (self::isValidTaxProductCode($metaCode)) {
851
+ return $metaCode;
852
+ }
853
+ }
854
+
855
+ // Try product meta fallback
856
+ if ($item->product_id) {
857
+ $product = Product::where('public_id', $item->product_id)->first();
858
+
859
+ if ($product && $product->hasMeta('tax_product_code')) {
860
+ $productCode = $product->getMeta('tax_product_code');
861
+ if (self::isValidTaxProductCode($productCode)) {
862
+ return $productCode;
863
+ }
864
+ }
865
+ }
866
+
867
+ // Final fallback
868
+ return $taxProductCode;
869
+ }
398
870
  }