@fleetbase/storefront-engine 0.4.6 → 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/config/storefront.php +1 -1
- package/server/src/Http/Controllers/ActionController.php +61 -0
- package/server/src/Http/Controllers/v1/CheckoutController.php +26 -10
- package/server/src/Http/Controllers/v1/CustomerController.php +22 -16
- 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 +550 -2
- package/server/src/Support/Storefront.php +14 -0
- package/server/src/routes.php +1 -0
- package/translations/en-us.yaml +28 -0
|
@@ -2,19 +2,110 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\Storefront\Support;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
6
|
+
use Fleetbase\Storefront\Models\Cart;
|
|
5
7
|
use Fleetbase\Storefront\Models\Checkout;
|
|
8
|
+
use Fleetbase\Storefront\Models\Product;
|
|
6
9
|
use Fleetbase\Support\Utils;
|
|
7
10
|
use GuzzleHttp\Client;
|
|
8
11
|
use Illuminate\Support\Str;
|
|
9
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
|
+
*/
|
|
10
23
|
class QPay
|
|
11
24
|
{
|
|
12
|
-
|
|
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
|
+
*/
|
|
13
33
|
private string $namespace = 'v2';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The callback URL for payment notifications.
|
|
37
|
+
*/
|
|
14
38
|
private ?string $callbackUrl;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* HTTP request options for the Guzzle client.
|
|
42
|
+
*/
|
|
15
43
|
private array $requestOptions = [];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The Guzzle HTTP client instance.
|
|
47
|
+
*/
|
|
16
48
|
private Client $client;
|
|
17
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
|
+
*/
|
|
18
109
|
public function __construct(?string $username = null, ?string $password = null, ?string $callbackUrl = null)
|
|
19
110
|
{
|
|
20
111
|
$this->callbackUrl = $callbackUrl ?? Utils::apiUrl('storefront/v1/checkouts/process-qpay');
|
|
@@ -29,17 +120,37 @@ class QPay
|
|
|
29
120
|
$this->client = new Client($this->requestOptions);
|
|
30
121
|
}
|
|
31
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
|
+
*/
|
|
32
131
|
public function updateRequestOption($key, $value)
|
|
33
132
|
{
|
|
34
133
|
$this->requestOptions[$key] = $value;
|
|
35
134
|
$this->client = new Client($this->requestOptions);
|
|
36
135
|
}
|
|
37
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Get the current Guzzle HTTP client instance.
|
|
139
|
+
*
|
|
140
|
+
* @return Client The HTTP client instance
|
|
141
|
+
*/
|
|
38
142
|
public function getClient()
|
|
39
143
|
{
|
|
40
144
|
return $this->client;
|
|
41
145
|
}
|
|
42
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Set the callback URL for payment notifications.
|
|
149
|
+
*
|
|
150
|
+
* @param string $url The callback URL
|
|
151
|
+
*
|
|
152
|
+
* @return $this
|
|
153
|
+
*/
|
|
43
154
|
public function setCallback(string $url)
|
|
44
155
|
{
|
|
45
156
|
$this->callbackUrl = $url;
|
|
@@ -47,6 +158,11 @@ class QPay
|
|
|
47
158
|
return $this;
|
|
48
159
|
}
|
|
49
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
|
+
*/
|
|
50
166
|
public function setNamespace(string $namespace): ?QPay
|
|
51
167
|
{
|
|
52
168
|
$this->namespace = $namespace;
|
|
@@ -55,6 +171,13 @@ class QPay
|
|
|
55
171
|
return $this;
|
|
56
172
|
}
|
|
57
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
|
+
*/
|
|
58
181
|
private function buildRequestUrl(string $path = ''): string
|
|
59
182
|
{
|
|
60
183
|
$url = trim($this->host . $this->namespace . '/' . $path);
|
|
@@ -62,6 +185,11 @@ class QPay
|
|
|
62
185
|
return $url;
|
|
63
186
|
}
|
|
64
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Switch to using the QPay sandbox environment for testing.
|
|
190
|
+
*
|
|
191
|
+
* @return $this
|
|
192
|
+
*/
|
|
65
193
|
public function useSandbox()
|
|
66
194
|
{
|
|
67
195
|
$this->host = 'https://merchant-sandbox.qpay.mn/';
|
|
@@ -70,16 +198,39 @@ class QPay
|
|
|
70
198
|
return $this;
|
|
71
199
|
}
|
|
72
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
|
+
*/
|
|
73
210
|
public static function instance(?string $username = null, ?string $password = null, ?string $callbackUrl = null): QPay
|
|
74
211
|
{
|
|
75
212
|
return new static($username, $password, $callbackUrl);
|
|
76
213
|
}
|
|
77
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Check if a callback URL has been set.
|
|
217
|
+
*
|
|
218
|
+
* @return bool True if callback URL is set, false otherwise
|
|
219
|
+
*/
|
|
78
220
|
private function hasCallbackUrl()
|
|
79
221
|
{
|
|
80
222
|
return isset($this->callbackUrl);
|
|
81
223
|
}
|
|
82
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
|
+
*/
|
|
83
234
|
private function request(string $method, string $path, array $options = [])
|
|
84
235
|
{
|
|
85
236
|
$options['http_errors'] = false;
|
|
@@ -92,6 +243,15 @@ class QPay
|
|
|
92
243
|
return $json;
|
|
93
244
|
}
|
|
94
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
|
+
*/
|
|
95
255
|
public function post(string $path, array $params = [], array $options = [])
|
|
96
256
|
{
|
|
97
257
|
$options = ['json' => $params];
|
|
@@ -99,6 +259,15 @@ class QPay
|
|
|
99
259
|
return $this->request('POST', $path, $options);
|
|
100
260
|
}
|
|
101
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
|
+
*/
|
|
102
271
|
public function delete(string $path, array $params = [], array $options = [])
|
|
103
272
|
{
|
|
104
273
|
$options = ['json' => $params];
|
|
@@ -106,21 +275,44 @@ class QPay
|
|
|
106
275
|
return $this->request('DELETE', $path, $options);
|
|
107
276
|
}
|
|
108
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
|
+
*/
|
|
109
286
|
public function get(string $path, array $options = [])
|
|
110
287
|
{
|
|
111
288
|
return $this->request('GET', $path, $options);
|
|
112
289
|
}
|
|
113
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Obtain an authentication token from QPay.
|
|
293
|
+
*
|
|
294
|
+
* @return object|null Response containing access token
|
|
295
|
+
*/
|
|
114
296
|
public function getAuthToken()
|
|
115
297
|
{
|
|
116
298
|
return $this->post('auth/token');
|
|
117
299
|
}
|
|
118
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Refresh the current authentication token.
|
|
303
|
+
*
|
|
304
|
+
* @return object|null Response containing refreshed access token
|
|
305
|
+
*/
|
|
119
306
|
public function refreshAuthToken()
|
|
120
307
|
{
|
|
121
308
|
return $this->post('auth/refresh');
|
|
122
309
|
}
|
|
123
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Configure the client to use Bearer token authentication.
|
|
313
|
+
*
|
|
314
|
+
* @param string $token The Bearer token
|
|
315
|
+
*/
|
|
124
316
|
private function useBearerToken(string $token): QPay
|
|
125
317
|
{
|
|
126
318
|
unset($this->requestOptions['auth']);
|
|
@@ -130,6 +322,13 @@ class QPay
|
|
|
130
322
|
return $this;
|
|
131
323
|
}
|
|
132
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
|
+
*/
|
|
133
332
|
public function setAuthToken(?string $accessToken = null): QPay
|
|
134
333
|
{
|
|
135
334
|
if ($accessToken) {
|
|
@@ -146,6 +345,18 @@ class QPay
|
|
|
146
345
|
return $this;
|
|
147
346
|
}
|
|
148
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
|
+
*/
|
|
149
360
|
public function createSimpleInvoice(int $amount, ?string $invoiceCode = '', ?string $invoiceDescription = '', ?string $invoiceReceiverCode = '', ?string $senderInvoiceNo = '', ?string $callbackUrl = null)
|
|
150
361
|
{
|
|
151
362
|
if (!$callbackUrl && $this->hasCallbackUrl()) {
|
|
@@ -164,6 +375,21 @@ class QPay
|
|
|
164
375
|
return $this->createQPayInvoice($params);
|
|
165
376
|
}
|
|
166
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
|
+
*/
|
|
167
393
|
public function createEbarimtInvoice(?string $invoiceCode = '', ?string $senderInvoiceNo = '', ?string $invoiceReceiverCode = '', array $invoiceReceiverData = [], ?string $invoiceDescription = '', ?string $taxType = '1', ?string $districtCode = '', array $lines = [], ?string $callbackUrl = null)
|
|
168
394
|
{
|
|
169
395
|
if (!$callbackUrl && $this->hasCallbackUrl()) {
|
|
@@ -185,6 +411,14 @@ class QPay
|
|
|
185
411
|
return $this->createQPayInvoice($params);
|
|
186
412
|
}
|
|
187
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
|
+
*/
|
|
188
422
|
public function createQPayInvoice(array $params = [], $options = [])
|
|
189
423
|
{
|
|
190
424
|
if (!isset($params['callback_url']) && $this->hasCallbackUrl()) {
|
|
@@ -194,11 +428,26 @@ class QPay
|
|
|
194
428
|
return $this->post('invoice', $params, $options);
|
|
195
429
|
}
|
|
196
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
|
+
*/
|
|
197
438
|
public function paymentGet(string $paymentId)
|
|
198
439
|
{
|
|
199
440
|
return $this->get('payment/' . $paymentId);
|
|
200
441
|
}
|
|
201
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
|
+
*/
|
|
202
451
|
public function paymentCheck(string $invoiceId, $options = [])
|
|
203
452
|
{
|
|
204
453
|
$params = [
|
|
@@ -209,6 +458,13 @@ class QPay
|
|
|
209
458
|
return $this->post('payment/check', $params, $options);
|
|
210
459
|
}
|
|
211
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
|
+
*/
|
|
212
468
|
public function getPayment(string $invoiceId)
|
|
213
469
|
{
|
|
214
470
|
$paymentCheck = $this->paymentCheck($invoiceId);
|
|
@@ -217,6 +473,14 @@ class QPay
|
|
|
217
473
|
return $paymentCheck && is_array($rows) && count($rows) ? $rows[0] : null;
|
|
218
474
|
}
|
|
219
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
|
+
*/
|
|
220
484
|
public function paymentCancel(string $invoiceId, $options = [])
|
|
221
485
|
{
|
|
222
486
|
$params = [
|
|
@@ -226,6 +490,14 @@ class QPay
|
|
|
226
490
|
return $this->delete('payment/cancel', $params, $options);
|
|
227
491
|
}
|
|
228
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
|
+
*/
|
|
229
501
|
public function paymentRefund(string $invoiceId, $options = [])
|
|
230
502
|
{
|
|
231
503
|
$params = [
|
|
@@ -235,16 +507,39 @@ class QPay
|
|
|
235
507
|
return $this->delete('payment/refund', $params, $options);
|
|
236
508
|
}
|
|
237
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
|
+
*/
|
|
238
519
|
public static function createInvoice(string $username, string $password, array $invoiceParams = [])
|
|
239
520
|
{
|
|
240
521
|
return static::instance($username, $password)->setAuthToken()->createQPayInvoice($invoiceParams);
|
|
241
522
|
}
|
|
242
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
|
+
*/
|
|
243
531
|
public static function generateCode(string $id)
|
|
244
532
|
{
|
|
245
533
|
return date('Ymd') . $id;
|
|
246
534
|
}
|
|
247
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
|
+
*/
|
|
248
543
|
public static function cleanCode(string $code)
|
|
249
544
|
{
|
|
250
545
|
$code = str_replace(' ', '-', $code);
|
|
@@ -252,6 +547,13 @@ class QPay
|
|
|
252
547
|
return preg_replace('/[^A-Za-z0-9\-]/', '', $code);
|
|
253
548
|
}
|
|
254
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
|
+
*/
|
|
255
557
|
public static function createTestPaymentDataFromCheckout(Checkout $checkout): array
|
|
256
558
|
{
|
|
257
559
|
return [
|
|
@@ -271,6 +573,11 @@ class QPay
|
|
|
271
573
|
];
|
|
272
574
|
}
|
|
273
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Generate a mock eBarimt response for testing purposes.
|
|
578
|
+
*
|
|
579
|
+
* @return array Mock eBarimt response data structure
|
|
580
|
+
*/
|
|
274
581
|
public static function mockEbarimtResponse(): array
|
|
275
582
|
{
|
|
276
583
|
return [
|
|
@@ -281,7 +588,6 @@ class QPay
|
|
|
281
588
|
'ebarimt_receiver_type' => 'CITIZEN',
|
|
282
589
|
'ebarimt_receiver' => '88614450',
|
|
283
590
|
'ebarimt_district_code' => '3505',
|
|
284
|
-
'ebarimt_bill_type' => '1',
|
|
285
591
|
'g_merchant_id' => 'KKTT',
|
|
286
592
|
'merchant_branch_code' => 'BRANCH1',
|
|
287
593
|
'merchant_terminal_code' => null,
|
|
@@ -312,6 +618,16 @@ class QPay
|
|
|
312
618
|
];
|
|
313
619
|
}
|
|
314
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
|
+
*/
|
|
315
631
|
public static function calculateTax($amount): float
|
|
316
632
|
{
|
|
317
633
|
$result = ((float) $amount / 1.1) * 0.10;
|
|
@@ -319,4 +635,236 @@ class QPay
|
|
|
319
635
|
|
|
320
636
|
return $truncated;
|
|
321
637
|
}
|
|
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
|
+
*/
|
|
651
|
+
public static function createQpayInitialLines(Cart $cart, ?ServiceQuote $serviceQuote, $checkoutOptions): array
|
|
652
|
+
{
|
|
653
|
+
// Prepare dependencies
|
|
654
|
+
$checkoutOptions = (object) $checkoutOptions;
|
|
655
|
+
$subtotal = (int) $cart->subtotal;
|
|
656
|
+
$total = $subtotal;
|
|
657
|
+
$tip = $checkoutOptions->tip ?? false;
|
|
658
|
+
$deliveryTip = $checkoutOptions->delivery_tip ?? false;
|
|
659
|
+
$isPickup = $checkoutOptions->is_pickup ?? false;
|
|
660
|
+
|
|
661
|
+
// Initialize lines
|
|
662
|
+
$lines = [];
|
|
663
|
+
|
|
664
|
+
if ($tip) {
|
|
665
|
+
$tipAmount = Storefront::calculateTipAmount($tip, $subtotal);
|
|
666
|
+
$lines[] = [
|
|
667
|
+
'line_description' => 'Tip',
|
|
668
|
+
'line_quantity' => number_format(1, 2, '.', ''),
|
|
669
|
+
'line_unit_price' => number_format($tipAmount, 2, '.', ''),
|
|
670
|
+
'note' => 'Tip',
|
|
671
|
+
'classification_code' => '6511100',
|
|
672
|
+
'tax_product_code' => '319',
|
|
673
|
+
'taxes' => [
|
|
674
|
+
[
|
|
675
|
+
'tax_code' => 'VAT',
|
|
676
|
+
'description' => 'VAT',
|
|
677
|
+
'amount' => QPay::calculateTax($tipAmount),
|
|
678
|
+
'note' => 'Tip',
|
|
679
|
+
],
|
|
680
|
+
],
|
|
681
|
+
];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if ($deliveryTip && !$isPickup) {
|
|
685
|
+
$deliveryTipAmount = Storefront::calculateTipAmount($deliveryTip, $subtotal);
|
|
686
|
+
$lines[] = [
|
|
687
|
+
'line_description' => 'Delivery Tip',
|
|
688
|
+
'line_quantity' => number_format(1, 2, '.', ''),
|
|
689
|
+
'line_unit_price' => number_format($deliveryTipAmount, 2, '.', ''),
|
|
690
|
+
'note' => 'Delivery Tip',
|
|
691
|
+
'classification_code' => '6511100',
|
|
692
|
+
'tax_product_code' => '319',
|
|
693
|
+
'taxes' => [
|
|
694
|
+
[
|
|
695
|
+
'tax_code' => 'VAT',
|
|
696
|
+
'description' => 'VAT',
|
|
697
|
+
'amount' => QPay::calculateTax($deliveryTipAmount),
|
|
698
|
+
'note' => 'Delivery Tip',
|
|
699
|
+
],
|
|
700
|
+
],
|
|
701
|
+
];
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (!$isPickup) {
|
|
705
|
+
$serviceQuoteAmount = Utils::numbersOnly($serviceQuote->amount);
|
|
706
|
+
$lines[] = [
|
|
707
|
+
'line_description' => 'Delivery Fee',
|
|
708
|
+
'line_quantity' => number_format(1, 2, '.', ''),
|
|
709
|
+
'line_unit_price' => number_format($serviceQuoteAmount, 2, '.', ''),
|
|
710
|
+
'note' => 'Delivery Fee',
|
|
711
|
+
'classification_code' => '6511100',
|
|
712
|
+
'tax_product_code' => '319',
|
|
713
|
+
'taxes' => [
|
|
714
|
+
[
|
|
715
|
+
'tax_code' => 'VAT',
|
|
716
|
+
'description' => 'VAT',
|
|
717
|
+
'amount' => QPay::calculateTax($serviceQuoteAmount),
|
|
718
|
+
'note' => 'Delivery Fee',
|
|
719
|
+
],
|
|
720
|
+
],
|
|
721
|
+
];
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return $lines;
|
|
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
|
+
}
|
|
322
870
|
}
|