@fleetbase/storefront-engine 0.3.26 → 0.3.28
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/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/src/Http/Controllers/v1/CheckoutController.php +148 -62
- package/server/src/Http/Controllers/v1/CustomerController.php +97 -2
- package/server/src/Http/Controllers/v1/FoodTruckController.php +19 -0
- package/server/src/Http/Resources/FoodTruck.php +18 -17
- package/server/src/routes.php +2 -0
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ use Fleetbase\Storefront\Http\Requests\InitializeCheckoutRequest;
|
|
|
19
19
|
use Fleetbase\Storefront\Models\Cart;
|
|
20
20
|
use Fleetbase\Storefront\Models\Checkout;
|
|
21
21
|
use Fleetbase\Storefront\Models\Customer;
|
|
22
|
+
use Fleetbase\Storefront\Models\FoodTruck;
|
|
22
23
|
use Fleetbase\Storefront\Models\Gateway;
|
|
23
24
|
use Fleetbase\Storefront\Models\Product;
|
|
24
25
|
use Fleetbase\Storefront\Models\Store;
|
|
@@ -475,8 +476,8 @@ class CheckoutController extends Controller
|
|
|
475
476
|
$invoiceAmount = $amount;
|
|
476
477
|
$invoiceCode = $gateway->sandbox ? 'TEST_INVOICE' : $gateway->config->invoice_id;
|
|
477
478
|
$invoiceDescription = $about->name . ' cart checkout';
|
|
478
|
-
$invoiceReceiverCode = $
|
|
479
|
-
$senderInvoiceNo = $
|
|
479
|
+
$invoiceReceiverCode = $checkout->public_id;
|
|
480
|
+
$senderInvoiceNo = $checkout->public_id;
|
|
480
481
|
|
|
481
482
|
// Create qpay invoice
|
|
482
483
|
$invoice = $qpay->createSimpleInvoice($invoiceAmount, $invoiceCode, $invoiceDescription, $invoiceReceiverCode, $senderInvoiceNo, $callbackUrl);
|
|
@@ -491,69 +492,137 @@ class CheckoutController extends Controller
|
|
|
491
492
|
]);
|
|
492
493
|
}
|
|
493
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Capture and process QPay callback.
|
|
497
|
+
*
|
|
498
|
+
* This controller method handles QPay callback requests by verifying and processing
|
|
499
|
+
* payment information for a specified checkout. It performs the following steps:
|
|
500
|
+
*
|
|
501
|
+
* - Retrieves the checkout identifier from the request.
|
|
502
|
+
* - Looks up the associated Checkout and Gateway records.
|
|
503
|
+
* - If in sandbox mode and a test scenario is provided, it simulates a test payment
|
|
504
|
+
* response for either success or error scenarios.
|
|
505
|
+
* - Initializes a QPay instance with the gateway configuration and sets the authentication token.
|
|
506
|
+
* - Retrieves the invoice ID from the checkout options and performs a payment check using QPay's API.
|
|
507
|
+
* - Publishes the payment data or error response to the SocketCluster channel.
|
|
508
|
+
*
|
|
509
|
+
* Depending on the 'respond' flag from the request, the method returns a JSON response
|
|
510
|
+
* or completes the processing without returning data.
|
|
511
|
+
*
|
|
512
|
+
* @param Request $request The HTTP request containing:
|
|
513
|
+
* - `checkout` (string): The public checkout identifier.
|
|
514
|
+
* - `respond` (boolean): Whether to return a JSON response.
|
|
515
|
+
* - `test` (string|null): A test scenario indicator ('success' or 'error') for sandbox mode.
|
|
516
|
+
*
|
|
517
|
+
* @return \Illuminate\Http\JsonResponse a JSON response with payment data or error details
|
|
518
|
+
*
|
|
519
|
+
* @throws \Exception if an error occurs during payment processing, an API error is returned
|
|
520
|
+
*/
|
|
494
521
|
public function captureQPayCallback(Request $request)
|
|
495
522
|
{
|
|
496
|
-
$checkoutId
|
|
497
|
-
$
|
|
498
|
-
$
|
|
499
|
-
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
$data = ['payment' =>(array) $payment, 'checkout' => $checkout->public_id, 'error' => null];
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
SocketClusterService::publish('checkout.' . $checkout->public_id, $data);
|
|
546
|
-
if ($respond) {
|
|
547
|
-
return response()->json($data);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
} catch (\Exception $e) {
|
|
551
|
-
Log::error('[QPAY CHECKOUT ERROR: ' . $e->getMessage() . ']', $checkout->toArray());
|
|
552
|
-
if ($respond) {
|
|
553
|
-
return response()->apiError($e->getMessage());
|
|
554
|
-
}
|
|
555
|
-
}
|
|
523
|
+
$checkoutId = $request->input('checkout');
|
|
524
|
+
$shouldRespond = $request->boolean('respond');
|
|
525
|
+
$testScenario = $request->input('test'); // Expected: 'success' or 'error'
|
|
526
|
+
|
|
527
|
+
if (!$checkoutId) {
|
|
528
|
+
return response()->json([
|
|
529
|
+
'error' => 'CHECKOUT_ID_MISSING',
|
|
530
|
+
'checkout' => null,
|
|
531
|
+
'payment' => null,
|
|
532
|
+
]);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
$checkout = Checkout::where('public_id', $checkoutId)->first();
|
|
536
|
+
if (!$checkout) {
|
|
537
|
+
return response()->json([
|
|
538
|
+
'error' => 'CHECKOUT_SESSION_NOT_FOUND',
|
|
539
|
+
'checkout' => null,
|
|
540
|
+
'payment' => null,
|
|
541
|
+
]);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
$gateway = Gateway::where('uuid', $checkout->gateway_uuid)->first();
|
|
545
|
+
if (!$gateway) {
|
|
546
|
+
return response()->json([
|
|
547
|
+
'error' => 'GATEWAY_NOT_CONFIGURED',
|
|
548
|
+
'checkout' => $checkout->public_id,
|
|
549
|
+
'payment' => null,
|
|
550
|
+
]);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
// Handle test scenarios if in sandbox mode.
|
|
555
|
+
if ($gateway->sandbox && in_array($testScenario, ['success', 'error'], true)) {
|
|
556
|
+
$data = [
|
|
557
|
+
'checkout' => $checkout->public_id,
|
|
558
|
+
'payment' => null,
|
|
559
|
+
'error' => null,
|
|
560
|
+
];
|
|
561
|
+
|
|
562
|
+
if ($testScenario === 'success') {
|
|
563
|
+
$data['payment'] = QPay::createTestPaymentDataFromCheckout($checkout);
|
|
564
|
+
} else {
|
|
565
|
+
$data['error'] = [
|
|
566
|
+
'error' => 'PAYMENT_NOT_PAID',
|
|
567
|
+
'message' => 'Payment has not been paid!',
|
|
568
|
+
];
|
|
556
569
|
}
|
|
570
|
+
|
|
571
|
+
SocketClusterService::publish('checkout.' . $checkout->public_id, $data);
|
|
572
|
+
|
|
573
|
+
return $shouldRespond ? response()->json($data) : response()->json();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Create the QPay instance.
|
|
577
|
+
$qpay = QPay::instance(
|
|
578
|
+
$gateway->config->username,
|
|
579
|
+
$gateway->config->password,
|
|
580
|
+
$gateway->callback_url
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
if ($gateway->sandbox) {
|
|
584
|
+
$qpay->useSandbox();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
$qpay->setAuthToken();
|
|
588
|
+
|
|
589
|
+
$invoiceId = $checkout->getOption('qpay_invoice_id');
|
|
590
|
+
if (!$invoiceId) {
|
|
591
|
+
Log::error("Missing QPay invoice ID for checkout: {$checkout->public_id}");
|
|
592
|
+
|
|
593
|
+
return response()->json([
|
|
594
|
+
'error' => 'MISSING_INVOICE_ID',
|
|
595
|
+
'checkout' => $checkout->public_id,
|
|
596
|
+
'payment' => null,
|
|
597
|
+
]);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
$paymentCheck = $qpay->paymentCheck($invoiceId);
|
|
601
|
+
|
|
602
|
+
if (!$paymentCheck || empty($paymentCheck->count) || $paymentCheck->count < 1) {
|
|
603
|
+
return response()->json([
|
|
604
|
+
'error' => 'PAYMENT_NOTFOUND',
|
|
605
|
+
'checkout' => $checkout->public_id,
|
|
606
|
+
'payment' => null,
|
|
607
|
+
]);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
$payment = data_get($paymentCheck, 'rows.0');
|
|
611
|
+
if ($payment) {
|
|
612
|
+
$data = [
|
|
613
|
+
'checkout' => $checkout->public_id,
|
|
614
|
+
'payment' => (array) $payment,
|
|
615
|
+
'error' => null,
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
SocketClusterService::publish('checkout.' . $checkout->public_id, $data);
|
|
619
|
+
|
|
620
|
+
return $shouldRespond ? response()->json($data) : response()->json();
|
|
621
|
+
}
|
|
622
|
+
} catch (\Exception $e) {
|
|
623
|
+
Log::error('[QPAY CHECKOUT ERROR]: ' . $e->getMessage(), ['checkout' => $checkout->toArray()]);
|
|
624
|
+
if ($shouldRespond) {
|
|
625
|
+
return response()->apiError($e->getMessage());
|
|
557
626
|
}
|
|
558
627
|
}
|
|
559
628
|
|
|
@@ -738,6 +807,18 @@ class CheckoutController extends Controller
|
|
|
738
807
|
$origin = Arr::first($origin);
|
|
739
808
|
}
|
|
740
809
|
|
|
810
|
+
// Check if the order origin is from a food truck via cart property
|
|
811
|
+
$foodTruck = collect($cart->items)
|
|
812
|
+
->map(function ($cartItem) {
|
|
813
|
+
return data_get($cartItem, 'food_truck_id');
|
|
814
|
+
})
|
|
815
|
+
->unique()
|
|
816
|
+
->filter()
|
|
817
|
+
->map(function ($foodTruckId) {
|
|
818
|
+
return FoodTruck::where('public_id', $foodTruckId)->first();
|
|
819
|
+
})
|
|
820
|
+
->first();
|
|
821
|
+
|
|
741
822
|
// if there is no origin attempt to get from cart
|
|
742
823
|
if (!$origin) {
|
|
743
824
|
$storeLocation = collect($cart->items)->map(function ($cartItem) {
|
|
@@ -818,6 +899,11 @@ class CheckoutController extends Controller
|
|
|
818
899
|
...$transactionDetails,
|
|
819
900
|
]);
|
|
820
901
|
|
|
902
|
+
// if there is a food truck include it in the order meta
|
|
903
|
+
if ($foodTruck) {
|
|
904
|
+
$orderMeta['food_truck_id'] = $foodTruck->public_id;
|
|
905
|
+
}
|
|
906
|
+
|
|
821
907
|
// initialize order creation input
|
|
822
908
|
$orderInput = [
|
|
823
909
|
'company_uuid' => $store->company_uuid ?? session('company'),
|
|
@@ -176,8 +176,8 @@ class CustomerController extends Controller
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// verify code
|
|
179
|
-
$
|
|
180
|
-
if (!$
|
|
179
|
+
$verificationCode = VerificationCode::where(['code' => $code, 'for' => 'storefront_create_customer', 'meta->identity' => $identity])->exists();
|
|
180
|
+
if (!$verificationCode) {
|
|
181
181
|
return response()->apiError('Invalid verification code provided!');
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -802,4 +802,99 @@ class CustomerController extends Controller
|
|
|
802
802
|
return response()->apiError($e->getMessage());
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
|
+
|
|
806
|
+
public function startAccountClosure(Request $request)
|
|
807
|
+
{
|
|
808
|
+
$about = Storefront::about(['company_uuid']);
|
|
809
|
+
if (!$about) {
|
|
810
|
+
return response()->apiError('Storefront not found.');
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
$customer = Storefront::getCustomerFromToken();
|
|
814
|
+
if (!$customer) {
|
|
815
|
+
return response()->apiError('Not authorized to view customers places');
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Get the user account for the contact/customer
|
|
819
|
+
$user = User::where(['uuid' => $customer->user_uuid])->first();
|
|
820
|
+
if (!$user) {
|
|
821
|
+
return response()->apiError('Customer user account not found.');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Check for phone or email
|
|
825
|
+
if (!$user->phone && !$user->email) {
|
|
826
|
+
return response()->apiError('Customer account must have a valid email or phone number linked.');
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Send account closure confirmation with code
|
|
830
|
+
try {
|
|
831
|
+
if ($user->phone) {
|
|
832
|
+
VerificationCode::generateSmsVerificationFor($user, 'storefront_account_closure', [
|
|
833
|
+
'messageCallback' => function ($verification) use ($about) {
|
|
834
|
+
return "Your {$about->name} account closure verification code is {$verification->code}";
|
|
835
|
+
},
|
|
836
|
+
'meta' => ['identity' => $user->phone],
|
|
837
|
+
]);
|
|
838
|
+
} elseif ($user->email) {
|
|
839
|
+
VerificationCode::generateEmailVerificationFor($user, 'storefront_account_closure', [
|
|
840
|
+
'subject' => $about->name . ' account closure request',
|
|
841
|
+
'messageCallback' => function ($verification) use ($about) {
|
|
842
|
+
return "Your {$about->name} account closure verification code is {$verification->code}";
|
|
843
|
+
},
|
|
844
|
+
'meta' => ['identity' => $user->email],
|
|
845
|
+
]);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return response()->json(['status' => 'OK']);
|
|
849
|
+
} catch (\Exception $e) {
|
|
850
|
+
return response()->apiError($e->getMessage());
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return response()->apiError('An uknown error occured attempting to close customer account.');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
public function confirmAccountClosure(Request $request)
|
|
857
|
+
{
|
|
858
|
+
$code = $request->input('code');
|
|
859
|
+
$about = Storefront::about(['company_uuid']);
|
|
860
|
+
if (!$about) {
|
|
861
|
+
return response()->apiError('Storefront not found.');
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
$customer = Storefront::getCustomerFromToken();
|
|
865
|
+
if (!$customer) {
|
|
866
|
+
return response()->apiError('Not authorized to view customers places');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Get the user account for the contact/customer
|
|
870
|
+
$user = User::where(['uuid' => $customer->user_uuid])->first();
|
|
871
|
+
if (!$user) {
|
|
872
|
+
return response()->apiError('Customer user account not found.');
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Get verification identity
|
|
876
|
+
$identity = $user->phone ?? $user->email;
|
|
877
|
+
|
|
878
|
+
// verify account closure code
|
|
879
|
+
$verificationCode = VerificationCode::where(['code' => $code, 'for' => 'storefront_account_closure', 'meta->identity' => $identity])->exists();
|
|
880
|
+
if (!$verificationCode && $code !== config('storefront.storefront_app.bypass_verification_code')) {
|
|
881
|
+
return response()->apiError('Invalid verification code provided!');
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
try {
|
|
885
|
+
// If the user type is `contact` or `customer` delete the user account
|
|
886
|
+
if ($user->isType(['contact', 'customer'])) {
|
|
887
|
+
$user->delete();
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Delete the customer
|
|
891
|
+
$customer->delete();
|
|
892
|
+
|
|
893
|
+
return response()->json(['status' => 'OK']);
|
|
894
|
+
} catch (\Exception $e) {
|
|
895
|
+
return response()->apiError($e->getMessage());
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return response()->apiError('An uknown error occured attempting to close customer account.');
|
|
899
|
+
}
|
|
805
900
|
}
|
|
@@ -5,6 +5,7 @@ namespace Fleetbase\Storefront\Http\Controllers\v1;
|
|
|
5
5
|
use Fleetbase\Http\Controllers\Controller;
|
|
6
6
|
use Fleetbase\Storefront\Http\Resources\FoodTruck as FoodTruckResource;
|
|
7
7
|
use Fleetbase\Storefront\Models\FoodTruck;
|
|
8
|
+
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
8
9
|
use Illuminate\Http\Request;
|
|
9
10
|
|
|
10
11
|
class FoodTruckController extends Controller
|
|
@@ -36,4 +37,22 @@ class FoodTruckController extends Controller
|
|
|
36
37
|
|
|
37
38
|
return FoodTruckResource::collection($results);
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Finds a single Storefront FoodTruck resources.
|
|
43
|
+
*
|
|
44
|
+
* @return \Fleetbase\Http\Resources\EntityCollection
|
|
45
|
+
*/
|
|
46
|
+
public function find($id)
|
|
47
|
+
{
|
|
48
|
+
// find for the food truck
|
|
49
|
+
try {
|
|
50
|
+
$foodTruck = FoodTruck::findRecordOrFail($id);
|
|
51
|
+
} catch (ModelNotFoundException $exception) {
|
|
52
|
+
return response()->error('Food Truck resource not found.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// response the product resource
|
|
56
|
+
return new FoodTruckResource($foodTruck);
|
|
57
|
+
}
|
|
39
58
|
}
|
|
@@ -20,23 +20,24 @@ class FoodTruck extends FleetbaseResource
|
|
|
20
20
|
public function toArray($request)
|
|
21
21
|
{
|
|
22
22
|
return [
|
|
23
|
-
'id'
|
|
24
|
-
'uuid'
|
|
25
|
-
'public_id'
|
|
26
|
-
'company_uuid'
|
|
27
|
-
'created_by_uuid'
|
|
28
|
-
'store_uuid'
|
|
29
|
-
'service_area_uuid'
|
|
30
|
-
'zone_uuid'
|
|
31
|
-
'vehicle_uuid'
|
|
32
|
-
'vehicle'
|
|
33
|
-
'service_area'
|
|
34
|
-
'zone'
|
|
35
|
-
'catalogs'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
23
|
+
'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
|
|
24
|
+
'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
|
|
25
|
+
'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
|
|
26
|
+
'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
|
|
27
|
+
'created_by_uuid' => $this->when(Http::isInternalRequest(), $this->created_by_uuid),
|
|
28
|
+
'store_uuid' => $this->when(Http::isInternalRequest(), $this->store_uuid),
|
|
29
|
+
'service_area_uuid' => $this->when(Http::isInternalRequest(), $this->service_area_uuid),
|
|
30
|
+
'zone_uuid' => $this->when(Http::isInternalRequest(), $this->zone_uuid),
|
|
31
|
+
'vehicle_uuid' => $this->when(Http::isInternalRequest(), $this->vehicle_uuid),
|
|
32
|
+
'vehicle' => $this->vehicle ? new VehicleResource($this->vehicle) : null,
|
|
33
|
+
'service_area' => $this->serviceArea ? new ServiceAreaResource($this->serviceArea) : null,
|
|
34
|
+
'zone' => $this->zone ? new ZoneResource($this->zone) : null,
|
|
35
|
+
'catalogs' => Catalog::collection($this->catalogs ?? []),
|
|
36
|
+
'location' => $this->vehicle ? $this->vehicle->location : null,
|
|
37
|
+
'online' => $this->vehicle ? $this->vehicle->online : false,
|
|
38
|
+
'status' => $this->status,
|
|
39
|
+
'created_at' => $this->created_at,
|
|
40
|
+
'updated_at' => $this->updated_at,
|
|
40
41
|
];
|
|
41
42
|
}
|
|
42
43
|
}
|
package/server/src/routes.php
CHANGED
|
@@ -113,6 +113,8 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
|
|
|
113
113
|
$router->post('request-creation-code', 'CustomerController@requestCustomerCreationCode');
|
|
114
114
|
$router->post('stripe-ephemeral-key', 'CustomerController@getStripeEphemeralKey');
|
|
115
115
|
$router->post('stripe-setup-intent', 'CustomerController@getStripeSetupIntent');
|
|
116
|
+
$router->post('account-closure', 'CustomerController@startAccountClosure');
|
|
117
|
+
$router->post('confirm-account-closure', 'CustomerController@confirmAccountClosure');
|
|
116
118
|
});
|
|
117
119
|
|
|
118
120
|
// hotfix! storefront-app sending customer update to /contacts/ route
|