@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.
- package/addon/components/storefront-order-summary.hbs +69 -68
- package/addon/components/storefront-order-summary.js +10 -1
- package/addon/controllers/base-controller.js +2 -22
- package/addon/controllers/networks/index/network.js +25 -0
- package/addon/controllers/promotions/push-notifications.js +64 -0
- package/addon/controllers/promotions.js +16 -0
- package/addon/engine.js +1 -31
- package/addon/extension.js +35 -0
- package/addon/routes/customers/index.js +0 -9
- package/addon/routes/networks/index/network/customers.js +0 -9
- package/addon/routes/networks/index.js +0 -9
- package/addon/routes/orders/index.js +0 -9
- package/addon/routes/products/index.js +2 -8
- package/addon/routes/promotions/push-notifications.js +19 -0
- package/addon/routes/promotions.js +16 -0
- package/addon/routes.js +3 -1
- package/addon/styles/storefront-engine.css +4 -0
- package/addon/templates/application.hbs +7 -0
- package/addon/templates/networks/index/network/index.hbs +30 -8
- package/addon/templates/networks/index/network.hbs +5 -26
- package/addon/templates/promotions/push-notifications.hbs +85 -0
- package/addon/templates/promotions.hbs +8 -0
- package/addon/templates/settings/index.hbs +19 -0
- package/app/controllers/promotions/push-notifications.js +1 -0
- package/app/controllers/promotions.js +1 -0
- package/app/routes/promotions/push-notifications.js +1 -0
- package/app/routes/promotions.js +1 -0
- package/app/templates/promotions/push-notifications.hbs +1 -0
- package/app/templates/promotions.hbs +1 -0
- package/composer.json +1 -1
- package/config/environment.js +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/src/Http/Controllers/ActionController.php +61 -0
- package/server/src/Http/Controllers/v1/CheckoutController.php +25 -9
- package/server/src/Models/Cart.php +2 -1
- package/server/src/Models/Network.php +25 -0
- package/server/src/Models/Store.php +25 -0
- package/server/src/Notifications/PromotionalPushNotification.php +166 -0
- package/server/src/Support/QPay.php +475 -3
- package/server/src/routes.php +1 -0
- 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\
|
|
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
|
-
|
|
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
|
}
|