@fleetbase/storefront-engine 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.php-cs-fixer.php +29 -0
- package/LICENSE.md +16 -4
- package/README.md +106 -13
- package/addon/components/modals/add-store-to-category.hbs +5 -0
- package/addon/components/modals/create-network-category.hbs +28 -28
- package/addon/components/modals/share-network.hbs +3 -3
- package/addon/components/modals/store-details.hbs +22 -0
- package/addon/components/modals/store-form.hbs +22 -0
- package/addon/components/modals/store-form.js +21 -0
- package/addon/components/network-category-picker.hbs +40 -0
- package/addon/components/network-category-picker.js +83 -0
- package/addon/components/store-selector.hbs +4 -2
- package/addon/components/widget/orders.js +22 -17
- package/addon/controllers/networks/index/network/index.js +133 -9
- package/addon/controllers/networks/index/network/stores.js +422 -153
- package/addon/controllers/networks/index.js +66 -4
- package/addon/controllers/products/index/category.js +1 -1
- package/addon/controllers/settings/index.js +14 -7
- package/addon/controllers/settings/notifications.js +1 -1
- package/addon/models/store.js +44 -7
- package/addon/routes/networks/index/network/customers.js +4 -1
- package/addon/routes/networks/index/network/orders.js +4 -1
- package/addon/routes/networks/index/network/stores.js +19 -15
- package/addon/serializers/store.js +1 -0
- package/addon/templates/customers/index.hbs +0 -11
- package/addon/templates/networks/index/network/index.hbs +1 -1
- package/addon/templates/networks/index/network/stores.hbs +8 -163
- package/addon/templates/networks/index/network.hbs +2 -3
- package/addon/utils/create-shareable-link.js +21 -0
- package/app/adapters/addon-category.js +1 -1
- package/app/adapters/gateway.js +1 -1
- package/app/adapters/network.js +1 -1
- package/app/adapters/notification-channel.js +1 -1
- package/app/adapters/product-addon-category.js +1 -1
- package/app/adapters/product-addon.js +1 -1
- package/app/adapters/product-hour.js +1 -1
- package/app/adapters/product-store-location.js +1 -1
- package/app/adapters/product-variant-option.js +1 -1
- package/app/adapters/product-variant.js +1 -1
- package/app/adapters/product.js +1 -1
- package/app/adapters/store-hour.js +1 -1
- package/app/adapters/store-location.js +1 -1
- package/app/adapters/store.js +1 -1
- package/app/adapters/storefront.js +1 -1
- package/app/components/file-record.js +1 -1
- package/app/components/modals/add-store-hours.js +1 -1
- package/app/components/modals/add-store-to-category.js +1 -0
- package/app/components/modals/add-stores-to-network.js +1 -1
- package/app/components/modals/assign-driver.js +1 -1
- package/app/components/modals/create-first-store.js +1 -1
- package/app/components/modals/create-gateway.js +1 -1
- package/app/components/modals/create-network-category.js +1 -1
- package/app/components/modals/create-network.js +1 -1
- package/app/components/modals/create-new-variant.js +1 -1
- package/app/components/modals/create-notification-channel.js +1 -1
- package/app/components/modals/create-product-category.js +1 -1
- package/app/components/modals/create-store.js +1 -1
- package/app/components/modals/edit-network.js +1 -1
- package/app/components/modals/import-products.js +1 -1
- package/app/components/modals/incoming-order.js +1 -1
- package/app/components/modals/manage-addons.js +1 -1
- package/app/components/modals/order-ready-assign-driver.js +1 -1
- package/app/components/modals/select-addon-category.js +1 -1
- package/app/components/modals/share-network.js +1 -1
- package/app/components/modals/store-details.js +1 -0
- package/app/components/modals/store-form.js +1 -0
- package/app/components/modals/store-location-form.js +1 -1
- package/app/components/network-category-picker.js +1 -0
- package/app/components/order-card.js +1 -1
- package/app/components/schedule-manager.js +1 -1
- package/app/components/settings-container.js +1 -1
- package/app/components/store-selector.js +1 -1
- package/app/components/widget/customers.js +1 -1
- package/app/components/widget/orders.js +1 -1
- package/app/components/widget/storefront-metrics.js +1 -1
- package/app/controllers/application.js +1 -1
- package/app/controllers/customers/index.js +1 -1
- package/app/controllers/home.js +1 -1
- package/app/controllers/networks/index/network/customers.js +1 -1
- package/app/controllers/networks/index/network/index.js +1 -1
- package/app/controllers/networks/index/network/orders.js +1 -1
- package/app/controllers/networks/index/network/stores.js +1 -1
- package/app/controllers/networks/index/network.js +1 -1
- package/app/controllers/networks/index.js +1 -1
- package/app/controllers/orders/index.js +1 -1
- package/app/controllers/products/index/category/edit.js +1 -1
- package/app/controllers/products/index/category/new.js +1 -1
- package/app/controllers/products/index/category.js +1 -1
- package/app/controllers/products/index/index/edit.js +1 -1
- package/app/controllers/products/index/index.js +1 -1
- package/app/controllers/products/index.js +1 -1
- package/app/controllers/settings/api.js +1 -1
- package/app/controllers/settings/gateways.js +1 -1
- package/app/controllers/settings/index.js +1 -1
- package/app/controllers/settings/locations.js +1 -1
- package/app/controllers/settings/notifications.js +1 -1
- package/app/helpers/get-tip-amount.js +1 -1
- package/app/models/addon-category.js +1 -1
- package/app/models/gateway.js +1 -1
- package/app/models/network.js +1 -1
- package/app/models/notification-channel.js +1 -1
- package/app/models/product-addon-category.js +1 -1
- package/app/models/product-addon.js +1 -1
- package/app/models/product-hour.js +1 -1
- package/app/models/product-store-location.js +1 -1
- package/app/models/product-variant-option.js +1 -1
- package/app/models/product-variant.js +1 -1
- package/app/models/product.js +1 -1
- package/app/models/store-hour.js +1 -1
- package/app/models/store-location.js +1 -1
- package/app/models/store.js +1 -1
- package/app/routes/application.js +1 -1
- package/app/routes/customers/index/edit.js +1 -1
- package/app/routes/customers/index.js +1 -1
- package/app/routes/home.js +1 -1
- package/app/routes/networks/index/network/customers.js +1 -1
- package/app/routes/networks/index/network/index.js +1 -1
- package/app/routes/networks/index/network/orders.js +1 -1
- package/app/routes/networks/index/network/stores.js +1 -1
- package/app/routes/networks/index/network.js +1 -1
- package/app/routes/networks/index.js +1 -1
- package/app/routes/orders/index/edit.js +1 -1
- package/app/routes/orders/index/new.js +1 -1
- package/app/routes/orders/index/view.js +1 -1
- package/app/routes/orders/index.js +1 -1
- package/app/routes/products/index/category/edit.js +1 -1
- package/app/routes/products/index/category/new.js +1 -1
- package/app/routes/products/index/category.js +1 -1
- package/app/routes/products/index/index/edit.js +1 -1
- package/app/routes/products/index/index.js +1 -1
- package/app/routes/products/index.js +1 -1
- package/app/routes/settings/api.js +1 -1
- package/app/routes/settings/gateways.js +1 -1
- package/app/routes/settings/index.js +1 -1
- package/app/routes/settings/locations.js +1 -1
- package/app/routes/settings/notifications.js +1 -1
- package/app/serializers/addon-category.js +1 -1
- package/app/serializers/network.js +1 -1
- package/app/serializers/notification-channel.js +1 -1
- package/app/serializers/product-addon-category.js +1 -1
- package/app/serializers/product-variant.js +1 -1
- package/app/serializers/product.js +1 -1
- package/app/serializers/store-location.js +1 -1
- package/app/serializers/store.js +1 -1
- package/app/services/storefront.js +1 -1
- package/app/templates/customers/index/edit.js +1 -1
- package/app/templates/customers/index.js +1 -1
- package/app/templates/home.js +1 -1
- package/app/templates/networks/index/network/customers.js +1 -1
- package/app/templates/networks/index/network/index.js +1 -1
- package/app/templates/networks/index/network/orders.js +1 -1
- package/app/templates/networks/index/network/stores.js +1 -1
- package/app/templates/networks/index/network.js +1 -1
- package/app/templates/networks/index.js +1 -1
- package/app/templates/orders/index/edit.js +1 -1
- package/app/templates/orders/index/new.js +1 -1
- package/app/templates/orders/index/view.js +1 -1
- package/app/templates/orders/index.js +1 -1
- package/app/templates/products/index/category/edit.js +1 -1
- package/app/templates/products/index/category/new.js +1 -1
- package/app/templates/products/index/category.js +1 -1
- package/app/templates/products/index/index/edit.js +1 -1
- package/app/templates/products/index/index.js +1 -1
- package/app/templates/products/index.js +1 -1
- package/app/templates/settings/api.js +1 -1
- package/app/templates/settings/gateways.js +1 -1
- package/app/templates/settings/index.js +1 -1
- package/app/templates/settings/locations.js +1 -1
- package/app/templates/settings/notifications.js +1 -1
- package/app/templates/settings.js +1 -1
- package/app/utils/create-shareable-link.js +1 -0
- package/app/utils/get-gateway-schemas.js +1 -1
- package/app/utils/get-notification-schemas.js +1 -1
- package/composer.json +88 -0
- package/extension.json +10 -0
- package/package.json +6 -7
- 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
|
+
}
|