@fleetbase/storefront-engine 0.1.7 → 0.1.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/.php-cs-fixer.php +29 -0
- package/LICENSE.md +16 -4
- package/README.md +106 -13
- package/composer.json +88 -0
- package/extension.json +10 -0
- package/package.json +6 -6
- package/phpstan.neon.dist +8 -0
- package/phpunit.xml.dist +16 -0
- package/server/.gitattributes +14 -0
- package/server/README.md +40 -0
- package/server/config/api.php +101 -0
- package/server/config/database.connections.php +57 -0
- package/server/config/storefront.php +19 -0
- package/server/config/twilio-notification-channel.php +36 -0
- package/server/migrations/2023_05_03_025307_create_carts_table.php +44 -0
- package/server/migrations/2023_05_03_025307_create_checkouts_table.php +51 -0
- package/server/migrations/2023_05_03_025307_create_gateways_table.php +48 -0
- package/server/migrations/2023_05_03_025307_create_network_stores_table.php +36 -0
- package/server/migrations/2023_05_03_025307_create_networks_table.php +56 -0
- package/server/migrations/2023_05_03_025307_create_notification_channels_table.php +43 -0
- package/server/migrations/2023_05_03_025307_create_payment_methods_table.php +44 -0
- package/server/migrations/2023_05_03_025307_create_product_addon_categories_table.php +38 -0
- package/server/migrations/2023_05_03_025307_create_product_addons_table.php +43 -0
- package/server/migrations/2023_05_03_025307_create_product_hours_table.php +37 -0
- package/server/migrations/2023_05_03_025307_create_product_store_locations_table.php +34 -0
- package/server/migrations/2023_05_03_025307_create_product_variant_options_table.php +40 -0
- package/server/migrations/2023_05_03_025307_create_product_variants_table.php +44 -0
- package/server/migrations/2023_05_03_025307_create_products_table.php +59 -0
- package/server/migrations/2023_05_03_025307_create_reviews_table.php +41 -0
- package/server/migrations/2023_05_03_025307_create_store_hours_table.php +37 -0
- package/server/migrations/2023_05_03_025307_create_store_locations_table.php +38 -0
- package/server/migrations/2023_05_03_025307_create_stores_table.php +57 -0
- package/server/migrations/2023_05_03_025307_create_votes_table.php +39 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_carts_table.php +40 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_checkouts_table.php +48 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_gateways_table.php +40 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_network_stores_table.php +40 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_networks_table.php +42 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_notification_channels_table.php +40 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_payment_methods_table.php +40 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_addon_categories_table.php +38 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_addons_table.php +38 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_hours_table.php +32 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_store_locations_table.php +34 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_variant_options_table.php +32 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_variants_table.php +32 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_products_table.php +44 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_reviews_table.php +38 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_store_hours_table.php +32 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_store_locations_table.php +40 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_stores_table.php +42 -0
- package/server/migrations/2023_05_03_025310_add_foreign_keys_to_votes_table.php +38 -0
- package/server/src/Auth/Schemas/Storefront.php +95 -0
- package/server/src/Console/Commands/NotifyStorefrontOrderNearby.php +86 -0
- package/server/src/Expansions/EntityExpansion.php +44 -0
- package/server/src/Http/Controllers/ActionController.php +119 -0
- package/server/src/Http/Controllers/AddonCategoryController.php +13 -0
- package/server/src/Http/Controllers/CustomerController.php +13 -0
- package/server/src/Http/Controllers/GatewayController.php +13 -0
- package/server/src/Http/Controllers/MetricsController.php +71 -0
- package/server/src/Http/Controllers/NetworkController.php +170 -0
- package/server/src/Http/Controllers/NotificationChannelController.php +13 -0
- package/server/src/Http/Controllers/OrderController.php +154 -0
- package/server/src/Http/Controllers/ProductAddonCategoryController.php +14 -0
- package/server/src/Http/Controllers/ProductAddonController.php +14 -0
- package/server/src/Http/Controllers/ProductController.php +123 -0
- package/server/src/Http/Controllers/ProductHourController.php +13 -0
- package/server/src/Http/Controllers/ProductVariantController.php +13 -0
- package/server/src/Http/Controllers/ProductVariantOptionController.php +13 -0
- package/server/src/Http/Controllers/ReviewController.php +13 -0
- package/server/src/Http/Controllers/StoreController.php +26 -0
- package/server/src/Http/Controllers/StoreHourController.php +13 -0
- package/server/src/Http/Controllers/StoreLocationController.php +13 -0
- package/server/src/Http/Controllers/StorefrontController.php +15 -0
- package/server/src/Http/Controllers/VoteController.php +13 -0
- package/server/src/Http/Controllers/v1/CartController.php +161 -0
- package/server/src/Http/Controllers/v1/CategoryController.php +138 -0
- package/server/src/Http/Controllers/v1/CheckoutController.php +957 -0
- package/server/src/Http/Controllers/v1/CustomerController.php +482 -0
- package/server/src/Http/Controllers/v1/GatewayControllerController.php +11 -0
- package/server/src/Http/Controllers/v1/NetworkController.php +281 -0
- package/server/src/Http/Controllers/v1/PaymentMethodController.php +11 -0
- package/server/src/Http/Controllers/v1/ProductController.php +94 -0
- package/server/src/Http/Controllers/v1/ReviewController.php +270 -0
- package/server/src/Http/Controllers/v1/ServiceQuoteController.php +402 -0
- package/server/src/Http/Controllers/v1/StoreController.php +176 -0
- package/server/src/Http/Filter/AddonCategoryFilter.php +19 -0
- package/server/src/Http/Filter/CustomerFilter.php +18 -0
- package/server/src/Http/Filter/GatewayFilter.php +13 -0
- package/server/src/Http/Filter/NetworkFilter.php +18 -0
- package/server/src/Http/Filter/NotificationChannelFilter.php +13 -0
- package/server/src/Http/Filter/OrderFilter.php +46 -0
- package/server/src/Http/Filter/ProductFilter.php +28 -0
- package/server/src/Http/Filter/StoreFilter.php +42 -0
- package/server/src/Http/Filter/StoreLocationFilter.php +23 -0
- package/server/src/Http/Middleware/SetStorefrontSession.php +130 -0
- package/server/src/Http/Requests/AddStoreToNetworkCategory.php +45 -0
- package/server/src/Http/Requests/CaptureOrderRequest.php +30 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +44 -0
- package/server/src/Http/Requests/CreateReviewRequest.php +34 -0
- package/server/src/Http/Requests/GetServiceQuoteFromCart.php +40 -0
- package/server/src/Http/Requests/InitializeCheckoutRequest.php +38 -0
- package/server/src/Http/Requests/NetworkActionRequest.php +43 -0
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +31 -0
- package/server/src/Http/Resources/Cart.php +31 -0
- package/server/src/Http/Resources/Category.php +48 -0
- package/server/src/Http/Resources/Customer.php +36 -0
- package/server/src/Http/Resources/Gateway.php +32 -0
- package/server/src/Http/Resources/Media.php +29 -0
- package/server/src/Http/Resources/Network.php +48 -0
- package/server/src/Http/Resources/Product.php +209 -0
- package/server/src/Http/Resources/Review.php +45 -0
- package/server/src/Http/Resources/ReviewCustomer.php +60 -0
- package/server/src/Http/Resources/Store.php +76 -0
- package/server/src/Http/Resources/StoreHour.php +29 -0
- package/server/src/Http/Resources/StoreLocation.php +33 -0
- package/server/src/Imports/ProductsImport.php +20 -0
- package/server/src/Jobs/DownloadProductImageUrl.php +60 -0
- package/server/src/Listeners/HandleOrderCompleted.php +31 -0
- package/server/src/Listeners/HandleOrderDispatched.php +34 -0
- package/server/src/Listeners/HandleOrderDriverAssigned.php +37 -0
- package/server/src/Listeners/HandleOrderStarted.php +27 -0
- package/server/src/Mail/StorefrontNetworkInvite.php +48 -0
- package/server/src/Models/AddonCategory.php +30 -0
- package/server/src/Models/Cart.php +691 -0
- package/server/src/Models/Checkout.php +166 -0
- package/server/src/Models/Customer.php +88 -0
- package/server/src/Models/Gateway.php +165 -0
- package/server/src/Models/Network.php +300 -0
- package/server/src/Models/NetworkStore.php +86 -0
- package/server/src/Models/NotificationChannel.php +147 -0
- package/server/src/Models/PaymentMethod.php +99 -0
- package/server/src/Models/Product.php +315 -0
- package/server/src/Models/ProductAddon.php +128 -0
- package/server/src/Models/ProductAddonCategory.php +90 -0
- package/server/src/Models/ProductHour.php +59 -0
- package/server/src/Models/ProductStoreLocation.php +77 -0
- package/server/src/Models/ProductVariant.php +125 -0
- package/server/src/Models/ProductVariantOption.php +86 -0
- package/server/src/Models/Review.php +127 -0
- package/server/src/Models/Store.php +478 -0
- package/server/src/Models/StoreHour.php +59 -0
- package/server/src/Models/StoreLocation.php +126 -0
- package/server/src/Models/StorefrontModel.php +22 -0
- package/server/src/Models/Vote.php +84 -0
- package/server/src/Notifications/StorefrontOrderCanceled.php +196 -0
- package/server/src/Notifications/StorefrontOrderCompleted.php +201 -0
- package/server/src/Notifications/StorefrontOrderCreated.php +157 -0
- package/server/src/Notifications/StorefrontOrderDriverAssigned.php +200 -0
- package/server/src/Notifications/StorefrontOrderEnroute.php +199 -0
- package/server/src/Notifications/StorefrontOrderNearby.php +201 -0
- package/server/src/Notifications/StorefrontOrderPreparing.php +202 -0
- package/server/src/Notifications/StorefrontOrderReadyForPickup.php +202 -0
- package/server/src/Observers/NetworkObserver.php +40 -0
- package/server/src/Observers/ProductObserver.php +118 -0
- package/server/src/Providers/EventServiceProvider.php +23 -0
- package/server/src/Providers/StorefrontServiceProvider.php +103 -0
- package/server/src/Support/Metrics.php +193 -0
- package/server/src/Support/OrderConfig.php +13 -0
- package/server/src/Support/QPay.php +208 -0
- package/server/src/Support/Storefront.php +201 -0
- package/server/src/routes.php +180 -0
- package/server/tests/Feature.php +5 -0
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\Storefront\Http\Controllers\v1;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Http\Controllers\Controller;
|
|
6
|
+
use Fleetbase\Storefront\Http\Requests\CaptureOrderRequest;
|
|
7
|
+
use Fleetbase\Storefront\Http\Requests\InitializeCheckoutRequest;
|
|
8
|
+
use Fleetbase\Storefront\Notifications\StorefrontOrderPreparing;
|
|
9
|
+
use Fleetbase\Storefront\Models\Cart;
|
|
10
|
+
use Fleetbase\Storefront\Models\Checkout;
|
|
11
|
+
use Fleetbase\Storefront\Models\Gateway;
|
|
12
|
+
use Fleetbase\Storefront\Models\Product;
|
|
13
|
+
use Fleetbase\Storefront\Models\StoreLocation;
|
|
14
|
+
use Fleetbase\Storefront\Models\Store;
|
|
15
|
+
use Fleetbase\Storefront\Support\QPay;
|
|
16
|
+
use Fleetbase\Storefront\Support\Storefront;
|
|
17
|
+
use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
|
|
18
|
+
use Fleetbase\FleetOps\Models\Contact;
|
|
19
|
+
use Fleetbase\FleetOps\Models\Entity;
|
|
20
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
21
|
+
use Fleetbase\FleetOps\Models\Payload;
|
|
22
|
+
use Fleetbase\FleetOps\Models\Place;
|
|
23
|
+
use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
24
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
25
|
+
use Fleetbase\Models\Transaction;
|
|
26
|
+
use Fleetbase\Models\TransactionItem;
|
|
27
|
+
use Illuminate\Http\Request;
|
|
28
|
+
use Illuminate\Support\Arr;
|
|
29
|
+
use Illuminate\Support\Str;
|
|
30
|
+
use Stripe\Exception\InvalidRequestException;
|
|
31
|
+
|
|
32
|
+
class CheckoutController extends Controller
|
|
33
|
+
{
|
|
34
|
+
public function beforeCheckout(InitializeCheckoutRequest $request)
|
|
35
|
+
{
|
|
36
|
+
$gatewayCode = $request->input('gateway');
|
|
37
|
+
$customerId = $request->input('customer');
|
|
38
|
+
$cartId = $request->input('cart');
|
|
39
|
+
$serviceQuoteId = $request->or(['serviceQuote', 'service_quote']);
|
|
40
|
+
$isCashOnDelivery = $request->input('cash') || $gatewayCode === 'cash';
|
|
41
|
+
$isPickup = $request->input('pickup', false);
|
|
42
|
+
$tip = $request->input('tip', false);
|
|
43
|
+
$deliveryTip = $request->or(['delivery_tip', 'deliveryTip'], false);
|
|
44
|
+
|
|
45
|
+
// create checkout options
|
|
46
|
+
$checkoutOptions = Utils::createObject([
|
|
47
|
+
'is_pickup' => $isPickup,
|
|
48
|
+
'is_cod' => $isCashOnDelivery,
|
|
49
|
+
'tip' => $tip,
|
|
50
|
+
'delivery_tip' => $deliveryTip
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// find and validate cart session
|
|
54
|
+
$cart = Cart::retrieve($cartId);
|
|
55
|
+
$gateway = Storefront::findGateway($gatewayCode);
|
|
56
|
+
$customer = Contact::findFromCustomerId($customerId);
|
|
57
|
+
$serviceQuote = ServiceQuote::select(['amount', 'meta', 'uuid', 'public_id'])->where('public_id', $serviceQuoteId)->first();
|
|
58
|
+
|
|
59
|
+
// handle cash orders
|
|
60
|
+
if ($isCashOnDelivery) {
|
|
61
|
+
return static::initializeCashCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!$gateway) {
|
|
65
|
+
return response()->error('No gateway configured!');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// handle checkout initialization based on gateway
|
|
69
|
+
if ($gateway->isStripeGateway) {
|
|
70
|
+
return static::initializeStripeCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// handle checkout initialization based on gateway
|
|
74
|
+
if ($gateway->isQpayGateway) {
|
|
75
|
+
return static::initializeQpayCheckout($customer, $gateway, $serviceQuote, $cart, $checkoutOptions);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return response()->error('Unable to initialize checkout!');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public static function initializeCashCheckout(Contact $customer, Gateway $gateway, ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions)
|
|
82
|
+
{
|
|
83
|
+
// check if pickup order
|
|
84
|
+
$isPickup = $checkoutOptions->is_pickup;
|
|
85
|
+
|
|
86
|
+
// get amount/subtotal
|
|
87
|
+
$amount = static::calculateCheckoutAmount($cart, $serviceQuote, $checkoutOptions);
|
|
88
|
+
$currency = $cart->currency ?? session('storefront_currency');
|
|
89
|
+
|
|
90
|
+
// get store id if applicable
|
|
91
|
+
$storeId = session('storefront_store');
|
|
92
|
+
|
|
93
|
+
if (!$storeId) {
|
|
94
|
+
$storeIds = collect($cart->items)->map(function ($cartItem) {
|
|
95
|
+
return $cartItem->store_id;
|
|
96
|
+
})->unique()->filter();
|
|
97
|
+
|
|
98
|
+
if ($storeIds->count() === 1) {
|
|
99
|
+
$publicStoreId = $storeIds->first();
|
|
100
|
+
|
|
101
|
+
if (Str::startsWith($publicStoreId, 'store_')) {
|
|
102
|
+
$storeId = Store::select('uuid')->where('public_id', $publicStoreId)->first()->uuid;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// create checkout token
|
|
108
|
+
$checkout = Checkout::create([
|
|
109
|
+
'company_uuid' => session('company'),
|
|
110
|
+
'store_uuid' => $storeId,
|
|
111
|
+
'network_uuid' => session('storefront_network'),
|
|
112
|
+
'cart_uuid' => $cart->uuid,
|
|
113
|
+
'gateway_uuid' => $gateway->uuid ?? null,
|
|
114
|
+
'service_quote_uuid' => $serviceQuote->uuid,
|
|
115
|
+
'owner_uuid' => $customer->uuid,
|
|
116
|
+
'owner_type' => 'contact',
|
|
117
|
+
'amount' => $amount,
|
|
118
|
+
'currency' => $currency,
|
|
119
|
+
'is_cod' => true,
|
|
120
|
+
'is_pickup' => $isPickup,
|
|
121
|
+
'options' => $checkoutOptions,
|
|
122
|
+
'cart_state' => $cart->toArray()
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
return response()->json([
|
|
126
|
+
'token' => $checkout->token
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public static function initializeStripeCheckout(Contact $customer, Gateway $gateway, ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions)
|
|
131
|
+
{
|
|
132
|
+
// check if pickup order
|
|
133
|
+
$isPickup = $checkoutOptions->is_pickup;
|
|
134
|
+
|
|
135
|
+
// get amount/subtotal
|
|
136
|
+
$amount = static::calculateCheckoutAmount($cart, $serviceQuote, $checkoutOptions);
|
|
137
|
+
$currency = $cart->currency ?? session('storefront_currency');
|
|
138
|
+
|
|
139
|
+
// check for secret key first
|
|
140
|
+
if (!isset($gateway->config->secret_key)) {
|
|
141
|
+
return response()->error('Gateway not configured correctly!');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Set the stipre secret key from gateway
|
|
145
|
+
\Stripe\Stripe::setApiKey($gateway->config->secret_key);
|
|
146
|
+
|
|
147
|
+
// Check customer meta for stripe id
|
|
148
|
+
if ($customer->missingMeta('stripe_id')) {
|
|
149
|
+
Storefront::createStripeCustomerForContact($customer);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$ephemeralKey = null;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
$ephemeralKey = \Stripe\EphemeralKey::create(
|
|
156
|
+
['customer' => $customer->getMeta('stripe_id')],
|
|
157
|
+
['stripe_version' => '2020-08-27']
|
|
158
|
+
);
|
|
159
|
+
} catch (InvalidRequestException $e) {
|
|
160
|
+
$errorMessage = $e->getMessage();
|
|
161
|
+
|
|
162
|
+
if (Str::contains($errorMessage, 'No such customer')) {
|
|
163
|
+
// create the customer for this network/store
|
|
164
|
+
Storefront::createStripeCustomerForContact($customer);
|
|
165
|
+
// regenerate key
|
|
166
|
+
$ephemeralKey = \Stripe\EphemeralKey::create(
|
|
167
|
+
['customer' => $customer->getMeta('stripe_id')],
|
|
168
|
+
['stripe_version' => '2020-08-27']
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
return response()->error('Error from Stripe: ' . $errorMessage);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
$paymentIntent = \Stripe\PaymentIntent::create([
|
|
176
|
+
'amount' => $amount,
|
|
177
|
+
'currency' => $currency,
|
|
178
|
+
'customer' => $customer->getMeta('stripe_id')
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
// create checkout token
|
|
182
|
+
$checkout = Checkout::create([
|
|
183
|
+
'company_uuid' => session('company'),
|
|
184
|
+
'store_uuid' => session('storefront_store'),
|
|
185
|
+
'network_uuid' => session('storefront_network'),
|
|
186
|
+
'cart_uuid' => $cart->uuid,
|
|
187
|
+
'gateway_uuid' => $gateway->uuid,
|
|
188
|
+
'service_quote_uuid' => $serviceQuote->uuid,
|
|
189
|
+
'owner_uuid' => $customer->uuid,
|
|
190
|
+
'owner_type' => 'contact',
|
|
191
|
+
'amount' => $amount,
|
|
192
|
+
'currency' => $currency,
|
|
193
|
+
'is_pickup' => $isPickup,
|
|
194
|
+
'options' => $checkoutOptions,
|
|
195
|
+
'cart_state' => $cart->toArray()
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
return response()->json([
|
|
199
|
+
'paymentIntent' => $paymentIntent->client_secret,
|
|
200
|
+
'ephemeralKey' => $ephemeralKey->secret,
|
|
201
|
+
'customerId' => $customer->getMeta('stripe_id'),
|
|
202
|
+
'token' => $checkout->token
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public static function initializeQpayCheckout(Contact $customer, Gateway $gateway, ServiceQuote $serviceQuote, Cart $cart, $checkoutOptions)
|
|
207
|
+
{
|
|
208
|
+
// Get store info
|
|
209
|
+
$about = Storefront::about();
|
|
210
|
+
|
|
211
|
+
// check if pickup order
|
|
212
|
+
$isPickup = $checkoutOptions->is_pickup;
|
|
213
|
+
|
|
214
|
+
// get amount/subtotal
|
|
215
|
+
$amount = static::calculateCheckoutAmount($cart, $serviceQuote, $checkoutOptions);
|
|
216
|
+
$currency = $cart->currency ?? session('storefront_currency');
|
|
217
|
+
|
|
218
|
+
// check for secret key first
|
|
219
|
+
if (!isset($gateway->config->username)) {
|
|
220
|
+
return response()->error('Gateway not configured correctly!');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Create qpay instance
|
|
224
|
+
$qpay = QPay::instance($gateway->config->username, $gateway->config->password, $gateway->callback_url);
|
|
225
|
+
|
|
226
|
+
if ($gateway->sandbox) {
|
|
227
|
+
$qpay = $qpay->useSandbox();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Set auth token
|
|
231
|
+
$qpay = $qpay->setAuthToken();
|
|
232
|
+
|
|
233
|
+
// Create invoice description
|
|
234
|
+
$invoiceAmount = round($amount / 100);
|
|
235
|
+
$invoiceCode = $gateway->sandbox ? 'TEST_INVOICE' : $gateway->config->invoice_id;
|
|
236
|
+
$invoiceDescription = $about->name . ' cart checkout';
|
|
237
|
+
$invoiceReceiverCode = $gateway->public_id;
|
|
238
|
+
$senderInvoiceNo = $cart->id;
|
|
239
|
+
|
|
240
|
+
// Create qpay invoice
|
|
241
|
+
$invoice = $qpay->createSimpleInvoice($invoiceAmount, $invoiceCode, $invoiceDescription, $invoiceReceiverCode, $senderInvoiceNo);
|
|
242
|
+
|
|
243
|
+
// Create checkout token
|
|
244
|
+
$checkout = Checkout::create([
|
|
245
|
+
'company_uuid' => session('company'),
|
|
246
|
+
'store_uuid' => session('storefront_store'),
|
|
247
|
+
'network_uuid' => session('storefront_network'),
|
|
248
|
+
'cart_uuid' => $cart->uuid,
|
|
249
|
+
'gateway_uuid' => $gateway->uuid,
|
|
250
|
+
'service_quote_uuid' => $serviceQuote->uuid,
|
|
251
|
+
'owner_uuid' => $customer->uuid,
|
|
252
|
+
'owner_type' => 'contact',
|
|
253
|
+
'amount' => $amount,
|
|
254
|
+
'currency' => $currency,
|
|
255
|
+
'is_pickup' => $isPickup,
|
|
256
|
+
'options' => $checkoutOptions,
|
|
257
|
+
'cart_state' => $cart->toArray()
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
return response()->json([
|
|
261
|
+
'invoice' => $invoice,
|
|
262
|
+
'token' => $checkout->token
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public function captureOrder(CaptureOrderRequest $request)
|
|
267
|
+
{
|
|
268
|
+
$token = $request->input('token');
|
|
269
|
+
$transactionDetails = $request->input('transactionDetails', []); // optional details to be supplied about transaction
|
|
270
|
+
|
|
271
|
+
// validate transaction details
|
|
272
|
+
if (!is_array($transactionDetails)) {
|
|
273
|
+
$transactionDetails = [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// get checkout data to create order
|
|
277
|
+
$about = Storefront::about();
|
|
278
|
+
$checkout = Checkout::where('token', $token)->with(['gateway', 'owner', 'serviceQuote', 'cart'])->first();
|
|
279
|
+
$customer = $checkout->owner;
|
|
280
|
+
$serviceQuote = $checkout->serviceQuote;
|
|
281
|
+
$gateway = $checkout->is_cod ? Gateway::cash() : $checkout->gateway;
|
|
282
|
+
$origin = $serviceQuote->getMeta('origin', []);
|
|
283
|
+
$destination = $serviceQuote->getMeta('destination');
|
|
284
|
+
$cart = $checkout->cart;
|
|
285
|
+
|
|
286
|
+
// if cart is null then cart has either been deleted or expired
|
|
287
|
+
if (!$cart) {
|
|
288
|
+
return response()->json([
|
|
289
|
+
'error' => 'Cart expired'
|
|
290
|
+
], 400);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// $amount = $checkout->amount ?? ($checkout->is_pickup ? $cart->subtotal : $cart->subtotal + $serviceQuote->amount);
|
|
294
|
+
$amount = static::calculateCheckoutAmount($cart, $serviceQuote, $checkout->options);
|
|
295
|
+
$currency = $checkout->currency ?? ($cart->currency ?? session('storefront_currency'));
|
|
296
|
+
$store = $about;
|
|
297
|
+
|
|
298
|
+
// check if order is via network for a single store
|
|
299
|
+
$isNetworkOrder = $about->is_network === true;
|
|
300
|
+
$isMultiCart = $cart->isMultiCart;
|
|
301
|
+
$isSingleStoreCheckout = $isNetworkOrder && !$isMultiCart;
|
|
302
|
+
$isMultipleStoreCheckout = $isNetworkOrder && $isMultiCart;
|
|
303
|
+
|
|
304
|
+
// if multi store checkout send to captureMultipleOrders()
|
|
305
|
+
if ($isMultipleStoreCheckout) {
|
|
306
|
+
return $this->captureMultipleOrders($request);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// if single store set store variable
|
|
310
|
+
if ($isSingleStoreCheckout) {
|
|
311
|
+
$store = Storefront::findAbout($cart->checkoutStoreId);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// super rare condition
|
|
315
|
+
if (!$store) {
|
|
316
|
+
return response()->error('No storefront in request to capture order!');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// prepare for integrated vendor order if applicable
|
|
320
|
+
$integratedVendorOrder = null;
|
|
321
|
+
|
|
322
|
+
// if service quote is applied, resolve it
|
|
323
|
+
if ($serviceQuote instanceof ServiceQuote && $serviceQuote->fromIntegratedVendor()) {
|
|
324
|
+
// create order with integrated vendor, then resume fleetbase order creation
|
|
325
|
+
try {
|
|
326
|
+
$integratedVendorOrder = $serviceQuote->integratedVendor->api()->createOrderFromServiceQuote($serviceQuote, $request);
|
|
327
|
+
} catch (\Exception $e) {
|
|
328
|
+
return response()->error($e->getMessage());
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// setup transaction meta
|
|
333
|
+
$transactionMeta = [
|
|
334
|
+
'storefront' => $store->name,
|
|
335
|
+
'storefront_id' => $store->public_id,
|
|
336
|
+
...$transactionDetails
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
if ($about->is_network) {
|
|
340
|
+
$transactionMeta['storefront_network'] = $about->name;
|
|
341
|
+
$transactionMeta['storefront_network_id'] = $about->public_id;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// create transactions for cart
|
|
345
|
+
$transaction = Transaction::create([
|
|
346
|
+
'company_uuid' => session('company'),
|
|
347
|
+
'customer_uuid' => $customer->uuid,
|
|
348
|
+
'customer_type' => 'contact',
|
|
349
|
+
'gateway_transaction_id' => Utils::or($transactionDetails, ['id', 'transaction_id']) ?? Transaction::generateNumber(),
|
|
350
|
+
'gateway' => $gateway->code,
|
|
351
|
+
'gateway_uuid' => $gateway->uuid,
|
|
352
|
+
'amount' => $amount,
|
|
353
|
+
'currency' => $currency,
|
|
354
|
+
'description' => 'Storefront order',
|
|
355
|
+
'type' => 'storefront',
|
|
356
|
+
'status' => 'success',
|
|
357
|
+
'meta' => $transactionMeta
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
// create transaction items
|
|
361
|
+
foreach ($cart->items as $cartItem) {
|
|
362
|
+
TransactionItem::create([
|
|
363
|
+
'transaction_uuid' => $transaction->uuid,
|
|
364
|
+
'amount' => $cartItem->subtotal,
|
|
365
|
+
'currency' => $checkout->currency,
|
|
366
|
+
'details' => Storefront::getFullDescriptionFromCartItem($cartItem),
|
|
367
|
+
'code' => 'product',
|
|
368
|
+
]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// create transaction item for service quote
|
|
372
|
+
if (!$checkout->is_pickup) {
|
|
373
|
+
TransactionItem::create([
|
|
374
|
+
'transaction_uuid' => $transaction->uuid,
|
|
375
|
+
'amount' => $serviceQuote->amount,
|
|
376
|
+
'currency' => $serviceQuote->currency,
|
|
377
|
+
'details' => 'Delivery fee',
|
|
378
|
+
'code' => 'delivery_fee',
|
|
379
|
+
]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// if tip create transaction item for tip
|
|
383
|
+
if ($checkout->hasOption('tip')) {
|
|
384
|
+
TransactionItem::create([
|
|
385
|
+
'transaction_uuid' => $transaction->uuid,
|
|
386
|
+
'amount' => static::calculateTipAmount($checkout->getOption('tip'), $cart->subtotal),
|
|
387
|
+
'currency' => $checkout->currency,
|
|
388
|
+
'details' => 'Tip',
|
|
389
|
+
'code' => 'tip',
|
|
390
|
+
]);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// if delivery tip create transaction item for tip
|
|
394
|
+
if ($checkout->hasOption('delivery_tip')) {
|
|
395
|
+
TransactionItem::create([
|
|
396
|
+
'transaction_uuid' => $transaction->uuid,
|
|
397
|
+
'amount' => static::calculateTipAmount($checkout->getOption('delivery_tip'), $cart->subtotal),
|
|
398
|
+
'currency' => $checkout->currency,
|
|
399
|
+
'details' => 'Delivery Tip',
|
|
400
|
+
'code' => 'delivery_tip',
|
|
401
|
+
]);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// if single cart checkout and origin is array get the first id
|
|
405
|
+
if (is_array($origin)) {
|
|
406
|
+
$origin = Arr::first($origin);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// if there is no origin attempt to get from cart
|
|
410
|
+
if (!$origin) {
|
|
411
|
+
$storeLocation = collect($cart->items)->map(function ($cartItem) {
|
|
412
|
+
$storeLocationId = $cartItem->store_location_id;
|
|
413
|
+
|
|
414
|
+
// if no store location id set, use first locations id
|
|
415
|
+
if (!$storeLocationId) {
|
|
416
|
+
$store = Store::where('public_id', $cartItem->store_id)->first();
|
|
417
|
+
|
|
418
|
+
if ($store) {
|
|
419
|
+
$storeLocationId = Utils::get($store, 'locations.0.public_id');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return $storeLocationId;
|
|
424
|
+
})->unique()->filter()->map(function ($storeLocationId) {
|
|
425
|
+
return StoreLocation::where('public_id', $storeLocationId)->first();
|
|
426
|
+
})->first();
|
|
427
|
+
|
|
428
|
+
if ($storeLocation) {
|
|
429
|
+
$origin = $storeLocation->place_uuid;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// convert payload destinations to Place
|
|
434
|
+
$origin = Place::createFromMixed($origin);
|
|
435
|
+
$destination = Place::createFromMixed($destination);
|
|
436
|
+
|
|
437
|
+
// create payload for order
|
|
438
|
+
$payloadDetails = [
|
|
439
|
+
'company_uuid' => session('company'),
|
|
440
|
+
'pickup_uuid' => $origin instanceof Place ? $origin->uuid : null,
|
|
441
|
+
'dropoff_uuid' => $destination instanceof Place ? $destination->uuid : null,
|
|
442
|
+
'return_uuid' => $origin instanceof Place ? $origin->uuid : null,
|
|
443
|
+
'payment_method' => $gateway->type,
|
|
444
|
+
'type' => 'storefront'
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
// if cash on delivery set cod attributes
|
|
448
|
+
if ($checkout->is_cod) {
|
|
449
|
+
$payloadDetails['cod_amount'] = $amount;
|
|
450
|
+
$payloadDetails['cod_currency'] = $checkout->currency;
|
|
451
|
+
// @todo could be card if card swipe on delivery
|
|
452
|
+
$payloadDetails['cod_payment_method'] = 'cash';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// create payload
|
|
456
|
+
$payload = Payload::create($payloadDetails);
|
|
457
|
+
|
|
458
|
+
// create entities
|
|
459
|
+
foreach ($cart->items as $cartItem) {
|
|
460
|
+
$product = Product::where('public_id', $cartItem->product_id)->first();
|
|
461
|
+
$entity = Entity::fromStorefrontProduct($product)->fill([
|
|
462
|
+
'company_uuid' => session('company'),
|
|
463
|
+
'payload_uuid' => $payload->uuid,
|
|
464
|
+
'customer_uuid' => $customer->uuid,
|
|
465
|
+
'customer_type' => 'contact',
|
|
466
|
+
])->setMetas([
|
|
467
|
+
'variants' => $cartItem->variants,
|
|
468
|
+
'addons' => $cartItem->addons,
|
|
469
|
+
'subtotal' => $cartItem->subtotal,
|
|
470
|
+
'quantity' => $cartItem->quantity,
|
|
471
|
+
'scheduled_at' => $cartItem->scheduled_at ?? null
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
$entity->save();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// create order meta
|
|
478
|
+
$orderMeta = [
|
|
479
|
+
'storefront' => $store->name,
|
|
480
|
+
'storefront_id' => $store->public_id,
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
// if network add network to order meta
|
|
484
|
+
if ($isNetworkOrder) {
|
|
485
|
+
$orderMeta['storefront_network'] = $about->name;
|
|
486
|
+
$orderMeta['storefront_network_id'] = $about->public_id;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
$orderMeta = array_merge($orderMeta, [
|
|
490
|
+
'checkout_id' => $checkout->public_id,
|
|
491
|
+
'subtotal' => Utils::numbersOnly($cart->subtotal),
|
|
492
|
+
'delivery_fee' => $checkout->is_pickip ? 0 : Utils::numbersOnly($serviceQuote->amount),
|
|
493
|
+
'tip' => $checkout->getOption('tip'),
|
|
494
|
+
'delivery_tip' => $checkout->getOption('delivery_tip'),
|
|
495
|
+
'total' => Utils::numbersOnly($amount),
|
|
496
|
+
'currency' => $currency,
|
|
497
|
+
'require_pod' => $about->getOption('require_pod'),
|
|
498
|
+
'pod_method' => $about->pod_method,
|
|
499
|
+
'is_pickup' => $checkout->is_pickup,
|
|
500
|
+
...$transactionDetails
|
|
501
|
+
]);
|
|
502
|
+
|
|
503
|
+
// initialize order creation input
|
|
504
|
+
$orderInput = [
|
|
505
|
+
'company_uuid' => $store->company_uuid ?? session('company'),
|
|
506
|
+
'payload_uuid' => $payload->uuid,
|
|
507
|
+
'customer_uuid' => $customer->uuid,
|
|
508
|
+
'customer_type' => 'contact',
|
|
509
|
+
'transaction_uuid' => $transaction->uuid,
|
|
510
|
+
'adhoc' => $about->isOption('auto_dispatch'),
|
|
511
|
+
'type' => 'storefront',
|
|
512
|
+
'status' => 'created',
|
|
513
|
+
'meta' => $orderMeta
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
// if it's integrated vendor order apply to meta
|
|
517
|
+
if ($integratedVendorOrder) {
|
|
518
|
+
$orderMeta['integrated_vendor'] = $serviceQuote->integratedVendor->public_id;
|
|
519
|
+
$orderMeta['integrated_vendor_order'] = $integratedVendorOrder;
|
|
520
|
+
// order input
|
|
521
|
+
$orderInput['facilitator_uuid'] = $serviceQuote->integratedVendor->uuid;
|
|
522
|
+
$orderInput['facilitator_type'] = Utils::getModelClassName('integrated_vendors');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// create order
|
|
526
|
+
$order = Order::create($orderInput);
|
|
527
|
+
|
|
528
|
+
// notify order creation
|
|
529
|
+
Storefront::alertNewOrder($order);
|
|
530
|
+
|
|
531
|
+
// purchase service quote
|
|
532
|
+
$order->purchaseQuote($serviceQuote->uuid, $transactionDetails);
|
|
533
|
+
|
|
534
|
+
// if order is auto accepted update status
|
|
535
|
+
if ($about->isOption('auto_accept_orders')) {
|
|
536
|
+
if ($about->isOption('auto_dispatch')) {
|
|
537
|
+
$order->updateStatus(['preparing', 'dispatched']);
|
|
538
|
+
} else {
|
|
539
|
+
$order->updateStatus('preparing');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// notify customer order is preparing
|
|
543
|
+
$customer->notify(new StorefrontOrderPreparing($order));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// update the cart with the checkout
|
|
547
|
+
$checkout->checkedout();
|
|
548
|
+
|
|
549
|
+
// update checkout token
|
|
550
|
+
$checkout->update([
|
|
551
|
+
'order_uuid' => $order->uuid,
|
|
552
|
+
// 'store_uuid' => $about->uuid,
|
|
553
|
+
'captured' => true
|
|
554
|
+
]);
|
|
555
|
+
|
|
556
|
+
return new OrderResource($order);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
public function captureMultipleOrders(CaptureOrderRequest $request)
|
|
560
|
+
{
|
|
561
|
+
$token = $request->input('token');
|
|
562
|
+
$transactionDetails = $request->input('transactionDetails', []); // optional details to be supplied about transaction
|
|
563
|
+
|
|
564
|
+
// validate transaction details
|
|
565
|
+
if (!is_array($transactionDetails)) {
|
|
566
|
+
$transactionDetails = [];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// get checkout data to create order
|
|
570
|
+
$about = Storefront::about();
|
|
571
|
+
$checkout = Checkout::where('token', $token)->with(['gateway', 'owner', 'serviceQuote', 'cart'])->first();
|
|
572
|
+
$customer = $checkout->owner;
|
|
573
|
+
$serviceQuote = $checkout->serviceQuote;
|
|
574
|
+
$gateway = $checkout->is_cod ? Gateway::cash() : $checkout->gateway;
|
|
575
|
+
$origins = $serviceQuote->getMeta('origin');
|
|
576
|
+
// set origin
|
|
577
|
+
$origin = Arr::first($origins);
|
|
578
|
+
$waypoints = array_slice($origins, 1);
|
|
579
|
+
$destination = $serviceQuote->getMeta('destination');
|
|
580
|
+
$cart = $checkout->cart;
|
|
581
|
+
// $amount = $checkout->amount ?? ($checkout->is_pickup ? $cart->subtotal : $cart->subtotal + $serviceQuote->amount);
|
|
582
|
+
$amount = static::calculateCheckoutAmount($cart, $serviceQuote, $checkout->options);
|
|
583
|
+
$currency = $checkout->currency ?? ($cart->currency ?? session('storefront_currency'));
|
|
584
|
+
|
|
585
|
+
if (!$about) {
|
|
586
|
+
return response()->error('No network in request to capture order!');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// prepare for integrated vendor order if applicable
|
|
590
|
+
$integratedVendorOrder = null;
|
|
591
|
+
|
|
592
|
+
// if service quote is applied, resolve it
|
|
593
|
+
if ($serviceQuote instanceof ServiceQuote && $serviceQuote->fromIntegratedVendor()) {
|
|
594
|
+
// create order with integrated vendor, then resume fleetbase order creation
|
|
595
|
+
try {
|
|
596
|
+
$integratedVendorOrder = $serviceQuote->integratedVendor->api()->createOrderFromServiceQuote($serviceQuote, $request);
|
|
597
|
+
} catch (\Exception $e) {
|
|
598
|
+
return response()->error($e->getMessage());
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// setup transaction meta
|
|
603
|
+
$transactionMeta = [
|
|
604
|
+
'storefront_network' => $about->name,
|
|
605
|
+
'storefront_network_id' => $about->public_id,
|
|
606
|
+
...$transactionDetails
|
|
607
|
+
];
|
|
608
|
+
|
|
609
|
+
// create transactions for cart
|
|
610
|
+
$transaction = Transaction::create([
|
|
611
|
+
'company_uuid' => session('company'),
|
|
612
|
+
'customer_uuid' => $customer->uuid,
|
|
613
|
+
'customer_type' => 'contact',
|
|
614
|
+
'gateway_transaction_id' => Utils::or($transactionDetails, ['id', 'transaction_id']) ?? Transaction::generateNumber(),
|
|
615
|
+
'gateway' => $gateway->code,
|
|
616
|
+
'gateway_uuid' => $gateway->uuid,
|
|
617
|
+
'amount' => $amount,
|
|
618
|
+
'currency' => $currency,
|
|
619
|
+
'description' => 'Storefront network order',
|
|
620
|
+
'type' => 'storefront',
|
|
621
|
+
'status' => 'success',
|
|
622
|
+
'meta' => $transactionMeta
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
// create transaction items
|
|
626
|
+
foreach ($cart->items as $cartItem) {
|
|
627
|
+
$store = Storefront::findAbout($cartItem->store_id);
|
|
628
|
+
|
|
629
|
+
TransactionItem::create([
|
|
630
|
+
'transaction_uuid' => $transaction->uuid,
|
|
631
|
+
'amount' => $cartItem->subtotal,
|
|
632
|
+
'currency' => $checkout->currency,
|
|
633
|
+
'details' => Storefront::getFullDescriptionFromCartItem($cartItem),
|
|
634
|
+
'code' => 'product',
|
|
635
|
+
'meta' => [
|
|
636
|
+
'storefront_network' => $about->name,
|
|
637
|
+
'storefront_network_id' => $about->public_id,
|
|
638
|
+
'storefront' => $store->name ?? null,
|
|
639
|
+
'storefront_id' => $store->public_id ?? null
|
|
640
|
+
]
|
|
641
|
+
]);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// create transaction item for service quote
|
|
645
|
+
if (!$checkout->is_pickup) {
|
|
646
|
+
TransactionItem::create([
|
|
647
|
+
'transaction_uuid' => $transaction->uuid,
|
|
648
|
+
'amount' => $serviceQuote->amount,
|
|
649
|
+
'currency' => $serviceQuote->currency,
|
|
650
|
+
'details' => 'Delivery fee',
|
|
651
|
+
'code' => 'delivery_fee',
|
|
652
|
+
]);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// if tip create transaction item for tip
|
|
656
|
+
if ($checkout->hasOption('tip')) {
|
|
657
|
+
TransactionItem::create([
|
|
658
|
+
'transaction_uuid' => $transaction->uuid,
|
|
659
|
+
'amount' => static::calculateTipAmount($checkout->getOption('tip'), $cart->subtotal),
|
|
660
|
+
'currency' => $checkout->currency,
|
|
661
|
+
'details' => 'Tip',
|
|
662
|
+
'code' => 'tip',
|
|
663
|
+
]);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// if delivery tip create transaction item for tip
|
|
667
|
+
if ($checkout->hasOption('delivery_tip')) {
|
|
668
|
+
TransactionItem::create([
|
|
669
|
+
'transaction_uuid' => $transaction->uuid,
|
|
670
|
+
'amount' => static::calculateTipAmount($checkout->getOption('delivery_tip'), $cart->subtotal),
|
|
671
|
+
'currency' => $checkout->currency,
|
|
672
|
+
'details' => 'Delivery Tip',
|
|
673
|
+
'code' => 'delivery_tip',
|
|
674
|
+
]);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// convert payload destinations to Place
|
|
678
|
+
$origins = collect($origins)->map(function ($publicId) {
|
|
679
|
+
return Place::createFromMixed($publicId);
|
|
680
|
+
});
|
|
681
|
+
$destination = Place::createFromMixed($destination);
|
|
682
|
+
|
|
683
|
+
$multipleOrders = [];
|
|
684
|
+
|
|
685
|
+
foreach ($origins as $pickup) {
|
|
686
|
+
$store = Storefront::getStoreFromLocation($pickup);
|
|
687
|
+
|
|
688
|
+
// create payload
|
|
689
|
+
$payload = Payload::create([
|
|
690
|
+
'company_uuid' => $store->company_uuid,
|
|
691
|
+
'pickup_uuid' => $pickup instanceof Place ? $pickup->uuid : null,
|
|
692
|
+
'dropoff_uuid' => $destination instanceof Place ? $destination->uuid : null,
|
|
693
|
+
'return_uuid' => $pickup instanceof Place ? $pickup->uuid : null,
|
|
694
|
+
'payment_method' => $gateway->type,
|
|
695
|
+
'type' => 'storefront'
|
|
696
|
+
]);
|
|
697
|
+
|
|
698
|
+
// get cart items from this store
|
|
699
|
+
$cartItems = $cart->getItemsForStore($store);
|
|
700
|
+
|
|
701
|
+
// create entities
|
|
702
|
+
foreach ($cartItems as $cartItem) {
|
|
703
|
+
$product = Product::where('public_id', $cartItem->product_id)->first();
|
|
704
|
+
|
|
705
|
+
$entity = Entity::fromStorefrontProduct($product)->fill([
|
|
706
|
+
'company_uuid' => $store->company_uuid,
|
|
707
|
+
'payload_uuid' => $payload->uuid,
|
|
708
|
+
'customer_uuid' => $customer->uuid,
|
|
709
|
+
'customer_type' => 'contact',
|
|
710
|
+
])->setMetas([
|
|
711
|
+
'variants' => $cartItem->variants,
|
|
712
|
+
'addons' => $cartItem->addons,
|
|
713
|
+
'subtotal' => $cartItem->subtotal,
|
|
714
|
+
'quantity' => $cartItem->quantity,
|
|
715
|
+
'scheduled_at' => $cartItem->scheduled_at ?? null
|
|
716
|
+
]);
|
|
717
|
+
|
|
718
|
+
$entity->save();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// get order subtotal
|
|
722
|
+
$subtotal = $cart->getSubtotalForStore($store);
|
|
723
|
+
|
|
724
|
+
// prepare order meta
|
|
725
|
+
$orderMeta = [
|
|
726
|
+
'is_master_order' => false,
|
|
727
|
+
'storefront' => $store->name,
|
|
728
|
+
'storefront_id' => $store->public_id,
|
|
729
|
+
'storefront_network' => $about->name,
|
|
730
|
+
'storefront_network_id' => $about->public_id,
|
|
731
|
+
'checkout_id' => $checkout->public_id,
|
|
732
|
+
'subtotal' => $subtotal,
|
|
733
|
+
'delivery_fee' => 0,
|
|
734
|
+
'tip' => 0,
|
|
735
|
+
'delivery_tip' => 0,
|
|
736
|
+
'total' => $subtotal,
|
|
737
|
+
'currency' => $currency,
|
|
738
|
+
'require_pod' => $about->getOption('require_pod'),
|
|
739
|
+
'pod_method' => $about->pod_method,
|
|
740
|
+
'is_pickup' => $checkout->is_pickup,
|
|
741
|
+
...$transactionDetails
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
// prepare order input
|
|
745
|
+
$orderInput = [
|
|
746
|
+
'company_uuid' => $store->company_uuid,
|
|
747
|
+
'payload_uuid' => $payload->uuid,
|
|
748
|
+
'customer_uuid' => $customer->uuid,
|
|
749
|
+
'customer_type' => 'contact',
|
|
750
|
+
'transaction_uuid' => $transaction->uuid,
|
|
751
|
+
'adhoc' => $about->isOption('auto_dispatch'),
|
|
752
|
+
'type' => 'storefront',
|
|
753
|
+
'status' => 'created',
|
|
754
|
+
];
|
|
755
|
+
|
|
756
|
+
// if it's integrated vendor order apply to meta
|
|
757
|
+
if ($integratedVendorOrder) {
|
|
758
|
+
$orderMeta['integrated_vendor'] = $serviceQuote->integratedVendor->public_id;
|
|
759
|
+
$orderMeta['integrated_vendor_order'] = $integratedVendorOrder;
|
|
760
|
+
// order input
|
|
761
|
+
$orderInput['facilitator_uuid'] = $serviceQuote->integratedVendor->uuid;
|
|
762
|
+
$orderInput['facilitator_type'] = Utils::getModelClassName('integrated_vendors');
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// set meta to order input last
|
|
766
|
+
$orderInput['meta'] = $orderMeta;
|
|
767
|
+
|
|
768
|
+
// create order
|
|
769
|
+
$multipleOrders[] = $order = Order::create($orderInput);
|
|
770
|
+
|
|
771
|
+
// set driving distance and time
|
|
772
|
+
$order->setPreliminaryDistanceAndTime();
|
|
773
|
+
|
|
774
|
+
// purchase service quote
|
|
775
|
+
$order->purchaseQuote($serviceQuote->uuid, $transactionDetails);
|
|
776
|
+
|
|
777
|
+
// notify order creation
|
|
778
|
+
Storefront::alertNewOrder($order);
|
|
779
|
+
|
|
780
|
+
// if order is auto accepted update status
|
|
781
|
+
if ($store->isOption('auto_accept_orders')) {
|
|
782
|
+
if ($store->isOption('auto_dispatch')) {
|
|
783
|
+
$order->updateStatus(['preparing', 'dispatched']);
|
|
784
|
+
} else {
|
|
785
|
+
$order->updateStatus('preparing');
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// notify customer order is preparing
|
|
789
|
+
$customer->notify(new StorefrontOrderPreparing($order));
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// convert origin to Place
|
|
794
|
+
$origin = Place::createFromMixed($origin);
|
|
795
|
+
|
|
796
|
+
// create master payload
|
|
797
|
+
$payload = Payload::create([
|
|
798
|
+
'company_uuid' => session('company'),
|
|
799
|
+
'pickup_uuid' => $origin instanceof Place ? $origin->uuid : null,
|
|
800
|
+
'dropoff_uuid' => $destination instanceof Place ? $destination->uuid : null,
|
|
801
|
+
'return_uuid' => $origin instanceof Place ? $origin->uuid : null,
|
|
802
|
+
'payment_method' => $gateway->type,
|
|
803
|
+
'type' => 'storefront'
|
|
804
|
+
])->setWaypoints($waypoints);
|
|
805
|
+
|
|
806
|
+
// create entities
|
|
807
|
+
foreach ($cart->items as $cartItem) {
|
|
808
|
+
$product = Product::where('public_id', $cartItem->product_id)->first();
|
|
809
|
+
|
|
810
|
+
$entity = Entity::fromStorefrontProduct($product)->fill([
|
|
811
|
+
'company_uuid' => session('company'),
|
|
812
|
+
'payload_uuid' => $payload->uuid,
|
|
813
|
+
'customer_uuid' => $customer->uuid,
|
|
814
|
+
'customer_type' => 'contact',
|
|
815
|
+
])->setMetas([
|
|
816
|
+
'variants' => $cartItem->variants,
|
|
817
|
+
'addons' => $cartItem->addons,
|
|
818
|
+
'subtotal' => $cartItem->subtotal,
|
|
819
|
+
'quantity' => $cartItem->quantity,
|
|
820
|
+
'scheduled_at' => $cartItem->scheduled_at ?? null
|
|
821
|
+
]);
|
|
822
|
+
|
|
823
|
+
$entity->save();
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// prepare master order meta
|
|
827
|
+
$masterOrderMeta = [
|
|
828
|
+
'is_master_order' => true,
|
|
829
|
+
'related_orders' => collect($multipleOrders)->pluck('public_id')->toArray(),
|
|
830
|
+
'storefront' => $about->name,
|
|
831
|
+
'storefront_id' => $about->public_id,
|
|
832
|
+
'storefront_network' => $about->name,
|
|
833
|
+
'storefront_network_id' => $about->public_id,
|
|
834
|
+
'checkout_id' => $checkout->public_id,
|
|
835
|
+
'subtotal' => Utils::numbersOnly($cart->subtotal),
|
|
836
|
+
'delivery_fee' => $checkout->is_pickip ? 0 : Utils::numbersOnly($serviceQuote->amount),
|
|
837
|
+
'tip' => $checkout->getOption('tip'),
|
|
838
|
+
'delivery_tip' => $checkout->getOption('delivery_tip'),
|
|
839
|
+
'total' => Utils::numbersOnly($amount),
|
|
840
|
+
'currency' => $currency,
|
|
841
|
+
'require_pod' => $about->getOption('require_pod'),
|
|
842
|
+
'pod_method' => $about->pod_method,
|
|
843
|
+
'is_pickup' => $checkout->is_pickup,
|
|
844
|
+
...$transactionDetails
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
// prepare master order input
|
|
848
|
+
$masterOrderInput = [
|
|
849
|
+
'company_uuid' => session('company'),
|
|
850
|
+
'payload_uuid' => $payload->uuid,
|
|
851
|
+
'customer_uuid' => $customer->uuid,
|
|
852
|
+
'customer_type' => 'contact',
|
|
853
|
+
'transaction_uuid' => $transaction->uuid,
|
|
854
|
+
'adhoc' => $about->isOption('auto_dispatch'),
|
|
855
|
+
'type' => 'storefront',
|
|
856
|
+
'status' => 'created',
|
|
857
|
+
];
|
|
858
|
+
|
|
859
|
+
// if it's integrated vendor order apply to meta
|
|
860
|
+
if ($integratedVendorOrder) {
|
|
861
|
+
$masterOrderMeta['integrated_vendor'] = $serviceQuote->integratedVendor->public_id;
|
|
862
|
+
$masterOrderMeta['integrated_vendor_order'] = $integratedVendorOrder;
|
|
863
|
+
// order input
|
|
864
|
+
$masterOrderInput['facilitator_uuid'] = $serviceQuote->integratedVendor->uuid;
|
|
865
|
+
$masterOrderInput['facilitator_type'] = Utils::getModelClassName('integrated_vendors');
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// finally apply meta to master order
|
|
869
|
+
$masterOrderInput['meta'] = $masterOrderMeta;
|
|
870
|
+
|
|
871
|
+
// create master order
|
|
872
|
+
$order = Order::create($masterOrderInput);
|
|
873
|
+
|
|
874
|
+
// update child orders with master order id in meta
|
|
875
|
+
foreach ($multipleOrders as $childOrder) {
|
|
876
|
+
$childOrder->updateMeta('master_order_id', $order->public_id);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// notify driver if assigned
|
|
880
|
+
$order->notifyDriverAssigned();
|
|
881
|
+
|
|
882
|
+
// set driving distance and time
|
|
883
|
+
$order->setPreliminaryDistanceAndTime();
|
|
884
|
+
|
|
885
|
+
// purchase service quote
|
|
886
|
+
$order->purchaseQuote($serviceQuote->uuid, $transactionDetails);
|
|
887
|
+
|
|
888
|
+
// dispatch if flagged true
|
|
889
|
+
$order->firstDispatch();
|
|
890
|
+
|
|
891
|
+
// update the cart with the checkout
|
|
892
|
+
$checkout->checkedout();
|
|
893
|
+
|
|
894
|
+
// update checkout token
|
|
895
|
+
$checkout->update([
|
|
896
|
+
'order_uuid' => $order->uuid,
|
|
897
|
+
// 'store_uuid' => $about->uuid,
|
|
898
|
+
'captured' => true
|
|
899
|
+
]);
|
|
900
|
+
|
|
901
|
+
return new OrderResource($order);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
public function afterCheckout(Request $request)
|
|
905
|
+
{
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Calculates the total checkout amount.
|
|
910
|
+
*
|
|
911
|
+
* @param Cart $cart
|
|
912
|
+
* @param ServiceQuote $serviceQuote
|
|
913
|
+
* @param stdClass $checkoutOptions
|
|
914
|
+
* @return integer
|
|
915
|
+
*/
|
|
916
|
+
private static function calculateCheckoutAmount(Cart $cart, ServiceQuote $serviceQuote, $checkoutOptions): int
|
|
917
|
+
{
|
|
918
|
+
// cast checkout options to object always
|
|
919
|
+
$checkoutOptions = (object) $checkoutOptions;
|
|
920
|
+
$subtotal = (int) $cart->subtotal;
|
|
921
|
+
$total = $subtotal;
|
|
922
|
+
$tip = $checkoutOptions->tip ?? false;
|
|
923
|
+
$deliveryTip = $checkoutOptions->delivery_tip ?? false;
|
|
924
|
+
$isPickup = $checkoutOptions->is_pickup ?? false;
|
|
925
|
+
|
|
926
|
+
if ($tip) {
|
|
927
|
+
$tipAmount = static::calculateTipAmount($tip, $subtotal);
|
|
928
|
+
|
|
929
|
+
$total += $tipAmount;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if ($deliveryTip && !$isPickup) {
|
|
933
|
+
$deliveryTipAmount = static::calculateTipAmount($deliveryTip, $subtotal);
|
|
934
|
+
|
|
935
|
+
$total += $deliveryTipAmount;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!$isPickup) {
|
|
939
|
+
$total += Utils::numbersOnly($serviceQuote->amount);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return $total;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private static function calculateTipAmount($tip, $subtotal)
|
|
946
|
+
{
|
|
947
|
+
$tipAmount = 0;
|
|
948
|
+
|
|
949
|
+
if (is_string($tip) && Str::endsWith($tip, '%')) {
|
|
950
|
+
$tipAmount = Utils::calculatePercentage(Utils::numbersOnly($tip), $subtotal);
|
|
951
|
+
} else {
|
|
952
|
+
$tipAmount = Utils::numbersOnly($tip);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return $tipAmount;
|
|
956
|
+
}
|
|
957
|
+
}
|