@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.
Files changed (333) hide show
  1. package/.php-cs-fixer.php +29 -0
  2. package/LICENSE.md +16 -4
  3. package/README.md +106 -13
  4. package/addon/components/modals/add-store-to-category.hbs +5 -0
  5. package/addon/components/modals/create-network-category.hbs +28 -28
  6. package/addon/components/modals/share-network.hbs +3 -3
  7. package/addon/components/modals/store-details.hbs +22 -0
  8. package/addon/components/modals/store-form.hbs +22 -0
  9. package/addon/components/modals/store-form.js +21 -0
  10. package/addon/components/network-category-picker.hbs +40 -0
  11. package/addon/components/network-category-picker.js +83 -0
  12. package/addon/components/store-selector.hbs +4 -2
  13. package/addon/components/widget/orders.js +22 -17
  14. package/addon/controllers/networks/index/network/index.js +133 -9
  15. package/addon/controllers/networks/index/network/stores.js +422 -153
  16. package/addon/controllers/networks/index.js +66 -4
  17. package/addon/controllers/products/index/category.js +1 -1
  18. package/addon/controllers/settings/index.js +14 -7
  19. package/addon/controllers/settings/notifications.js +1 -1
  20. package/addon/models/store.js +44 -7
  21. package/addon/routes/networks/index/network/customers.js +4 -1
  22. package/addon/routes/networks/index/network/orders.js +4 -1
  23. package/addon/routes/networks/index/network/stores.js +19 -15
  24. package/addon/serializers/store.js +1 -0
  25. package/addon/templates/customers/index.hbs +0 -11
  26. package/addon/templates/networks/index/network/index.hbs +1 -1
  27. package/addon/templates/networks/index/network/stores.hbs +8 -163
  28. package/addon/templates/networks/index/network.hbs +2 -3
  29. package/addon/utils/create-shareable-link.js +21 -0
  30. package/app/adapters/addon-category.js +1 -1
  31. package/app/adapters/gateway.js +1 -1
  32. package/app/adapters/network.js +1 -1
  33. package/app/adapters/notification-channel.js +1 -1
  34. package/app/adapters/product-addon-category.js +1 -1
  35. package/app/adapters/product-addon.js +1 -1
  36. package/app/adapters/product-hour.js +1 -1
  37. package/app/adapters/product-store-location.js +1 -1
  38. package/app/adapters/product-variant-option.js +1 -1
  39. package/app/adapters/product-variant.js +1 -1
  40. package/app/adapters/product.js +1 -1
  41. package/app/adapters/store-hour.js +1 -1
  42. package/app/adapters/store-location.js +1 -1
  43. package/app/adapters/store.js +1 -1
  44. package/app/adapters/storefront.js +1 -1
  45. package/app/components/file-record.js +1 -1
  46. package/app/components/modals/add-store-hours.js +1 -1
  47. package/app/components/modals/add-store-to-category.js +1 -0
  48. package/app/components/modals/add-stores-to-network.js +1 -1
  49. package/app/components/modals/assign-driver.js +1 -1
  50. package/app/components/modals/create-first-store.js +1 -1
  51. package/app/components/modals/create-gateway.js +1 -1
  52. package/app/components/modals/create-network-category.js +1 -1
  53. package/app/components/modals/create-network.js +1 -1
  54. package/app/components/modals/create-new-variant.js +1 -1
  55. package/app/components/modals/create-notification-channel.js +1 -1
  56. package/app/components/modals/create-product-category.js +1 -1
  57. package/app/components/modals/create-store.js +1 -1
  58. package/app/components/modals/edit-network.js +1 -1
  59. package/app/components/modals/import-products.js +1 -1
  60. package/app/components/modals/incoming-order.js +1 -1
  61. package/app/components/modals/manage-addons.js +1 -1
  62. package/app/components/modals/order-ready-assign-driver.js +1 -1
  63. package/app/components/modals/select-addon-category.js +1 -1
  64. package/app/components/modals/share-network.js +1 -1
  65. package/app/components/modals/store-details.js +1 -0
  66. package/app/components/modals/store-form.js +1 -0
  67. package/app/components/modals/store-location-form.js +1 -1
  68. package/app/components/network-category-picker.js +1 -0
  69. package/app/components/order-card.js +1 -1
  70. package/app/components/schedule-manager.js +1 -1
  71. package/app/components/settings-container.js +1 -1
  72. package/app/components/store-selector.js +1 -1
  73. package/app/components/widget/customers.js +1 -1
  74. package/app/components/widget/orders.js +1 -1
  75. package/app/components/widget/storefront-metrics.js +1 -1
  76. package/app/controllers/application.js +1 -1
  77. package/app/controllers/customers/index.js +1 -1
  78. package/app/controllers/home.js +1 -1
  79. package/app/controllers/networks/index/network/customers.js +1 -1
  80. package/app/controllers/networks/index/network/index.js +1 -1
  81. package/app/controllers/networks/index/network/orders.js +1 -1
  82. package/app/controllers/networks/index/network/stores.js +1 -1
  83. package/app/controllers/networks/index/network.js +1 -1
  84. package/app/controllers/networks/index.js +1 -1
  85. package/app/controllers/orders/index.js +1 -1
  86. package/app/controllers/products/index/category/edit.js +1 -1
  87. package/app/controllers/products/index/category/new.js +1 -1
  88. package/app/controllers/products/index/category.js +1 -1
  89. package/app/controllers/products/index/index/edit.js +1 -1
  90. package/app/controllers/products/index/index.js +1 -1
  91. package/app/controllers/products/index.js +1 -1
  92. package/app/controllers/settings/api.js +1 -1
  93. package/app/controllers/settings/gateways.js +1 -1
  94. package/app/controllers/settings/index.js +1 -1
  95. package/app/controllers/settings/locations.js +1 -1
  96. package/app/controllers/settings/notifications.js +1 -1
  97. package/app/helpers/get-tip-amount.js +1 -1
  98. package/app/models/addon-category.js +1 -1
  99. package/app/models/gateway.js +1 -1
  100. package/app/models/network.js +1 -1
  101. package/app/models/notification-channel.js +1 -1
  102. package/app/models/product-addon-category.js +1 -1
  103. package/app/models/product-addon.js +1 -1
  104. package/app/models/product-hour.js +1 -1
  105. package/app/models/product-store-location.js +1 -1
  106. package/app/models/product-variant-option.js +1 -1
  107. package/app/models/product-variant.js +1 -1
  108. package/app/models/product.js +1 -1
  109. package/app/models/store-hour.js +1 -1
  110. package/app/models/store-location.js +1 -1
  111. package/app/models/store.js +1 -1
  112. package/app/routes/application.js +1 -1
  113. package/app/routes/customers/index/edit.js +1 -1
  114. package/app/routes/customers/index.js +1 -1
  115. package/app/routes/home.js +1 -1
  116. package/app/routes/networks/index/network/customers.js +1 -1
  117. package/app/routes/networks/index/network/index.js +1 -1
  118. package/app/routes/networks/index/network/orders.js +1 -1
  119. package/app/routes/networks/index/network/stores.js +1 -1
  120. package/app/routes/networks/index/network.js +1 -1
  121. package/app/routes/networks/index.js +1 -1
  122. package/app/routes/orders/index/edit.js +1 -1
  123. package/app/routes/orders/index/new.js +1 -1
  124. package/app/routes/orders/index/view.js +1 -1
  125. package/app/routes/orders/index.js +1 -1
  126. package/app/routes/products/index/category/edit.js +1 -1
  127. package/app/routes/products/index/category/new.js +1 -1
  128. package/app/routes/products/index/category.js +1 -1
  129. package/app/routes/products/index/index/edit.js +1 -1
  130. package/app/routes/products/index/index.js +1 -1
  131. package/app/routes/products/index.js +1 -1
  132. package/app/routes/settings/api.js +1 -1
  133. package/app/routes/settings/gateways.js +1 -1
  134. package/app/routes/settings/index.js +1 -1
  135. package/app/routes/settings/locations.js +1 -1
  136. package/app/routes/settings/notifications.js +1 -1
  137. package/app/serializers/addon-category.js +1 -1
  138. package/app/serializers/network.js +1 -1
  139. package/app/serializers/notification-channel.js +1 -1
  140. package/app/serializers/product-addon-category.js +1 -1
  141. package/app/serializers/product-variant.js +1 -1
  142. package/app/serializers/product.js +1 -1
  143. package/app/serializers/store-location.js +1 -1
  144. package/app/serializers/store.js +1 -1
  145. package/app/services/storefront.js +1 -1
  146. package/app/templates/customers/index/edit.js +1 -1
  147. package/app/templates/customers/index.js +1 -1
  148. package/app/templates/home.js +1 -1
  149. package/app/templates/networks/index/network/customers.js +1 -1
  150. package/app/templates/networks/index/network/index.js +1 -1
  151. package/app/templates/networks/index/network/orders.js +1 -1
  152. package/app/templates/networks/index/network/stores.js +1 -1
  153. package/app/templates/networks/index/network.js +1 -1
  154. package/app/templates/networks/index.js +1 -1
  155. package/app/templates/orders/index/edit.js +1 -1
  156. package/app/templates/orders/index/new.js +1 -1
  157. package/app/templates/orders/index/view.js +1 -1
  158. package/app/templates/orders/index.js +1 -1
  159. package/app/templates/products/index/category/edit.js +1 -1
  160. package/app/templates/products/index/category/new.js +1 -1
  161. package/app/templates/products/index/category.js +1 -1
  162. package/app/templates/products/index/index/edit.js +1 -1
  163. package/app/templates/products/index/index.js +1 -1
  164. package/app/templates/products/index.js +1 -1
  165. package/app/templates/settings/api.js +1 -1
  166. package/app/templates/settings/gateways.js +1 -1
  167. package/app/templates/settings/index.js +1 -1
  168. package/app/templates/settings/locations.js +1 -1
  169. package/app/templates/settings/notifications.js +1 -1
  170. package/app/templates/settings.js +1 -1
  171. package/app/utils/create-shareable-link.js +1 -0
  172. package/app/utils/get-gateway-schemas.js +1 -1
  173. package/app/utils/get-notification-schemas.js +1 -1
  174. package/composer.json +88 -0
  175. package/extension.json +10 -0
  176. package/package.json +6 -7
  177. package/phpstan.neon.dist +8 -0
  178. package/phpunit.xml.dist +16 -0
  179. package/server/.gitattributes +14 -0
  180. package/server/README.md +40 -0
  181. package/server/config/api.php +101 -0
  182. package/server/config/database.connections.php +57 -0
  183. package/server/config/storefront.php +19 -0
  184. package/server/config/twilio-notification-channel.php +36 -0
  185. package/server/migrations/2023_05_03_025307_create_carts_table.php +44 -0
  186. package/server/migrations/2023_05_03_025307_create_checkouts_table.php +51 -0
  187. package/server/migrations/2023_05_03_025307_create_gateways_table.php +48 -0
  188. package/server/migrations/2023_05_03_025307_create_network_stores_table.php +36 -0
  189. package/server/migrations/2023_05_03_025307_create_networks_table.php +56 -0
  190. package/server/migrations/2023_05_03_025307_create_notification_channels_table.php +43 -0
  191. package/server/migrations/2023_05_03_025307_create_payment_methods_table.php +44 -0
  192. package/server/migrations/2023_05_03_025307_create_product_addon_categories_table.php +38 -0
  193. package/server/migrations/2023_05_03_025307_create_product_addons_table.php +43 -0
  194. package/server/migrations/2023_05_03_025307_create_product_hours_table.php +37 -0
  195. package/server/migrations/2023_05_03_025307_create_product_store_locations_table.php +34 -0
  196. package/server/migrations/2023_05_03_025307_create_product_variant_options_table.php +40 -0
  197. package/server/migrations/2023_05_03_025307_create_product_variants_table.php +44 -0
  198. package/server/migrations/2023_05_03_025307_create_products_table.php +59 -0
  199. package/server/migrations/2023_05_03_025307_create_reviews_table.php +41 -0
  200. package/server/migrations/2023_05_03_025307_create_store_hours_table.php +37 -0
  201. package/server/migrations/2023_05_03_025307_create_store_locations_table.php +38 -0
  202. package/server/migrations/2023_05_03_025307_create_stores_table.php +57 -0
  203. package/server/migrations/2023_05_03_025307_create_votes_table.php +39 -0
  204. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_carts_table.php +40 -0
  205. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_checkouts_table.php +48 -0
  206. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_gateways_table.php +40 -0
  207. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_network_stores_table.php +40 -0
  208. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_networks_table.php +42 -0
  209. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_notification_channels_table.php +40 -0
  210. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_payment_methods_table.php +40 -0
  211. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_addon_categories_table.php +38 -0
  212. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_addons_table.php +38 -0
  213. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_hours_table.php +32 -0
  214. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_store_locations_table.php +34 -0
  215. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_variant_options_table.php +32 -0
  216. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_variants_table.php +32 -0
  217. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_products_table.php +44 -0
  218. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_reviews_table.php +38 -0
  219. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_store_hours_table.php +32 -0
  220. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_store_locations_table.php +40 -0
  221. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_stores_table.php +42 -0
  222. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_votes_table.php +38 -0
  223. package/server/src/Auth/Schemas/Storefront.php +95 -0
  224. package/server/src/Console/Commands/NotifyStorefrontOrderNearby.php +86 -0
  225. package/server/src/Expansions/EntityExpansion.php +44 -0
  226. package/server/src/Http/Controllers/ActionController.php +119 -0
  227. package/server/src/Http/Controllers/AddonCategoryController.php +13 -0
  228. package/server/src/Http/Controllers/CustomerController.php +13 -0
  229. package/server/src/Http/Controllers/GatewayController.php +13 -0
  230. package/server/src/Http/Controllers/MetricsController.php +71 -0
  231. package/server/src/Http/Controllers/NetworkController.php +170 -0
  232. package/server/src/Http/Controllers/NotificationChannelController.php +13 -0
  233. package/server/src/Http/Controllers/OrderController.php +154 -0
  234. package/server/src/Http/Controllers/ProductAddonCategoryController.php +14 -0
  235. package/server/src/Http/Controllers/ProductAddonController.php +14 -0
  236. package/server/src/Http/Controllers/ProductController.php +123 -0
  237. package/server/src/Http/Controllers/ProductHourController.php +13 -0
  238. package/server/src/Http/Controllers/ProductVariantController.php +13 -0
  239. package/server/src/Http/Controllers/ProductVariantOptionController.php +13 -0
  240. package/server/src/Http/Controllers/ReviewController.php +13 -0
  241. package/server/src/Http/Controllers/StoreController.php +26 -0
  242. package/server/src/Http/Controllers/StoreHourController.php +13 -0
  243. package/server/src/Http/Controllers/StoreLocationController.php +13 -0
  244. package/server/src/Http/Controllers/StorefrontController.php +15 -0
  245. package/server/src/Http/Controllers/VoteController.php +13 -0
  246. package/server/src/Http/Controllers/v1/CartController.php +161 -0
  247. package/server/src/Http/Controllers/v1/CategoryController.php +138 -0
  248. package/server/src/Http/Controllers/v1/CheckoutController.php +957 -0
  249. package/server/src/Http/Controllers/v1/CustomerController.php +482 -0
  250. package/server/src/Http/Controllers/v1/GatewayControllerController.php +11 -0
  251. package/server/src/Http/Controllers/v1/NetworkController.php +281 -0
  252. package/server/src/Http/Controllers/v1/PaymentMethodController.php +11 -0
  253. package/server/src/Http/Controllers/v1/ProductController.php +94 -0
  254. package/server/src/Http/Controllers/v1/ReviewController.php +270 -0
  255. package/server/src/Http/Controllers/v1/ServiceQuoteController.php +402 -0
  256. package/server/src/Http/Controllers/v1/StoreController.php +176 -0
  257. package/server/src/Http/Filter/AddonCategoryFilter.php +19 -0
  258. package/server/src/Http/Filter/CustomerFilter.php +18 -0
  259. package/server/src/Http/Filter/GatewayFilter.php +13 -0
  260. package/server/src/Http/Filter/NetworkFilter.php +18 -0
  261. package/server/src/Http/Filter/NotificationChannelFilter.php +13 -0
  262. package/server/src/Http/Filter/OrderFilter.php +46 -0
  263. package/server/src/Http/Filter/ProductFilter.php +28 -0
  264. package/server/src/Http/Filter/StoreFilter.php +42 -0
  265. package/server/src/Http/Filter/StoreLocationFilter.php +23 -0
  266. package/server/src/Http/Middleware/SetStorefrontSession.php +130 -0
  267. package/server/src/Http/Requests/AddStoreToNetworkCategory.php +45 -0
  268. package/server/src/Http/Requests/CaptureOrderRequest.php +30 -0
  269. package/server/src/Http/Requests/CreateCustomerRequest.php +44 -0
  270. package/server/src/Http/Requests/CreateReviewRequest.php +34 -0
  271. package/server/src/Http/Requests/GetServiceQuoteFromCart.php +40 -0
  272. package/server/src/Http/Requests/InitializeCheckoutRequest.php +38 -0
  273. package/server/src/Http/Requests/NetworkActionRequest.php +43 -0
  274. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +31 -0
  275. package/server/src/Http/Resources/Cart.php +31 -0
  276. package/server/src/Http/Resources/Category.php +48 -0
  277. package/server/src/Http/Resources/Customer.php +36 -0
  278. package/server/src/Http/Resources/Gateway.php +32 -0
  279. package/server/src/Http/Resources/Media.php +29 -0
  280. package/server/src/Http/Resources/Network.php +48 -0
  281. package/server/src/Http/Resources/Product.php +209 -0
  282. package/server/src/Http/Resources/Review.php +45 -0
  283. package/server/src/Http/Resources/ReviewCustomer.php +60 -0
  284. package/server/src/Http/Resources/Store.php +76 -0
  285. package/server/src/Http/Resources/StoreHour.php +29 -0
  286. package/server/src/Http/Resources/StoreLocation.php +33 -0
  287. package/server/src/Imports/ProductsImport.php +20 -0
  288. package/server/src/Jobs/DownloadProductImageUrl.php +60 -0
  289. package/server/src/Listeners/HandleOrderCompleted.php +31 -0
  290. package/server/src/Listeners/HandleOrderDispatched.php +34 -0
  291. package/server/src/Listeners/HandleOrderDriverAssigned.php +37 -0
  292. package/server/src/Listeners/HandleOrderStarted.php +27 -0
  293. package/server/src/Mail/StorefrontNetworkInvite.php +48 -0
  294. package/server/src/Models/AddonCategory.php +30 -0
  295. package/server/src/Models/Cart.php +691 -0
  296. package/server/src/Models/Checkout.php +166 -0
  297. package/server/src/Models/Customer.php +88 -0
  298. package/server/src/Models/Gateway.php +165 -0
  299. package/server/src/Models/Network.php +300 -0
  300. package/server/src/Models/NetworkStore.php +86 -0
  301. package/server/src/Models/NotificationChannel.php +147 -0
  302. package/server/src/Models/PaymentMethod.php +99 -0
  303. package/server/src/Models/Product.php +315 -0
  304. package/server/src/Models/ProductAddon.php +128 -0
  305. package/server/src/Models/ProductAddonCategory.php +90 -0
  306. package/server/src/Models/ProductHour.php +59 -0
  307. package/server/src/Models/ProductStoreLocation.php +77 -0
  308. package/server/src/Models/ProductVariant.php +125 -0
  309. package/server/src/Models/ProductVariantOption.php +86 -0
  310. package/server/src/Models/Review.php +127 -0
  311. package/server/src/Models/Store.php +478 -0
  312. package/server/src/Models/StoreHour.php +59 -0
  313. package/server/src/Models/StoreLocation.php +126 -0
  314. package/server/src/Models/StorefrontModel.php +22 -0
  315. package/server/src/Models/Vote.php +84 -0
  316. package/server/src/Notifications/StorefrontOrderCanceled.php +196 -0
  317. package/server/src/Notifications/StorefrontOrderCompleted.php +201 -0
  318. package/server/src/Notifications/StorefrontOrderCreated.php +157 -0
  319. package/server/src/Notifications/StorefrontOrderDriverAssigned.php +200 -0
  320. package/server/src/Notifications/StorefrontOrderEnroute.php +199 -0
  321. package/server/src/Notifications/StorefrontOrderNearby.php +201 -0
  322. package/server/src/Notifications/StorefrontOrderPreparing.php +202 -0
  323. package/server/src/Notifications/StorefrontOrderReadyForPickup.php +202 -0
  324. package/server/src/Observers/NetworkObserver.php +40 -0
  325. package/server/src/Observers/ProductObserver.php +118 -0
  326. package/server/src/Providers/EventServiceProvider.php +23 -0
  327. package/server/src/Providers/StorefrontServiceProvider.php +103 -0
  328. package/server/src/Support/Metrics.php +193 -0
  329. package/server/src/Support/OrderConfig.php +13 -0
  330. package/server/src/Support/QPay.php +208 -0
  331. package/server/src/Support/Storefront.php +201 -0
  332. package/server/src/routes.php +180 -0
  333. 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
+ }