@fleetbase/storefront-engine 0.4.7 → 0.4.9
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/index.js +0 -10
- 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
|
@@ -39,7 +39,11 @@
|
|
|
39
39
|
/>
|
|
40
40
|
</InputGroup>
|
|
41
41
|
|
|
42
|
-
<ContentPanel
|
|
42
|
+
<ContentPanel
|
|
43
|
+
@title={{t "storefront.networks.index.network.index.general-network-settings-form.contact-social-panel.panel-title"}}
|
|
44
|
+
@open={{false}}
|
|
45
|
+
@wrapperClass="bordered-classic"
|
|
46
|
+
>
|
|
43
47
|
<InputGroup @name={{t "storefront.networks.index.network.index.general-network-settings-form.contact-social-panel.phone"}}>
|
|
44
48
|
<PhoneInput @value={{@model.phone}} @onInput={{fn (mut @model.phone)}} class="form-input w-full" />
|
|
45
49
|
</InputGroup>
|
|
@@ -75,7 +79,11 @@
|
|
|
75
79
|
/>
|
|
76
80
|
</ContentPanel>
|
|
77
81
|
|
|
78
|
-
<ContentPanel
|
|
82
|
+
<ContentPanel
|
|
83
|
+
@title={{t "storefront.networks.index.network.index.general-network-settings-form.logo-backdrop-panel.panel-title"}}
|
|
84
|
+
@open={{false}}
|
|
85
|
+
@wrapperClass="bordered-classic"
|
|
86
|
+
>
|
|
79
87
|
<InputGroup
|
|
80
88
|
@name={{t "storefront.networks.index.network.index.general-network-settings-form.logo-backdrop-panel.logo-label"}}
|
|
81
89
|
@helpText={{t "storefront.networks.index.network.index.general-network-settings-form.logo-backdrop-panel.logo-help-text"}}
|
|
@@ -125,12 +133,7 @@
|
|
|
125
133
|
</InputGroup>
|
|
126
134
|
</ContentPanel>
|
|
127
135
|
|
|
128
|
-
<ContentPanel
|
|
129
|
-
@title={{t "storefront.networks.index.network.index.general-network-settings-form.alert-panel.panel-title"}}
|
|
130
|
-
@open={{false}}
|
|
131
|
-
@pad={{true}}
|
|
132
|
-
@panelBodyClass="bg-gray-800"
|
|
133
|
-
>
|
|
136
|
+
<ContentPanel @title={{t "storefront.networks.index.network.index.general-network-settings-form.alert-panel.panel-title"}} @open={{false}} @wrapperClass="bordered-classic">
|
|
134
137
|
<div>
|
|
135
138
|
<p class="dark:text-gray-100 mb-4">{{t "storefront.networks.index.network.index.general-network-settings-form.alert-panel.panel-description"}}</p>
|
|
136
139
|
<InputGroup @name={{t "storefront.networks.index.network.index.general-network-settings-form.alert-panel.new-order-alert-label"}} @wrapperClass="mb-0">
|
|
@@ -175,6 +178,25 @@
|
|
|
175
178
|
/>
|
|
176
179
|
{{/if}}
|
|
177
180
|
</div>
|
|
181
|
+
<div class="input-group">
|
|
182
|
+
<Toggle
|
|
183
|
+
@isToggled={{@model.options.required_checkout_min}}
|
|
184
|
+
@onToggle={{fn (mut @model.options.required_checkout_min)}}
|
|
185
|
+
@helpText={{t "storefront.networks.index.network.index.general-network-settings-form.config-switches.minimum-order-amount-input-help-text"}}
|
|
186
|
+
>
|
|
187
|
+
<FaIcon @icon="dollar-sign" class="text-gray-600 dark:text-gray-400 mx-2" /><span class="dark:text-gray-100 text-sm">{{t
|
|
188
|
+
"storefront.networks.index.network.index.general-network-settings-form.config-switches.enable-minimum-order-amount"
|
|
189
|
+
}}</span>
|
|
190
|
+
</Toggle>
|
|
191
|
+
{{#if @model.options.required_checkout_min}}
|
|
192
|
+
<MoneyInput
|
|
193
|
+
@wrapperClass="mb-0 mt-2"
|
|
194
|
+
@name={{t "storefront.networks.index.network.index.general-network-settings-form.config-switches.minimum-order-amount-input-label"}}
|
|
195
|
+
@value={{@model.options.required_checkout_min_amount}}
|
|
196
|
+
@currency={{@model.currency}}
|
|
197
|
+
/>
|
|
198
|
+
{{/if}}
|
|
199
|
+
</div>
|
|
178
200
|
<div class="input-group">
|
|
179
201
|
<Toggle @isToggled={{@model.options.auto_accept_orders}} @onToggle={{fn (mut @model.options.auto_accept_orders)}}>
|
|
180
202
|
<FaIcon @icon="robot" class="text-gray-600 dark:text-gray-400 mx-2" /><span class="dark:text-gray-100 text-sm">{{t
|
|
@@ -2,37 +2,16 @@
|
|
|
2
2
|
<Overlay::Header @title={{t "storefront.networks.index.network.manage-network"}} @onPressCancel={{this.transitionBack}} />
|
|
3
3
|
<Overlay::Body class="p-0i" @increaseInnerBodyHeightBy="0" @wrapperClass="space-y-4 pt-4">
|
|
4
4
|
<div class="flex flex-col">
|
|
5
|
-
<div class="px-4 flex items-center section-header-title
|
|
5
|
+
<div class="px-4 flex items-center section-header-title">
|
|
6
6
|
<FaIcon @icon="network-wired" @size="lg" class="text-sky-500 mr-3" />
|
|
7
7
|
<h3 class="text-lg font-semibold dark:text-white">
|
|
8
8
|
{{@model.name}}
|
|
9
9
|
</h3>
|
|
10
10
|
</div>
|
|
11
|
-
<div class="section-header-actions w-full overflow-x-scroll lg:overflow-x-auto">
|
|
12
|
-
<div class="ui-tabs">
|
|
13
|
-
<nav>
|
|
14
|
-
<LinkTo @route="networks.index.network.index" class="ui-tab">
|
|
15
|
-
<FaIcon @icon="cogs" class="mr-2" />
|
|
16
|
-
<span>{{t "storefront.networks.index.network.settings"}}</span>
|
|
17
|
-
</LinkTo>
|
|
18
|
-
<LinkTo @route="networks.index.network.stores" class="ui-tab">
|
|
19
|
-
<FaIcon @icon="store" class="mr-2" />
|
|
20
|
-
<span>{{t "storefront.networks.index.network.stores.store"}}</span>
|
|
21
|
-
</LinkTo>
|
|
22
|
-
<LinkTo @route="networks.index.network.orders" class="ui-tab">
|
|
23
|
-
<FaIcon @icon="file-invoice-dollar" class="mr-2" />
|
|
24
|
-
<span>{{t "storefront.networks.index.network.orders"}}</span>
|
|
25
|
-
</LinkTo>
|
|
26
|
-
<LinkTo @route="networks.index.network.customers" class="ui-tab">
|
|
27
|
-
<FaIcon @icon="users" class="mr-2" />
|
|
28
|
-
<span>{{t "storefront.networks.index.network.customers"}}</span>
|
|
29
|
-
</LinkTo>
|
|
30
|
-
</nav>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
11
|
</div>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
12
|
+
<TabNavigation @tabs={{this.tabs}}>
|
|
13
|
+
{{outlet}}
|
|
14
|
+
<Spacer @height="400px" />
|
|
15
|
+
</TabNavigation>
|
|
37
16
|
</Overlay::Body>
|
|
38
17
|
</Overlay>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<div class="overflow-y-scroll h-screen">
|
|
2
|
+
<div class="h-screen">
|
|
3
|
+
<main class="h-screen max-w-xl mx-auto pt-4">
|
|
4
|
+
<form class="h-screen" {{on "submit" this.sendPushNotification}}>
|
|
5
|
+
<div class="space-y-4 h-screen">
|
|
6
|
+
<div>
|
|
7
|
+
<h1 class="text-lg leading-6 font-bold text-gray-900 dark:text-gray-100">
|
|
8
|
+
{{t "storefront.promotions.push-notifications.header"}}
|
|
9
|
+
</h1>
|
|
10
|
+
<p class="mt-1 text-sm text-gray-500">
|
|
11
|
+
{{t "storefront.promotions.push-notifications.description"}}
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<InputGroup
|
|
16
|
+
@name={{t "storefront.promotions.push-notifications.title-label"}}
|
|
17
|
+
@value={{this.title}}
|
|
18
|
+
@placeholder={{t "storefront.promotions.push-notifications.title-placeholder"}}
|
|
19
|
+
@helpText={{t "storefront.promotions.push-notifications.title-help-text"}}
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
<InputGroup @name={{t "storefront.promotions.push-notifications.body-label"}} @helpText={{t "storefront.promotions.push-notifications.body-help-text"}}>
|
|
23
|
+
<Textarea @value={{this.body}} placeholder={{t "storefront.promotions.push-notifications.body-placeholder"}} rows="4" class="form-input w-full" />
|
|
24
|
+
</InputGroup>
|
|
25
|
+
|
|
26
|
+
<div class="mb-4">
|
|
27
|
+
<Toggle
|
|
28
|
+
@isToggled={{this.selectAllCustomers}}
|
|
29
|
+
@onToggle={{fn (mut this.selectAllCustomers)}}
|
|
30
|
+
@wrapperClass="justify-start"
|
|
31
|
+
@label={{t "storefront.promotions.push-notifications.select-all-customers"}}
|
|
32
|
+
@helpText={{t "storefront.promotions.push-notifications.select-all-customers-help-text"}}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
{{#unless this.selectAllCustomers}}
|
|
37
|
+
<InputGroup @name={{t "storefront.promotions.push-notifications.customers-label"}}>
|
|
38
|
+
<ModelSelectMultiple
|
|
39
|
+
@modelName="contact"
|
|
40
|
+
@query={{hash type="customer"}}
|
|
41
|
+
@selectedModel={{this.selectedCustomers}}
|
|
42
|
+
@placeholder={{t "storefront.promotions.push-notifications.customers-placeholder"}}
|
|
43
|
+
@infiniteScroll={{true}}
|
|
44
|
+
@renderInPlace={{true}}
|
|
45
|
+
@onChange={{fn (mut this.selectedCustomers)}}
|
|
46
|
+
@triggerClass="form-select form-input w-full"
|
|
47
|
+
as |model|
|
|
48
|
+
>
|
|
49
|
+
<div class="flex flex-row space-x-2">
|
|
50
|
+
<div class="hide-from-trigger">
|
|
51
|
+
<Image
|
|
52
|
+
src={{model.photo_url}}
|
|
53
|
+
@fallbackSrc={{config "defaultValues.customerImage"}}
|
|
54
|
+
alt={{model.name}}
|
|
55
|
+
height="40"
|
|
56
|
+
width="40"
|
|
57
|
+
class="h-10 w-10 rounded-lg shadow-sm"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
<div>
|
|
61
|
+
<div class="font-semibold normalize-in-trigger">{{model.name}}</div>
|
|
62
|
+
<div class="text-xs hide-from-trigger">{{n-a model.email}}</div>
|
|
63
|
+
<div class="text-xs hide-from-trigger">{{n-a model.phone}}</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</ModelSelectMultiple>
|
|
67
|
+
</InputGroup>
|
|
68
|
+
{{/unless}}
|
|
69
|
+
|
|
70
|
+
<div class="flex justify-end">
|
|
71
|
+
<Button
|
|
72
|
+
@type="primary"
|
|
73
|
+
@icon={{if this.isLoading "spinner" "paper-plane"}}
|
|
74
|
+
@iconPrefix={{if this.isLoading "fas fa-spin" "fas"}}
|
|
75
|
+
@text={{t "storefront.promotions.push-notifications.send-button"}}
|
|
76
|
+
@isLoading={{this.isLoading}}
|
|
77
|
+
@disabled={{this.isLoading}}
|
|
78
|
+
@onClick={{this.sendPushNotification}}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</form>
|
|
83
|
+
</main>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<TabNavigation @tabs={{this.tabs}} @contentClass="scrollable" @tablistClass="px-4 flex-row-reverse">
|
|
2
|
+
<:actions>
|
|
3
|
+
<h3 class="uppercase text-xs tracking-wide text-gray-700 dark:text-gray-400 font-semibold">{{t "storefront.promotions.title"}}</h3>
|
|
4
|
+
</:actions>
|
|
5
|
+
<:default>
|
|
6
|
+
{{outlet}}
|
|
7
|
+
</:default>
|
|
8
|
+
</TabNavigation>
|
|
@@ -254,6 +254,25 @@
|
|
|
254
254
|
/>
|
|
255
255
|
{{/if}}
|
|
256
256
|
</div>
|
|
257
|
+
<div class="input-group">
|
|
258
|
+
<Toggle
|
|
259
|
+
@isToggled={{@model.options.required_checkout_min}}
|
|
260
|
+
@onToggle={{fn (mut @model.options.required_checkout_min)}}
|
|
261
|
+
@helpText={{t "storefront.settings.index.minimum-order-amount-help-text"}}
|
|
262
|
+
>
|
|
263
|
+
<FaIcon @icon="dollar-sign" class="text-gray-600 dark:text-gray-400 mx-2" /><span class="dark:text-gray-100 text-sm">{{t
|
|
264
|
+
"storefront.settings.index.enable-minimum-order-amount"
|
|
265
|
+
}}</span>
|
|
266
|
+
</Toggle>
|
|
267
|
+
{{#if @model.options.required_checkout_min}}
|
|
268
|
+
<MoneyInput
|
|
269
|
+
@wrapperClass="mb-0 mt-2"
|
|
270
|
+
@name={{t "storefront.settings.index.minimum-order-amount"}}
|
|
271
|
+
@value={{@model.options.required_checkout_min_amount}}
|
|
272
|
+
@currency={{@model.currency}}
|
|
273
|
+
/>
|
|
274
|
+
{{/if}}
|
|
275
|
+
</div>
|
|
257
276
|
<div class="input-group">
|
|
258
277
|
<Toggle @isToggled={{@model.options.auto_accept_orders}} @onToggle={{fn (mut @model.options.auto_accept_orders)}}>
|
|
259
278
|
<FaIcon @icon="robot" class="text-gray-600 dark:text-gray-400 mx-2" /><span class="dark:text-gray-100 text-sm">{{t
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/storefront-engine/controllers/promotions/push-notifications';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/storefront-engine/controllers/promotions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/storefront-engine/routes/promotions/push-notifications';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/storefront-engine/routes/promotions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{{outlet}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{{outlet}}
|
package/composer.json
CHANGED
package/config/environment.js
CHANGED
package/extension.json
CHANGED
package/index.js
CHANGED
|
@@ -8,16 +8,6 @@ const path = require('path');
|
|
|
8
8
|
module.exports = buildEngine({
|
|
9
9
|
name,
|
|
10
10
|
|
|
11
|
-
postprocessTree(type, tree) {
|
|
12
|
-
if (type === 'css') {
|
|
13
|
-
tree = new Funnel(tree, {
|
|
14
|
-
exclude: ['**/@fleetbase/ember-ui/**/*.css'],
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return tree;
|
|
19
|
-
},
|
|
20
|
-
|
|
21
11
|
lazyLoading: {
|
|
22
12
|
enabled: true,
|
|
23
13
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/storefront-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
|
|
5
5
|
"fleetbase": {
|
|
6
6
|
"route": "storefront",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@babel/core": "^7.23.2",
|
|
47
|
-
"@fleetbase/ember-core": "^0.3.
|
|
48
|
-
"@fleetbase/ember-ui": "^0.3.
|
|
49
|
-
"@fleetbase/fleetops-data": "^0.1.
|
|
47
|
+
"@fleetbase/ember-core": "^0.3.8",
|
|
48
|
+
"@fleetbase/ember-ui": "^0.3.14",
|
|
49
|
+
"@fleetbase/fleetops-data": "^0.1.23",
|
|
50
50
|
"@fortawesome/ember-fontawesome": "^2.0.0",
|
|
51
51
|
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
|
52
52
|
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
|
@@ -95,4 +95,65 @@ class ActionController extends Controller
|
|
|
95
95
|
|
|
96
96
|
return response()->json($metrics);
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Send promotional push notification to selected customers.
|
|
101
|
+
*
|
|
102
|
+
* @return \Illuminate\Http\Response
|
|
103
|
+
*/
|
|
104
|
+
public function sendPushNotification(Request $request)
|
|
105
|
+
{
|
|
106
|
+
$title = $request->input('title');
|
|
107
|
+
$body = $request->input('body');
|
|
108
|
+
$customerIds = $request->input('customers', []);
|
|
109
|
+
$storeId = $request->input('store');
|
|
110
|
+
$selectAll = $request->boolean('select_all', false);
|
|
111
|
+
|
|
112
|
+
// Validate inputs
|
|
113
|
+
if (!$title || !$body) {
|
|
114
|
+
return response()->json(['error' => 'Title and body are required'], 400);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!$selectAll && empty($customerIds)) {
|
|
118
|
+
return response()->json(['error' => 'At least one customer must be selected'], 400);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get the store
|
|
122
|
+
$store = Store::where('public_id', $storeId)->first();
|
|
123
|
+
if (!$store) {
|
|
124
|
+
return response()->json(['error' => 'Store not found'], 404);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Get customers
|
|
128
|
+
if ($selectAll) {
|
|
129
|
+
// Get all customers for this store's company
|
|
130
|
+
$customers = Contact::where('company_uuid', session('company'))
|
|
131
|
+
->where('type', 'customer')
|
|
132
|
+
->get();
|
|
133
|
+
} else {
|
|
134
|
+
// Get only selected customers
|
|
135
|
+
$customers = Contact::whereIn('uuid', $customerIds)
|
|
136
|
+
->where('company_uuid', session('company'))
|
|
137
|
+
->where('type', 'customer')
|
|
138
|
+
->get();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Send notifications
|
|
142
|
+
$sentCount = 0;
|
|
143
|
+
foreach ($customers as $customer) {
|
|
144
|
+
try {
|
|
145
|
+
$customer->notify(new \Fleetbase\Storefront\Notifications\PromotionalPushNotification($title, $body, $store));
|
|
146
|
+
$sentCount++;
|
|
147
|
+
} catch (\Exception $e) {
|
|
148
|
+
// Log error but continue with other customers
|
|
149
|
+
\Log::error('Failed to send push notification to customer: ' . $customer->uuid, ['error' => $e->getMessage()]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return response()->json([
|
|
154
|
+
'status' => 'OK',
|
|
155
|
+
'sent_count' => $sentCount,
|
|
156
|
+
'total' => count($customers),
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
98
159
|
}
|
|
@@ -63,7 +63,7 @@ class CheckoutController extends Controller
|
|
|
63
63
|
|
|
64
64
|
// handle cash orders
|
|
65
65
|
if ($isCashOnDelivery) {
|
|
66
|
-
return static::initializeCashCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions);
|
|
66
|
+
return static::initializeCashCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions, $request);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
if (!$gateway) {
|
|
@@ -72,18 +72,18 @@ class CheckoutController extends Controller
|
|
|
72
72
|
|
|
73
73
|
// handle checkout initialization based on gateway
|
|
74
74
|
if ($gateway->isStripeGateway) {
|
|
75
|
-
return static::initializeStripeCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions);
|
|
75
|
+
return static::initializeStripeCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions, $request);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// handle checkout initialization based on gateway
|
|
79
79
|
if ($gateway->isQPayGateway) {
|
|
80
|
-
return static::initializeQPayCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions);
|
|
80
|
+
return static::initializeQPayCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions, $request);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
return response()->apiError('Unable to initialize checkout!');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
public static function initializeCashCheckout(Contact $customer, Gateway $gateway, ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions)
|
|
86
|
+
public static function initializeCashCheckout(Contact $customer, Gateway $gateway, ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions, $request)
|
|
87
87
|
{
|
|
88
88
|
// check if pickup order
|
|
89
89
|
$isPickup = $checkoutOptions->is_pickup;
|
|
@@ -132,7 +132,7 @@ class CheckoutController extends Controller
|
|
|
132
132
|
]);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
public static function initializeStripeCheckout(Contact $customer, Gateway $gateway, ?ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions)
|
|
135
|
+
public static function initializeStripeCheckout(Contact $customer, Gateway $gateway, ?ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions, $request)
|
|
136
136
|
{
|
|
137
137
|
// check if pickup order
|
|
138
138
|
$isPickup = $checkoutOptions->is_pickup;
|
|
@@ -416,7 +416,7 @@ class CheckoutController extends Controller
|
|
|
416
416
|
]);
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
public static function initializeQPayCheckout(Contact $customer, Gateway $gateway, ?ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions)
|
|
419
|
+
public static function initializeQPayCheckout(Contact $customer, Gateway $gateway, ?ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions, $request)
|
|
420
420
|
{
|
|
421
421
|
// Get store info
|
|
422
422
|
$about = Storefront::about();
|
|
@@ -424,6 +424,12 @@ class CheckoutController extends Controller
|
|
|
424
424
|
// check if pickup order
|
|
425
425
|
$isPickup = $checkoutOptions->is_pickup;
|
|
426
426
|
|
|
427
|
+
// Get ebarimt company registration number if any
|
|
428
|
+
$ebarimtRegistationNumber = $request->ebarimt_registration_no ?? '';
|
|
429
|
+
if ($ebarimtRegistationNumber) {
|
|
430
|
+
$customer->updateMeta('ebarimt_registration_no', $ebarimtRegistationNumber);
|
|
431
|
+
}
|
|
432
|
+
|
|
427
433
|
// get amount/subtotal
|
|
428
434
|
$amount = static::calculateCheckoutAmount($cart, $serviceQuote, $checkoutOptions);
|
|
429
435
|
$currency = $cart->getCurrency();
|
|
@@ -470,6 +476,7 @@ class CheckoutController extends Controller
|
|
|
470
476
|
$callbackUrl = Utils::apiUrl('storefront/v1/checkouts/capture-qpay', $callbackParams);
|
|
471
477
|
|
|
472
478
|
// Create invoice description
|
|
479
|
+
$taxType = '1'; // Start with VAT required
|
|
473
480
|
$ebarimtInvoiceCode = $gateway->sandbox ? 'TEST_INVOICE' : $gateway->config?->ebarimt_invoice_id ?? null;
|
|
474
481
|
$invoiceAmount = $amount;
|
|
475
482
|
$invoiceCode = $gateway->sandbox ? 'TEST_INVOICE' : $gateway->config?->invoice_id ?? null;
|
|
@@ -478,18 +485,25 @@ class CheckoutController extends Controller
|
|
|
478
485
|
$senderInvoiceNo = $checkout->public_id;
|
|
479
486
|
$districtCode = $gateway->config?->district_code ?? null;
|
|
480
487
|
$invoiceReceiverData = Utils::filterArray([
|
|
488
|
+
'register' => $ebarimtRegistationNumber,
|
|
481
489
|
'name' => $customer->name,
|
|
482
490
|
'email' => $customer->email ?? null,
|
|
483
491
|
'phone' => $customer->phone ?? null,
|
|
484
492
|
]);
|
|
493
|
+
|
|
494
|
+
// Create QPay line items
|
|
485
495
|
$lines = QPay::createQpayInitialLines($cart, $serviceQuote, $checkoutOptions);
|
|
486
496
|
foreach ($cart->items as $item) {
|
|
487
|
-
$
|
|
497
|
+
$classificationCode = QPay::getCartItemClassificationCode($item);
|
|
498
|
+
$isVatExempt = QPay::isTaxFreeClassificationCode($classificationCode);
|
|
499
|
+
|
|
500
|
+
$line = [
|
|
488
501
|
'line_description' => $item->name,
|
|
489
502
|
'line_quantity' => number_format($item->quantity ?? 1, 2, '.', ''),
|
|
490
503
|
'line_unit_price' => number_format($item->price, 2, '.', ''),
|
|
491
504
|
'note' => $checkout->public_id,
|
|
492
|
-
'classification_code' =>
|
|
505
|
+
'classification_code' => $classificationCode,
|
|
506
|
+
'tax_product_code' => QPay::getCartItemTaxProductCode($item),
|
|
493
507
|
'taxes' => [
|
|
494
508
|
[
|
|
495
509
|
'tax_code' => 'VAT',
|
|
@@ -499,12 +513,14 @@ class CheckoutController extends Controller
|
|
|
499
513
|
],
|
|
500
514
|
],
|
|
501
515
|
];
|
|
516
|
+
|
|
517
|
+
$lines[] = $line;
|
|
502
518
|
}
|
|
503
519
|
|
|
504
520
|
// Create qpay invoice
|
|
505
521
|
$invoice = null;
|
|
506
522
|
if ($ebarimtInvoiceCode) {
|
|
507
|
-
$invoice = $qpay->createEbarimtInvoice($ebarimtInvoiceCode, $senderInvoiceNo, $invoiceReceiverCode, $invoiceReceiverData, $invoiceDescription,
|
|
523
|
+
$invoice = $qpay->createEbarimtInvoice($ebarimtInvoiceCode, $senderInvoiceNo, $invoiceReceiverCode, $invoiceReceiverData, $invoiceDescription, $taxType, $districtCode, $lines);
|
|
508
524
|
} else {
|
|
509
525
|
$invoice = $qpay->createSimpleInvoice($invoiceAmount, $invoiceCode, $invoiceDescription, $invoiceReceiverCode, $senderInvoiceNo, $callbackUrl);
|
|
510
526
|
}
|
|
@@ -332,6 +332,7 @@ class Cart extends StorefrontModel
|
|
|
332
332
|
'subtotal' => $subtotal,
|
|
333
333
|
'variants' => $variants,
|
|
334
334
|
'addons' => $addons,
|
|
335
|
+
'meta' => $product->meta ?? [],
|
|
335
336
|
];
|
|
336
337
|
|
|
337
338
|
// If item was added from a food truck
|
|
@@ -677,7 +678,7 @@ class Cart extends StorefrontModel
|
|
|
677
678
|
*/
|
|
678
679
|
public static function findProduct(string $id): ?Product
|
|
679
680
|
{
|
|
680
|
-
return Product::select(['uuid', 'store_uuid', 'public_id', 'name', 'description', 'price', 'currency', 'sale_price', 'is_on_sale'])->where(['public_id' => $id])->with([])->first();
|
|
681
|
+
return Product::select(['uuid', 'store_uuid', 'public_id', 'name', 'description', 'price', 'currency', 'sale_price', 'is_on_sale', 'meta'])->where(['public_id' => $id])->with([])->first();
|
|
681
682
|
}
|
|
682
683
|
|
|
683
684
|
public function getCurrency(?string $fallbackCurrency = null): ?string
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\Storefront\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\Casts\Money;
|
|
6
7
|
use Fleetbase\FleetOps\Models\OrderConfig;
|
|
7
8
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
9
|
use Fleetbase\Models\Category;
|
|
@@ -228,6 +229,30 @@ class Network extends StorefrontModel
|
|
|
228
229
|
return $this->stores()->count();
|
|
229
230
|
}
|
|
230
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Mutator for the `options` attribute.
|
|
234
|
+
* Allows special handling for nested keys while keeping JSON casting intact.
|
|
235
|
+
*/
|
|
236
|
+
public function setOptionsAttribute($value)
|
|
237
|
+
{
|
|
238
|
+
// Ensure we operate on a PHP array
|
|
239
|
+
if (is_string($value)) {
|
|
240
|
+
$value = json_decode($value, true) ?? [];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!is_array($value)) {
|
|
244
|
+
$value = [];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Special handling for `required_checkout_min_amount`
|
|
248
|
+
if (array_key_exists('required_checkout_min_amount', $value)) {
|
|
249
|
+
$value['required_checkout_min_amount'] = Money::apply($value['required_checkout_min_amount']);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Assign the array back — JSON cast handles encoding
|
|
253
|
+
$this->attributes['options'] = $value;
|
|
254
|
+
}
|
|
255
|
+
|
|
231
256
|
/**
|
|
232
257
|
* Adds a new store to the network.
|
|
233
258
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\Storefront\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\Casts\Money;
|
|
6
7
|
use Fleetbase\FleetOps\Models\OrderConfig;
|
|
7
8
|
use Fleetbase\FleetOps\Models\Place;
|
|
8
9
|
use Fleetbase\Models\Category;
|
|
@@ -292,6 +293,30 @@ class Store extends StorefrontModel
|
|
|
292
293
|
->wherePivotNull('deleted_at');
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Mutator for the `options` attribute.
|
|
298
|
+
* Allows special handling for nested keys while keeping JSON casting intact.
|
|
299
|
+
*/
|
|
300
|
+
public function setOptionsAttribute($value)
|
|
301
|
+
{
|
|
302
|
+
// Ensure we operate on a PHP array
|
|
303
|
+
if (is_string($value)) {
|
|
304
|
+
$value = json_decode($value, true) ?? [];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!is_array($value)) {
|
|
308
|
+
$value = [];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Special handling for `required_checkout_min_amount`
|
|
312
|
+
if (array_key_exists('required_checkout_min_amount', $value)) {
|
|
313
|
+
$value['required_checkout_min_amount'] = Money::apply($value['required_checkout_min_amount']);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Assign the array back — JSON cast handles encoding
|
|
317
|
+
$this->attributes['options'] = $value;
|
|
318
|
+
}
|
|
319
|
+
|
|
295
320
|
/**
|
|
296
321
|
* @var string
|
|
297
322
|
*/
|