@fleetbase/storefront-engine 0.3.21 → 0.3.23
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/styles/storefront-engine.css +8 -0
- package/addon/templates/products/index/category/new.hbs +1 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/src/Console/Commands/SendOrderNotification.php +118 -0
- package/server/src/Http/Controllers/ProductController.php +0 -48
- package/server/src/Http/Controllers/v1/CheckoutController.php +2 -2
- package/server/src/Http/Controllers/v1/CustomerController.php +255 -16
- package/server/src/Http/Requests/{CustomerRequest.php → StorefrontCustomerRequest.php} +1 -1
- package/server/src/Http/Resources/Product.php +6 -5
- package/server/src/Observers/ProductObserver.php +9 -53
- package/server/src/Providers/StorefrontServiceProvider.php +1 -0
- package/server/src/routes.php +4 -1
|
@@ -59,3 +59,11 @@
|
|
|
59
59
|
.status-badge.order-canceled-status-badge > span svg {
|
|
60
60
|
color: #fca5a5;
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/** hotfix nav tab */
|
|
64
|
+
body[data-theme='dark'] .ui-tabs.overlay-content-panel > ul .nav-item.active,
|
|
65
|
+
body[data-theme='dark'] .ui-tabs.overlay-content-panel > nav .nav-item.active,
|
|
66
|
+
body[data-theme='dark'] .ui-tabs.overlay-content-panel > ul .ui-tab.active,
|
|
67
|
+
body[data-theme='dark'] .ui-tabs.overlay-content-panel > nav .ui-tab.active {
|
|
68
|
+
background-color: #202A37;
|
|
69
|
+
}
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
<Button @text="New Variant" @icon="plus" @iconPrefix="fas" @onClick={{this.createProductVariant}} @permission="storefront create product-variant" />
|
|
143
143
|
</div>
|
|
144
144
|
</div>
|
|
145
|
-
<Tabs as |Tab|>
|
|
145
|
+
<Tabs class="overlay-content-panel" as |Tab|>
|
|
146
146
|
{{#each this.product.variants as |variant|}}
|
|
147
147
|
<Tab @title={{variant.name}}>
|
|
148
148
|
<div class="px-4 py-3">
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\Storefront\Console\Commands;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
7
|
+
use Illuminate\Console\Command;
|
|
8
|
+
|
|
9
|
+
class SendOrderNotification extends Command
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* The name and signature of the console command.
|
|
13
|
+
*
|
|
14
|
+
* @var string
|
|
15
|
+
*/
|
|
16
|
+
protected $signature = 'storefront:send-notification {--id= : The ID of the order} {--event= : The name of the event to trigger}';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The console command description.
|
|
20
|
+
*
|
|
21
|
+
* @var string
|
|
22
|
+
*/
|
|
23
|
+
protected $description = 'Manually trigger a push notification for an order';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Mapping of event names to notification classes.
|
|
27
|
+
*
|
|
28
|
+
* @var array
|
|
29
|
+
*/
|
|
30
|
+
protected $eventToNotification = [
|
|
31
|
+
'created' => \Fleetbase\Storefront\Notifications\StorefrontOrderCreated::class,
|
|
32
|
+
'canceled' => \Fleetbase\Storefront\Notifications\StorefrontOrderCanceled::class,
|
|
33
|
+
'completed' => \Fleetbase\Storefront\Notifications\StorefrontOrderCompleted::class,
|
|
34
|
+
'driver_assigned' => \Fleetbase\Storefront\Notifications\StorefrontOrderDriverAssigned::class,
|
|
35
|
+
'enroute' => \Fleetbase\Storefront\Notifications\StorefrontOrderEnroute::class,
|
|
36
|
+
'preparing' => \Fleetbase\Storefront\Notifications\StorefrontOrderPreparing::class,
|
|
37
|
+
'ready_for_pickup' => \Fleetbase\Storefront\Notifications\StorefrontOrderReadyForPickup::class,
|
|
38
|
+
'nearby' => \Fleetbase\Storefront\Notifications\StorefrontOrderNearby::class,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execute the console command.
|
|
43
|
+
*
|
|
44
|
+
* @return int
|
|
45
|
+
*/
|
|
46
|
+
public function handle()
|
|
47
|
+
{
|
|
48
|
+
// Get order ID and event from options
|
|
49
|
+
$orderId = $this->option('id');
|
|
50
|
+
$event = $this->option('event');
|
|
51
|
+
|
|
52
|
+
// Prompt user to search for order ID if not provided
|
|
53
|
+
if (!$orderId) {
|
|
54
|
+
$orderId = $this->ask('Enter the order ID to trigger the notification');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Attempt to find the order
|
|
58
|
+
$order = Order::where('public_id', $orderId)->first();
|
|
59
|
+
|
|
60
|
+
if (!$order) {
|
|
61
|
+
$this->error('Order not found!');
|
|
62
|
+
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Load customer relation
|
|
67
|
+
$order->loadMissing('customer');
|
|
68
|
+
|
|
69
|
+
if (!$order->customer) {
|
|
70
|
+
$this->error('Order does not have an associated customer!');
|
|
71
|
+
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Prompt user to select event if not provided
|
|
76
|
+
if (!$event) {
|
|
77
|
+
$event = $this->choice(
|
|
78
|
+
'Select the event to trigger',
|
|
79
|
+
array_keys($this->eventToNotification),
|
|
80
|
+
'created' // Default event
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolve notification class
|
|
85
|
+
$notificationClass = $this->eventToNotification[$event] ?? null;
|
|
86
|
+
|
|
87
|
+
if (!$notificationClass) {
|
|
88
|
+
$this->error('Invalid event selected!');
|
|
89
|
+
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// nearby notification requires more arguments
|
|
94
|
+
try {
|
|
95
|
+
if ($event === 'nearby') {
|
|
96
|
+
$origin = $order->payload->getPickupOrFirstWaypoint();
|
|
97
|
+
$destination = $order->payload->getDropoffOrLastWaypoint();
|
|
98
|
+
$matrix = Utils::getDrivingDistanceAndTime($origin, $destination);
|
|
99
|
+
$distance = $matrix->distance;
|
|
100
|
+
$time = $matrix->time;
|
|
101
|
+
|
|
102
|
+
// Trigger notification
|
|
103
|
+
$order->customer->notify(new $notificationClass($order, $distance, $time));
|
|
104
|
+
} else {
|
|
105
|
+
// Trigger notification
|
|
106
|
+
$order->customer->notify(new $notificationClass($order));
|
|
107
|
+
}
|
|
108
|
+
} catch (\Exception $e) {
|
|
109
|
+
$this->error($e->getMessage());
|
|
110
|
+
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$this->info("Notification '{$event}' has been triggered for order ID '{$orderId}'.");
|
|
115
|
+
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -10,7 +10,6 @@ use Fleetbase\Storefront\Imports\ProductsImport;
|
|
|
10
10
|
use Fleetbase\Storefront\Jobs\DownloadProductImageUrl;
|
|
11
11
|
use Fleetbase\Storefront\Models\Product;
|
|
12
12
|
use Fleetbase\Storefront\Models\Store;
|
|
13
|
-
use Fleetbase\Support\Http;
|
|
14
13
|
use Illuminate\Http\Request;
|
|
15
14
|
use Illuminate\Support\Arr;
|
|
16
15
|
use Illuminate\Support\Str;
|
|
@@ -25,53 +24,6 @@ class ProductController extends StorefrontController
|
|
|
25
24
|
*/
|
|
26
25
|
public $resource = 'product';
|
|
27
26
|
|
|
28
|
-
/**
|
|
29
|
-
* Update a Product record.
|
|
30
|
-
* This update method was overwritten because the ProductObserver
|
|
31
|
-
* isn't firing on the `updated` callback.
|
|
32
|
-
*
|
|
33
|
-
* @return \Fleetbase\Storefront\Http\Resources\Product
|
|
34
|
-
*/
|
|
35
|
-
public function updateRecord(Request $request, string $id)
|
|
36
|
-
{
|
|
37
|
-
try {
|
|
38
|
-
$this->validateRequest($request);
|
|
39
|
-
$record = $this->model->updateRecordFromRequest($request, $id, function (&$request, Product &$product) {
|
|
40
|
-
$addonCategories = $request->array('product.addon_categories');
|
|
41
|
-
$variants = $request->array('product.variants');
|
|
42
|
-
$files = $request->array('product.files');
|
|
43
|
-
|
|
44
|
-
// dd($addonCategories);
|
|
45
|
-
|
|
46
|
-
// save addon categories
|
|
47
|
-
$product->setAddonCategories($addonCategories);
|
|
48
|
-
|
|
49
|
-
// save product variants
|
|
50
|
-
$product->setProductVariants($variants);
|
|
51
|
-
|
|
52
|
-
// set keys on files
|
|
53
|
-
foreach ($files as $file) {
|
|
54
|
-
$fileRecord = File::where('uuid', $file['uuid'])->first();
|
|
55
|
-
$fileRecord->setKey($product);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
if (Http::isInternalRequest($request)) {
|
|
60
|
-
$this->resource::wrap($this->resourceSingularlName);
|
|
61
|
-
|
|
62
|
-
return new $this->resource($record);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return new $this->resource($record);
|
|
66
|
-
} catch (\Exception $e) {
|
|
67
|
-
return response()->error($e->getMessage());
|
|
68
|
-
} catch (\Illuminate\Database\QueryException $e) {
|
|
69
|
-
return response()->error($e->getMessage());
|
|
70
|
-
} catch (\Fleetbase\Exceptions\FleetbaseRequestValidationException $e) {
|
|
71
|
-
return response()->error($e->getErrors());
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
27
|
/**
|
|
76
28
|
* List all activity options for current order.
|
|
77
29
|
*
|
|
@@ -186,7 +186,7 @@ class CheckoutController extends Controller
|
|
|
186
186
|
|
|
187
187
|
// Check if customer has a saved default payment method
|
|
188
188
|
if (StripeUtils::isCustomerPaymentMethodValid($customer)) {
|
|
189
|
-
$paymentIntentData['payment_method'] =
|
|
189
|
+
$paymentIntentData['payment_method'] = $customer->getMeta('stripe_payment_method_id');
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
try {
|
|
@@ -246,7 +246,7 @@ class CheckoutController extends Controller
|
|
|
246
246
|
|
|
247
247
|
// Check if customer has a saved default payment method
|
|
248
248
|
if (StripeUtils::isCustomerPaymentMethodValid($customer)) {
|
|
249
|
-
$paymentIntentData['payment_method'] =
|
|
249
|
+
$paymentIntentData['payment_method'] = $customer->getMeta('stripe_payment_method_id');
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
try {
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\Storefront\Http\Controllers\v1;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\Auth\AppleVerifier;
|
|
6
|
+
use Fleetbase\Auth\GoogleVerifier;
|
|
5
7
|
use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
|
|
6
8
|
use Fleetbase\FleetOps\Http\Requests\UpdateContactRequest;
|
|
7
9
|
use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
|
|
@@ -36,7 +38,7 @@ class CustomerController extends Controller
|
|
|
36
38
|
$customer = Storefront::getCustomerFromToken();
|
|
37
39
|
|
|
38
40
|
if (!$customer) {
|
|
39
|
-
return response()->
|
|
41
|
+
return response()->apiError('Not authorized to register device for cutomer');
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
$device = UserDevice::firstOrCreate(
|
|
@@ -68,7 +70,7 @@ class CustomerController extends Controller
|
|
|
68
70
|
$customer = Storefront::getCustomerFromToken();
|
|
69
71
|
|
|
70
72
|
if (!$customer) {
|
|
71
|
-
return response()->
|
|
73
|
+
return response()->apiError('Not authorized to view customers orders');
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
$results = Order::queryWithRequest($request, function (&$query) use ($customer) {
|
|
@@ -96,7 +98,7 @@ class CustomerController extends Controller
|
|
|
96
98
|
$customer = Storefront::getCustomerFromToken();
|
|
97
99
|
|
|
98
100
|
if (!$customer) {
|
|
99
|
-
return response()->
|
|
101
|
+
return response()->apiError('Not authorized to view customers places');
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
$results = Place::queryWithRequest($request, function (&$query) use ($customer) {
|
|
@@ -121,7 +123,7 @@ class CustomerController extends Controller
|
|
|
121
123
|
|
|
122
124
|
// validate identity
|
|
123
125
|
if ($mode === 'email' && !$isEmail) {
|
|
124
|
-
return response()->
|
|
126
|
+
return response()->apiError('Invalid email provided for identity');
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
// prepare phone number
|
|
@@ -176,7 +178,7 @@ class CustomerController extends Controller
|
|
|
176
178
|
// verify code
|
|
177
179
|
$isVerified = VerificationCode::where(['code' => $code, 'for' => 'storefront_create_customer', 'meta->identity' => $identity])->exists();
|
|
178
180
|
if (!$isVerified) {
|
|
179
|
-
return response()->
|
|
181
|
+
return response()->apiError('Invalid verification code provided!');
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
// check for existing user to attach contact to
|
|
@@ -250,7 +252,7 @@ class CustomerController extends Controller
|
|
|
250
252
|
try {
|
|
251
253
|
$contact = Contact::findRecordOrFail($id);
|
|
252
254
|
} catch (ModelNotFoundException $exception) {
|
|
253
|
-
return response()->
|
|
255
|
+
return response()->apiError('Customer resource not found.');
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
// get request input
|
|
@@ -303,7 +305,7 @@ class CustomerController extends Controller
|
|
|
303
305
|
try {
|
|
304
306
|
$contact = Contact::findRecordOrFail($id);
|
|
305
307
|
} catch (ModelNotFoundException $exception) {
|
|
306
|
-
return response()->
|
|
308
|
+
return response()->apiError('Customer resource not found.');
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
// response the customer resource
|
|
@@ -325,7 +327,7 @@ class CustomerController extends Controller
|
|
|
325
327
|
try {
|
|
326
328
|
$contact = Contact::findRecordOrFail($id);
|
|
327
329
|
} catch (ModelNotFoundException $exception) {
|
|
328
|
-
return response()->
|
|
330
|
+
return response()->apiError('Customer resource not found.');
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
// delete the product
|
|
@@ -349,7 +351,7 @@ class CustomerController extends Controller
|
|
|
349
351
|
$user = User::where('email', $identity)->orWhere('phone', static::phone($identity))->first();
|
|
350
352
|
|
|
351
353
|
if (!Hash::check($password, $user->password)) {
|
|
352
|
-
return response()->
|
|
354
|
+
return response()->apiError('Authentication failed using password provided.', 401);
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
// get the storefront or network logging in for
|
|
@@ -376,7 +378,7 @@ class CustomerController extends Controller
|
|
|
376
378
|
try {
|
|
377
379
|
$token = $user->createToken($contact->uuid);
|
|
378
380
|
} catch (\Exception $e) {
|
|
379
|
-
return response()->
|
|
381
|
+
return response()->apiError($e->getMessage());
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
$contact->token = $token->plainTextToken;
|
|
@@ -397,7 +399,7 @@ class CustomerController extends Controller
|
|
|
397
399
|
$user = User::where('phone', $phone)->whereNull('deleted_at')->withoutGlobalScopes()->first();
|
|
398
400
|
|
|
399
401
|
if (!$user) {
|
|
400
|
-
return response()->
|
|
402
|
+
return response()->apiError('No customer with this phone # found.');
|
|
401
403
|
}
|
|
402
404
|
|
|
403
405
|
// get the storefront or network logging in for
|
|
@@ -413,6 +415,243 @@ class CustomerController extends Controller
|
|
|
413
415
|
return response()->json(['status' => 'OK']);
|
|
414
416
|
}
|
|
415
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Handles user authentication via Apple Sign-In.
|
|
420
|
+
*
|
|
421
|
+
* This method validates the Apple ID token, checks if the user exists in the system,
|
|
422
|
+
* and creates a new user if necessary. It then ensures a contact record exists for
|
|
423
|
+
* the user and generates an authentication token.
|
|
424
|
+
*
|
|
425
|
+
* @param Request $request
|
|
426
|
+
* The HTTP request containing the following required fields:
|
|
427
|
+
* - `identityToken` (string): The token generated by Apple to identify the user.
|
|
428
|
+
* - `authorizationCode` (string): The one-time code issued by Apple during login.
|
|
429
|
+
* - `email` (string|null): The user's email address (provided on first login).
|
|
430
|
+
* - `phone` (string|null): The user's phone number (optional).
|
|
431
|
+
* - `name` (string|null): The user's full name (optional).
|
|
432
|
+
* - `appleUserId` (string): A unique identifier for the user assigned by Apple.
|
|
433
|
+
*
|
|
434
|
+
* @return \Illuminate\Http\JsonResponse
|
|
435
|
+
* A JSON response containing the authenticated customer's details, including an access token
|
|
436
|
+
*
|
|
437
|
+
* @throws \Exception
|
|
438
|
+
* If Apple authentication fails or any other error occurs during the process
|
|
439
|
+
*/
|
|
440
|
+
public function loginWithApple(Request $request)
|
|
441
|
+
{
|
|
442
|
+
$identityToken = $request->input('identityToken');
|
|
443
|
+
$authorizationCode = $request->input('authorizationCode');
|
|
444
|
+
$email = $request->input('email');
|
|
445
|
+
$phone = $request->input('phone');
|
|
446
|
+
$name = $request->input('name');
|
|
447
|
+
$appleUserId = $request->input('appleUserId');
|
|
448
|
+
|
|
449
|
+
if (!$identityToken || !$authorizationCode) {
|
|
450
|
+
return response()->apiError('Missing required Apple authentication parameters.', 400);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
// Verify the Apple token using the utility function
|
|
455
|
+
$isValid = AppleVerifier::verifyAppleJwt($identityToken);
|
|
456
|
+
if (!$isValid) {
|
|
457
|
+
return response()->apiError('Apple ID authentication is not valid.', 400);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check if the user exists in the system
|
|
461
|
+
$user = User::where(function ($query) use ($email, $appleUserId) {
|
|
462
|
+
if ($email) {
|
|
463
|
+
$query->where('email', $email);
|
|
464
|
+
$query->orWhere('apple_user_id', $appleUserId);
|
|
465
|
+
} else {
|
|
466
|
+
$query->where('apple_user_id', $appleUserId);
|
|
467
|
+
}
|
|
468
|
+
})->first();
|
|
469
|
+
|
|
470
|
+
if (!$user) {
|
|
471
|
+
// Create a new user
|
|
472
|
+
$user = User::create([
|
|
473
|
+
'email' => $email,
|
|
474
|
+
'phone' => $phone,
|
|
475
|
+
'name' => $name,
|
|
476
|
+
'apple_user_id' => $appleUserId,
|
|
477
|
+
'type' => 'customer',
|
|
478
|
+
'company_uuid' => session('company'),
|
|
479
|
+
]);
|
|
480
|
+
} else {
|
|
481
|
+
// Update the `apple_user_id` if it's not already set
|
|
482
|
+
if (!$user->apple_user_id) {
|
|
483
|
+
$user->apple_user_id = $appleUserId;
|
|
484
|
+
$user->save();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Ensure a customer contact exists
|
|
489
|
+
$contact = Contact::firstOrCreate(
|
|
490
|
+
['user_uuid' => $user->uuid, 'company_uuid' => session('company')],
|
|
491
|
+
['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'meta' => ['apple_user_id' => $appleUserId], 'type' => 'customer']
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
// Generate an auth token
|
|
495
|
+
$token = $user->createToken($contact->uuid);
|
|
496
|
+
$contact->token = $token->plainTextToken;
|
|
497
|
+
|
|
498
|
+
return new Customer($contact);
|
|
499
|
+
} catch (\Exception $e) {
|
|
500
|
+
return response()->apiError($e->getMessage(), 500);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Handles user authentication via Facebook Sign-In.
|
|
506
|
+
*
|
|
507
|
+
* This method checks if the user exists in the system based on their email or Facebook ID.
|
|
508
|
+
* If the user does not exist, it creates a new user and ensures a contact record is created.
|
|
509
|
+
* Finally, it generates an authentication token for the user.
|
|
510
|
+
*
|
|
511
|
+
* @param Request $request
|
|
512
|
+
* The HTTP request containing the following required fields:
|
|
513
|
+
* - `email` (string|null): The user's email address.
|
|
514
|
+
* - `name` (string|null): The user's full name.
|
|
515
|
+
* - `facebookUserId` (string): A unique identifier for the user assigned by Facebook.
|
|
516
|
+
*
|
|
517
|
+
* @return \Illuminate\Http\JsonResponse
|
|
518
|
+
* A JSON response containing the authenticated customer's details, including an access token
|
|
519
|
+
*
|
|
520
|
+
* @throws \Exception
|
|
521
|
+
* If Facebook authentication fails or any other error occurs during the process
|
|
522
|
+
*/
|
|
523
|
+
public function loginWithFacebook(Request $request)
|
|
524
|
+
{
|
|
525
|
+
$email = $request->input('email');
|
|
526
|
+
$name = $request->input('name');
|
|
527
|
+
$facebookUserId = $request->input('facebookUserId');
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
// Check if the user exists in the system
|
|
531
|
+
$user = User::where(function ($query) use ($email, $facebookUserId) {
|
|
532
|
+
if ($email) {
|
|
533
|
+
$query->where('email', $email);
|
|
534
|
+
$query->orWhere('facebook_user_id', $facebookUserId);
|
|
535
|
+
} else {
|
|
536
|
+
$query->where('facebook_user_id', $facebookUserId);
|
|
537
|
+
}
|
|
538
|
+
})->first();
|
|
539
|
+
|
|
540
|
+
if (!$user) {
|
|
541
|
+
// Create a new user
|
|
542
|
+
$user = User::create([
|
|
543
|
+
'email' => $email,
|
|
544
|
+
'name' => $name,
|
|
545
|
+
'facebook_user_id' => $facebookUserId,
|
|
546
|
+
'type' => 'customer',
|
|
547
|
+
'company_uuid' => session('company'),
|
|
548
|
+
]);
|
|
549
|
+
} else {
|
|
550
|
+
// Update the `facebook_user_id` if it's not already set
|
|
551
|
+
if (!$user->facebook_user_id) {
|
|
552
|
+
$user->facebook_user_id = $facebookUserId;
|
|
553
|
+
$user->save();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Ensure a customer contact exists
|
|
558
|
+
$contact = Contact::firstOrCreate(
|
|
559
|
+
['user_uuid' => $user->uuid, 'company_uuid' => session('company')],
|
|
560
|
+
['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'meta' => ['facebook_user_id' => $facebookUserId], 'type' => 'customer']
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
// Generate an auth token
|
|
564
|
+
$token = $user->createToken($contact->uuid);
|
|
565
|
+
$contact->token = $token->plainTextToken;
|
|
566
|
+
|
|
567
|
+
return new Customer($contact);
|
|
568
|
+
} catch (\Exception $e) {
|
|
569
|
+
return response()->apiError($e->getMessage(), 500);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Handles user authentication via Google Sign-In.
|
|
575
|
+
*
|
|
576
|
+
* This method validates the Google ID token, retrieves user details from the token payload,
|
|
577
|
+
* checks if the user exists in the system, and creates a new user if necessary.
|
|
578
|
+
* It ensures a contact record exists for the user and generates an authentication token.
|
|
579
|
+
*
|
|
580
|
+
* @param Request $request
|
|
581
|
+
* The HTTP request containing the following required fields:
|
|
582
|
+
* - `idToken` (string): The token generated by Google to identify the user.
|
|
583
|
+
* - `clientId` (string): The client ID associated with the app.
|
|
584
|
+
*
|
|
585
|
+
* @return \Illuminate\Http\JsonResponse
|
|
586
|
+
* A JSON response containing the authenticated customer's details, including an access token
|
|
587
|
+
*
|
|
588
|
+
* @throws \Exception
|
|
589
|
+
* If Google authentication fails or any other error occurs during the process
|
|
590
|
+
*/
|
|
591
|
+
public function loginWithGoogle(Request $request)
|
|
592
|
+
{
|
|
593
|
+
$idToken = $request->input('idToken');
|
|
594
|
+
$clientId = $request->input('clientId');
|
|
595
|
+
if (!$idToken || !$clientId) {
|
|
596
|
+
return response()->apiError('Missing required Google authentication parameters.', 400);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
// Verify the Google ID token using the utility function
|
|
601
|
+
$payload = GoogleVerifier::verifyIdToken($idToken, $clientId);
|
|
602
|
+
if (!$payload) {
|
|
603
|
+
return response()->apiError('Google Sign-In authentication is not valid.', 400);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Extract user details from the payload
|
|
607
|
+
$email = data_get($payload, 'email');
|
|
608
|
+
$name = data_get($payload, 'name');
|
|
609
|
+
$googleUserId = data_get($payload, 'sub');
|
|
610
|
+
$avatarUrl = data_get($payload, 'picture');
|
|
611
|
+
|
|
612
|
+
// Check if the user exists in the system
|
|
613
|
+
$user = User::where(function ($query) use ($email, $googleUserId) {
|
|
614
|
+
if ($email) {
|
|
615
|
+
$query->where('email', $email);
|
|
616
|
+
$query->orWhere('google_user_id', $googleUserId);
|
|
617
|
+
} else {
|
|
618
|
+
$query->where('google_user_id', $googleUserId);
|
|
619
|
+
}
|
|
620
|
+
})->first();
|
|
621
|
+
|
|
622
|
+
if (!$user) {
|
|
623
|
+
// Create a new user
|
|
624
|
+
$user = User::create([
|
|
625
|
+
'email' => $email,
|
|
626
|
+
'name' => $name,
|
|
627
|
+
'google_user_id' => $googleUserId,
|
|
628
|
+
'type' => 'customer',
|
|
629
|
+
'company_uuid' => session('company'),
|
|
630
|
+
]);
|
|
631
|
+
} else {
|
|
632
|
+
// Update the `google_user_id` if it's not already set
|
|
633
|
+
if (!$user->google_user_id) {
|
|
634
|
+
$user->google_user_id = $googleUserId;
|
|
635
|
+
$user->save();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Ensure a customer contact exists
|
|
640
|
+
$contact = Contact::firstOrCreate(
|
|
641
|
+
['user_uuid' => $user->uuid, 'company_uuid' => session('company')],
|
|
642
|
+
['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'meta' => ['google_user_id' => $googleUserId], 'type' => 'customer']
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
// Generate an auth token
|
|
646
|
+
$token = $user->createToken($contact->uuid);
|
|
647
|
+
$contact->token = $token->plainTextToken;
|
|
648
|
+
|
|
649
|
+
return new Customer($contact);
|
|
650
|
+
} catch (\Exception $e) {
|
|
651
|
+
return response()->apiError($e->getMessage(), 500);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
416
655
|
/**
|
|
417
656
|
* Verifys SMS code and sends auth token with customer resource.
|
|
418
657
|
*
|
|
@@ -433,13 +672,13 @@ class CustomerController extends Controller
|
|
|
433
672
|
$user = User::where('phone', $identity)->orWhere('email', $identity)->first();
|
|
434
673
|
|
|
435
674
|
if (!$user) {
|
|
436
|
-
return response()->
|
|
675
|
+
return response()->apiError('Unable to verify code.');
|
|
437
676
|
}
|
|
438
677
|
|
|
439
678
|
// find and verify code
|
|
440
679
|
$verificationCode = VerificationCode::where(['subject_uuid' => $user->uuid, 'code' => $code, 'for' => $for])->exists();
|
|
441
680
|
if (!$verificationCode && $code !== config('storefront.storefront_app.bypass_verification_code')) {
|
|
442
|
-
return response()->
|
|
681
|
+
return response()->apiError('Invalid verification code!');
|
|
443
682
|
}
|
|
444
683
|
|
|
445
684
|
// get the storefront or network logging in for
|
|
@@ -466,7 +705,7 @@ class CustomerController extends Controller
|
|
|
466
705
|
try {
|
|
467
706
|
$token = $user->createToken($contact->uuid);
|
|
468
707
|
} catch (\Exception $e) {
|
|
469
|
-
return response()->
|
|
708
|
+
return response()->apiError($e->getMessage());
|
|
470
709
|
}
|
|
471
710
|
|
|
472
711
|
$contact->token = $token->plainTextToken;
|
|
@@ -494,7 +733,7 @@ class CustomerController extends Controller
|
|
|
494
733
|
{
|
|
495
734
|
$customer = Storefront::getCustomerFromToken();
|
|
496
735
|
if (!$customer) {
|
|
497
|
-
return response()->
|
|
736
|
+
return response()->apiError('Not authorized to view customers places');
|
|
498
737
|
}
|
|
499
738
|
|
|
500
739
|
$gateway = Storefront::findGateway('stripe');
|
|
@@ -529,7 +768,7 @@ class CustomerController extends Controller
|
|
|
529
768
|
{
|
|
530
769
|
$customer = Storefront::getCustomerFromToken();
|
|
531
770
|
if (!$customer) {
|
|
532
|
-
return response()->
|
|
771
|
+
return response()->apiError('Not authorized to view customers places');
|
|
533
772
|
}
|
|
534
773
|
|
|
535
774
|
$gateway = Storefront::findGateway('stripe');
|
|
@@ -5,7 +5,7 @@ namespace Fleetbase\Storefront\Http\Requests;
|
|
|
5
5
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
6
6
|
use Fleetbase\Storefront\Rules\CustomerExists;
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class StorefrontCustomerRequest extends FleetbaseRequest
|
|
9
9
|
{
|
|
10
10
|
/**
|
|
11
11
|
* Determine if the user is authorized to make this request.
|
|
@@ -6,6 +6,7 @@ use Fleetbase\Http\Resources\FleetbaseResource;
|
|
|
6
6
|
use Fleetbase\Support\Http;
|
|
7
7
|
use Illuminate\Support\Arr;
|
|
8
8
|
use Illuminate\Support\Str;
|
|
9
|
+
use Illuminate\Support\Collection;
|
|
9
10
|
|
|
10
11
|
class Product extends FleetbaseResource
|
|
11
12
|
{
|
|
@@ -55,7 +56,7 @@ class Product extends FleetbaseResource
|
|
|
55
56
|
];
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
public function mapHours(
|
|
59
|
+
public function mapHours(Collection|array $hours = []): array
|
|
59
60
|
{
|
|
60
61
|
if (empty($hours)) {
|
|
61
62
|
return [];
|
|
@@ -79,7 +80,7 @@ class Product extends FleetbaseResource
|
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
public function mapFiles(
|
|
83
|
+
public function mapFiles(Collection|array $files = [], $contentType = 'image')
|
|
83
84
|
{
|
|
84
85
|
return collect($files)->map(function ($file) use ($contentType) {
|
|
85
86
|
if (!Str::contains($file->content_type, $contentType)) {
|
|
@@ -90,7 +91,7 @@ class Product extends FleetbaseResource
|
|
|
90
91
|
})->filter()->values();
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
public function mapAddonCategories(
|
|
94
|
+
public function mapAddonCategories(Collection|array $addonCategories = [])
|
|
94
95
|
{
|
|
95
96
|
return collect($addonCategories)->map(function ($addonCategory) {
|
|
96
97
|
$addons = data_get($addonCategory, 'category.addons', []);
|
|
@@ -122,7 +123,7 @@ class Product extends FleetbaseResource
|
|
|
122
123
|
});
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
public function mapProductAddons(
|
|
126
|
+
public function mapProductAddons(Collection|array $addons = [], $excluded = [])
|
|
126
127
|
{
|
|
127
128
|
return collect($addons)->map(function ($addon) use ($excluded) {
|
|
128
129
|
if (is_array($excluded) && in_array($addon->uuid, $excluded)) {
|
|
@@ -159,7 +160,7 @@ class Product extends FleetbaseResource
|
|
|
159
160
|
})->filter()->values();
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
public function mapVariants(
|
|
163
|
+
public function mapVariants(Collection|array $variants = [])
|
|
163
164
|
{
|
|
164
165
|
return collect($variants)->map(function ($variant) {
|
|
165
166
|
$productVariantArr = [
|
|
@@ -2,15 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\Storefront\Observers;
|
|
4
4
|
|
|
5
|
-
use Fleetbase\FleetOps\Support\Utils;
|
|
6
5
|
use Fleetbase\Models\File;
|
|
7
6
|
use Fleetbase\Storefront\Models\Product;
|
|
8
|
-
use Fleetbase\Storefront\Models\ProductAddonCategory;
|
|
9
|
-
use Fleetbase\Storefront\Models\ProductVariant;
|
|
10
|
-
use Fleetbase\Storefront\Models\ProductVariantOption;
|
|
11
|
-
use Illuminate\Support\Arr;
|
|
12
7
|
use Illuminate\Support\Facades\Request;
|
|
13
|
-
use Illuminate\Support\Str;
|
|
14
8
|
|
|
15
9
|
class ProductObserver
|
|
16
10
|
{
|
|
@@ -47,56 +41,18 @@ class ProductObserver
|
|
|
47
41
|
{
|
|
48
42
|
$addonCategories = Request::input('product.addon_categories', []);
|
|
49
43
|
$variants = Request::input('product.variants', []);
|
|
44
|
+
$files = Request::input('product.files', []);
|
|
50
45
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
if (isset($productAddonCategory['uuid']) && Str::isUuid($productAddonCategory['uuid'])) {
|
|
54
|
-
ProductAddonCategory::where('uuid', $productAddonCategory['uuid'])->update(Arr::except($productAddonCategory, ['uuid', 'name', 'category']));
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// add new addon category
|
|
59
|
-
$productAddonCategory['product_uuid'] = $product->uuid;
|
|
60
|
-
ProductAddonCategory::create(Arr::except($productAddonCategory, ['category']));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// update product variants
|
|
64
|
-
foreach ($variants as $variant) {
|
|
65
|
-
if (isset($variant['uuid']) && Str::isUuid($variant['uuid'])) {
|
|
66
|
-
// update product variante
|
|
67
|
-
ProductVariant::where('uuid', $variant['uuid'])->update(Arr::except($variant, ['uuid', 'options']));
|
|
68
|
-
|
|
69
|
-
// update product variant options
|
|
70
|
-
foreach ($variant['options'] as $option) {
|
|
71
|
-
if (!empty($option['uuid'])) {
|
|
72
|
-
// make sure additional cost is always numbers only
|
|
73
|
-
if (isset($option['additional_cost'])) {
|
|
74
|
-
$option['additional_cost'] = Utils::numbersOnly($option['additional_cost']);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
$updateAttrs = Arr::except($option, ['uuid']);
|
|
78
|
-
|
|
79
|
-
ProductVariantOption::where('uuid', $option['uuid'])->update($updateAttrs);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
$option['product_variant_uuid'] = $variant['uuid'];
|
|
84
|
-
ProductVariantOption::create($option);
|
|
85
|
-
}
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// create new variant
|
|
90
|
-
$variant['created_by_uuid'] = Request::session()->get('user');
|
|
91
|
-
$variant['company_uuid'] = Request::session()->get('company');
|
|
92
|
-
$variant['product_uuid'] = $product->uuid;
|
|
46
|
+
// save addon categories
|
|
47
|
+
$product->setAddonCategories($addonCategories);
|
|
93
48
|
|
|
94
|
-
|
|
49
|
+
// save product variants
|
|
50
|
+
$product->setProductVariants($variants);
|
|
95
51
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
52
|
+
// set keys on files
|
|
53
|
+
foreach ($files as $file) {
|
|
54
|
+
$fileRecord = File::where('uuid', $file['uuid'])->first();
|
|
55
|
+
$fileRecord->setKey($product);
|
|
100
56
|
}
|
|
101
57
|
}
|
|
102
58
|
}
|
package/server/src/routes.php
CHANGED
|
@@ -20,7 +20,7 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
|
|
|
20
20
|
| Public/Callback Receivable Storefront API Routes
|
|
21
21
|
|--------------------------------------------------------------------------
|
|
22
22
|
|
|
|
23
|
-
| End-user API routes, these are routes that the SDK and applications will interface with, and require API credentials.
|
|
23
|
+
| End-user API routes, these are routes that the SDK and applications will interface with, and DO NOT require API credentials.
|
|
24
24
|
*/
|
|
25
25
|
Route::group(['prefix' => 'v1', 'namespace' => 'v1'], function ($router) {
|
|
26
26
|
// storefront/v1/checkouts
|
|
@@ -94,6 +94,9 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
|
|
|
94
94
|
$router->get('{id}', 'CustomerController@find');
|
|
95
95
|
$router->post('/', 'CustomerController@create');
|
|
96
96
|
$router->post('login-with-sms', 'CustomerController@loginWithPhone');
|
|
97
|
+
$router->post('login-with-apple', 'CustomerController@loginWithApple');
|
|
98
|
+
$router->post('login-with-facebook', 'CustomerController@loginWithFacebook');
|
|
99
|
+
$router->post('login-with-google', 'CustomerController@loginWithGoogle');
|
|
97
100
|
$router->post('verify-code', 'CustomerController@verifyCode');
|
|
98
101
|
$router->post('login', 'CustomerController@login');
|
|
99
102
|
$router->post('request-creation-code', 'CustomerController@requestCustomerCreationCode');
|