@fleetbase/storefront-engine 0.3.16 → 0.3.18

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 (31) hide show
  1. package/addon/components/modals/manage-addons.js +6 -2
  2. package/addon/controllers/products/index/category/new.js +2 -1
  3. package/addon/controllers/products/index/index.js +0 -23
  4. package/addon/controllers/settings/gateways.js +14 -18
  5. package/addon/models/product-addon-category.js +3 -0
  6. package/addon/styles/storefront-engine.css +7 -0
  7. package/addon/templates/products/index/category/new.hbs +4 -1
  8. package/addon/templates/settings/gateways.hbs +1 -1
  9. package/addon/templates/settings/index.hbs +2 -2
  10. package/composer.json +1 -7
  11. package/extension.json +1 -1
  12. package/package.json +4 -4
  13. package/server/src/Http/Controllers/ProductController.php +2 -0
  14. package/server/src/Http/Controllers/v1/CheckoutController.php +331 -33
  15. package/server/src/Http/Controllers/v1/CustomerController.php +77 -0
  16. package/server/src/Http/Controllers/v1/StoreController.php +35 -5
  17. package/server/src/Http/Requests/CreateStripeSetupIntentRequest.php +31 -0
  18. package/server/src/Http/Requests/CustomerRequest.php +31 -0
  19. package/server/src/Http/Resources/Cart.php +18 -1
  20. package/server/src/Http/Resources/Customer.php +19 -14
  21. package/server/src/Http/Resources/Store.php +5 -1
  22. package/server/src/Models/AddonCategory.php +14 -16
  23. package/server/src/Models/Cart.php +10 -5
  24. package/server/src/Models/Customer.php +2 -2
  25. package/server/src/Models/Gateway.php +9 -4
  26. package/server/src/Models/Product.php +9 -10
  27. package/server/src/Models/ProductAddonCategory.php +2 -0
  28. package/server/src/Support/QPay.php +35 -1
  29. package/server/src/Support/StripeUtils.php +38 -0
  30. package/server/src/routes.php +19 -0
  31. package/translations/en-us.yaml +2 -1
@@ -0,0 +1,31 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+ use Fleetbase\Storefront\Rules\CustomerExists;
7
+
8
+ class CreateStripeSetupIntentRequest extends FleetbaseRequest
9
+ {
10
+ /**
11
+ * Determine if the user is authorized to make this request.
12
+ *
13
+ * @return bool
14
+ */
15
+ public function authorize()
16
+ {
17
+ return session('storefront_key');
18
+ }
19
+
20
+ /**
21
+ * Get the validation rules that apply to the request.
22
+ *
23
+ * @return array
24
+ */
25
+ public function rules()
26
+ {
27
+ return [
28
+ 'customer' => ['required', new CustomerExists()],
29
+ ];
30
+ }
31
+ }
@@ -0,0 +1,31 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+ use Fleetbase\Storefront\Rules\CustomerExists;
7
+
8
+ class CustomerRequest extends FleetbaseRequest
9
+ {
10
+ /**
11
+ * Determine if the user is authorized to make this request.
12
+ *
13
+ * @return bool
14
+ */
15
+ public function authorize()
16
+ {
17
+ return session('storefront_key');
18
+ }
19
+
20
+ /**
21
+ * Get the validation rules that apply to the request.
22
+ *
23
+ * @return array
24
+ */
25
+ public function rules()
26
+ {
27
+ return [
28
+ 'customer' => ['required', new CustomerExists()],
29
+ ];
30
+ }
31
+ }
@@ -3,6 +3,7 @@
3
3
  namespace Fleetbase\Storefront\Http\Resources;
4
4
 
5
5
  use Fleetbase\Http\Resources\FleetbaseResource;
6
+ use Fleetbase\Storefront\Models\Product;
6
7
  use Fleetbase\Support\Http;
7
8
 
8
9
  class Cart extends FleetbaseResource
@@ -29,7 +30,7 @@ class Cart extends FleetbaseResource
29
30
  'subtotal' => $this->subtotal,
30
31
  'total_items' => $this->total_items,
31
32
  'total_unique_items' => $this->total_unique_items,
32
- 'items' => $this->items ?? [],
33
+ 'items' => $this->getCartItems(),
33
34
  'events' => $this->events ?? [],
34
35
  'discount_code' => $this->discount_code,
35
36
  'expires_at' => $this->expires_at,
@@ -37,4 +38,20 @@ class Cart extends FleetbaseResource
37
38
  'updated_at' => $this->updated_at,
38
39
  ];
39
40
  }
41
+
42
+ public function getCartItems()
43
+ {
44
+ $items = $this->items ?? [];
45
+
46
+ return array_map(function ($cartItem) {
47
+ $product = Product::select(['public_id', 'primary_image_uuid', 'name', 'description'])->where('public_id', data_get($cartItem, 'product_id'))->first();
48
+ if ($product) {
49
+ data_set($cartItem, 'name', $product->name);
50
+ data_set($cartItem, 'description', $product->description);
51
+ data_set($cartItem, 'product_image_url', $product->primary_image_url);
52
+ }
53
+
54
+ return $cartItem;
55
+ }, $items);
56
+ }
40
57
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  namespace Fleetbase\Storefront\Http\Resources;
4
4
 
5
+ use Fleetbase\FleetOps\Http\Resources\v1\Place;
5
6
  use Fleetbase\Http\Resources\FleetbaseResource;
6
7
  use Fleetbase\Support\Http;
7
8
  use Illuminate\Support\Str;
@@ -17,21 +18,25 @@ class Customer extends FleetbaseResource
17
18
  */
18
19
  public function toArray($request)
19
20
  {
21
+ $this->loadMissing(['place', 'places']);
22
+
20
23
  return [
21
- 'id' => $this->when(Http::isInternalRequest(), $this->id, Str::replaceFirst('contact', 'customer', $this->public_id)),
22
- 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
23
- 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
24
- 'internal_id' => $this->internal_id,
25
- 'name' => $this->name,
26
- 'photo_url' => $this->photo_url,
27
- 'email' => $this->email,
28
- 'phone' => $this->phone,
29
- 'address' => data_get($this, 'address.address'),
30
- 'addresses' => $this->addresses,
31
- 'token' => $this->when($this->token, $this->token),
32
- 'slug' => $this->slug,
33
- 'created_at' => $this->created_at,
34
- 'updated_at' => $this->updated_at,
24
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, Str::replaceFirst('contact', 'customer', $this->public_id)),
25
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
26
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
27
+ 'address_id' => $this->place ? $this->place->public_id : null,
28
+ 'internal_id' => $this->internal_id,
29
+ 'name' => $this->name,
30
+ 'photo_url' => $this->photo_url,
31
+ 'email' => $this->email,
32
+ 'phone' => $this->phone,
33
+ 'address' => data_get($this, 'place.address'),
34
+ 'addresses' => $this->whenLoaded('places', Place::collection($this->places)),
35
+ 'token' => $this->when($this->token, $this->token),
36
+ 'meta' => $this->meta ?? [],
37
+ 'slug' => $this->slug,
38
+ 'created_at' => $this->created_at,
39
+ 'updated_at' => $this->updated_at,
35
40
  ];
36
41
  }
37
42
  }
@@ -4,6 +4,7 @@ namespace Fleetbase\Storefront\Http\Resources;
4
4
 
5
5
  use Fleetbase\Http\Resources\FleetbaseResource;
6
6
  use Fleetbase\Support\Http;
7
+ use Fleetbase\Support\Utils;
7
8
 
8
9
  class Store extends FleetbaseResource
9
10
  {
@@ -16,6 +17,8 @@ class Store extends FleetbaseResource
16
17
  */
17
18
  public function toArray($request)
18
19
  {
20
+ $currency = $this->currency ?? 'USD';
21
+
19
22
  return [
20
23
  'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
21
24
  'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
@@ -35,7 +38,8 @@ class Store extends FleetbaseResource
35
38
  'email' => $this->email,
36
39
  'phone' => $this->phone,
37
40
  'tags' => $this->tags ?? [],
38
- 'currency' => $this->currency ?? 'USD',
41
+ 'currency' => $currency,
42
+ 'country' => Utils::getCountryCodeByCurrency($currency, 'US'),
39
43
  'options' => $this->formatOptions($this->options),
40
44
  'logo_url' => $this->logo_url,
41
45
  'backdrop_url' => $this->backdrop_url,
@@ -2,8 +2,8 @@
2
2
 
3
3
  namespace Fleetbase\Storefront\Models;
4
4
 
5
+ use Fleetbase\Casts\Money as MoneyCast;
5
6
  use Fleetbase\Models\Category;
6
- use Illuminate\Support\Arr;
7
7
  use Illuminate\Support\Str;
8
8
 
9
9
  class AddonCategory extends Category
@@ -34,29 +34,27 @@ class AddonCategory extends Category
34
34
  // get uuid if set
35
35
  $id = data_get($addon, 'uuid');
36
36
 
37
- // make sure the cateogry is set to this current
38
- data_set($addon, 'category_uuid', $this->uuid);
39
-
40
- // make sure sale price is 0 if null
41
- if (data_get($addon, 'sale_price') === null) {
42
- data_set($addon, 'sale_price', 0);
43
- }
37
+ // create an upsertable array
38
+ $upsertableProductAddon = [
39
+ 'category_uuid' => $this->uuid,
40
+ 'name' => data_get($addon, 'name'),
41
+ 'description' => data_get($addon, 'description'),
42
+ 'translations' => data_get($addon, 'translations', []),
43
+ 'price' => MoneyCast::apply($addon['price'] ?? 0),
44
+ 'sale_price' => MoneyCast::apply($addon['sale_price'] ?? 0),
45
+ 'is_on_sale' => data_get($addon, 'is_on_sale'),
46
+ ];
44
47
 
45
48
  // update product addon category
46
49
  if (Str::isUuid($id)) {
47
- ProductAddon::where('uuid', $id)->update(Arr::except($addon, ['uuid', 'created_at', 'updated_at']));
50
+ ProductAddon::where('uuid', $id)->update($upsertableProductAddon);
48
51
  continue;
49
52
  }
50
53
 
51
54
  // create new product addon category
52
55
  ProductAddon::create([
53
- 'category_uuid' => $this->uuid,
54
- 'name' => data_get($addon, 'name'),
55
- 'description' => data_get($addon, 'description'),
56
- 'translations' => data_get($addon, 'translations', []),
57
- 'price' => data_get($addon, 'price'),
58
- 'sale_price' => data_get($addon, 'sale_price'),
59
- 'is_on_sale' => data_get($addon, 'is_on_sale'),
56
+ ...$upsertableProductAddon,
57
+ 'created_by_uuid' => session('user'),
60
58
  ]);
61
59
  }
62
60
 
@@ -5,6 +5,7 @@ namespace Fleetbase\Storefront\Models;
5
5
  use Fleetbase\FleetOps\Models\Contact;
6
6
  use Fleetbase\FleetOps\Support\Utils;
7
7
  use Fleetbase\Models\Company;
8
+ use Fleetbase\Models\User;
8
9
  use Fleetbase\Traits\Expirable;
9
10
  use Fleetbase\Traits\HasApiModelBehavior;
10
11
  use Fleetbase\Traits\HasPublicId;
@@ -294,11 +295,10 @@ class Cart extends StorefrontModel
294
295
  /**
295
296
  * Adds an item to cart.
296
297
  *
297
- * @param \Fleetbase\Models\Storefront\Product $product
298
- * @param int $quantity
299
- * @param array $variants
300
- * @param array $addons
301
- * @param string $createdAt
298
+ * @param int $quantity
299
+ * @param array $variants
300
+ * @param array $addons
301
+ * @param string $createdAt
302
302
  */
303
303
  public function addItem(Product $product, $quantity = 1, $variants = [], $addons = [], $storeLocationId = null, $scheduledAt = null, $createdAt = null)
304
304
  {
@@ -673,4 +673,9 @@ class Cart extends StorefrontModel
673
673
  {
674
674
  return Product::select(['uuid', 'store_uuid', 'public_id', 'name', 'description', 'price', 'currency', 'sale_price', 'is_on_sale'])->where(['public_id' => $id])->with([])->first();
675
675
  }
676
+
677
+ public function getCurrency(?string $fallbackCurrency = null): ?string
678
+ {
679
+ return $this->currency ?? session('storefront_currency', $fallbackCurrency);
680
+ }
676
681
  }
@@ -75,9 +75,9 @@ class Customer extends Contact
75
75
  *
76
76
  * @param string $publicId The public ID of the customer to find
77
77
  *
78
- * @return static|null The customer with the given public ID, or null if none was found
78
+ * @return Customer|null The customer with the given public ID, or null if none was found
79
79
  */
80
- public static function findFromCustomerId($publicId)
80
+ public static function findFromCustomerId($publicId): self
81
81
  {
82
82
  if (Str::startsWith($publicId, 'customer')) {
83
83
  $publicId = Str::replaceFirst('customer', 'contact', $publicId);
@@ -136,14 +136,19 @@ class Gateway extends StorefrontModel
136
136
  return (object) $sortedConfig;
137
137
  }
138
138
 
139
- public function getIsStripeGatewayAttribute()
139
+ public function getIsStripeGatewayAttribute(): bool
140
140
  {
141
- return $this->type === 'stripe';
141
+ return $this->isGateway('stripe');
142
142
  }
143
143
 
144
- public function getIsQpayGatewayAttribute()
144
+ public function getIsQPayGatewayAttribute(): bool
145
145
  {
146
- return $this->type === 'qpay';
146
+ return $this->isGateway('qpay');
147
+ }
148
+
149
+ public function isGateway(string $type): bool
150
+ {
151
+ return strtolower($this->type) === strtolower($type);
147
152
  }
148
153
 
149
154
  /**
@@ -305,23 +305,22 @@ class Product extends StorefrontModel
305
305
  // get uuid if set
306
306
  $id = data_get($addonCategory, 'uuid');
307
307
 
308
- // Make sure product is set
309
- data_set($addonCategory, 'product_uuid', $this->uuid);
308
+ $upsertableAddonCategory = [
309
+ 'product_uuid' => $this->uuid,
310
+ 'category_uuid' => data_get($addonCategory, 'category_uuid'),
311
+ 'excluded_addons' => data_get($addonCategory, 'excluded_addons'),
312
+ 'max_selectable' => data_get($addonCategory, 'max_selectable'),
313
+ 'is_required' => data_get($addonCategory, 'is_required'),
314
+ ];
310
315
 
311
316
  // update product addon category
312
317
  if (Str::isUuid($id)) {
313
- ProductAddonCategory::where('uuid', $id)->update(Arr::except($addonCategory, ['uuid', 'created_at', 'updated_at', 'name', 'category']));
318
+ ProductAddonCategory::where('uuid', $id)->update($upsertableAddonCategory);
314
319
  continue;
315
320
  }
316
321
 
317
322
  // create new product addon category
318
- ProductAddonCategory::create([
319
- 'product_uuid' => $this->uuid,
320
- 'category_uuid' => data_get($addonCategory, 'category_uuid'),
321
- 'excluded_addons' => data_get($addonCategory, 'excluded_addons'),
322
- 'max_selectable' => data_get($addonCategory, 'max_selectable'),
323
- 'is_required' => data_get($addonCategory, 'is_required'),
324
- ]);
323
+ ProductAddonCategory::create($upsertableAddonCategory);
325
324
  }
326
325
 
327
326
  return $this;
@@ -35,6 +35,7 @@ class ProductAddonCategory extends StorefrontModel
35
35
  'category_uuid',
36
36
  'excluded_addons',
37
37
  'max_selectable',
38
+ 'is_required',
38
39
  ];
39
40
 
40
41
  /**
@@ -44,6 +45,7 @@ class ProductAddonCategory extends StorefrontModel
44
45
  */
45
46
  protected $casts = [
46
47
  'excluded_addons' => Json::class,
48
+ 'is_required' => 'boolean',
47
49
  ];
48
50
 
49
51
  /**
@@ -2,7 +2,10 @@
2
2
 
3
3
  namespace Fleetbase\Storefront\Support;
4
4
 
5
+ use Fleetbase\Storefront\Models\Checkout;
6
+ use Fleetbase\Support\Utils;
5
7
  use GuzzleHttp\Client;
8
+ use Illuminate\Support\Str;
6
9
 
7
10
  class QPay
8
11
  {
@@ -14,7 +17,7 @@ class QPay
14
17
 
15
18
  public function __construct(?string $username = null, ?string $password = null, ?string $callbackUrl = null)
16
19
  {
17
- $this->callbackUrl = $callbackUrl;
20
+ $this->callbackUrl = $callbackUrl ?? Utils::apiUrl('storefront/v1/checkouts/process-qpay');
18
21
  $this->requestOptions = [
19
22
  'base_uri' => $this->buildRequestUrl(),
20
23
  'auth' => [$username, $password],
@@ -37,6 +40,13 @@ class QPay
37
40
  return $this->client;
38
41
  }
39
42
 
43
+ public function setCallback(string $url)
44
+ {
45
+ $this->callbackUrl = $url;
46
+
47
+ return $this;
48
+ }
49
+
40
50
  public function setNamespace(string $namespace): ?QPay
41
51
  {
42
52
  $this->namespace = $namespace;
@@ -163,6 +173,11 @@ class QPay
163
173
  return $this->post('invoice', $params, $options);
164
174
  }
165
175
 
176
+ public function paymentGet(string $paymentId)
177
+ {
178
+ return $this->get('payment/' . $paymentId);
179
+ }
180
+
166
181
  public function paymentCheck(string $invoiceId, $options = [])
167
182
  {
168
183
  $params = [
@@ -207,4 +222,23 @@ class QPay
207
222
 
208
223
  return preg_replace('/[^A-Za-z0-9\-]/', '', $code);
209
224
  }
225
+
226
+ public static function createTestPaymentDataFromCheckout(Checkout $checkout): array
227
+ {
228
+ return [
229
+ 'payment_id' => (string) Str::uuid(),
230
+ 'payment_status' => 'PAID',
231
+ 'payment_fee' => '1.00',
232
+ 'payment_amount' => $checkout->amount,
233
+ 'payment_currency' => $checkout->currency,
234
+ 'payment_date' => now(),
235
+ 'payment_wallet' => 'TEST',
236
+ 'object_type' => 'INVOICE',
237
+ 'object_id' => $checkout->getOption('qpay_invoice_id', (string) Str::uuid()),
238
+ 'next_payment_date' => null,
239
+ 'transaction_type' => 'P2P',
240
+ 'card_transactions' => [],
241
+ 'p2p_transactions' => [],
242
+ ];
243
+ }
210
244
  }
@@ -0,0 +1,38 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Support;
4
+
5
+ use Fleetbase\Storefront\Models\Customer;
6
+ use Illuminate\Support\Facades\Log;
7
+ use Stripe\PaymentMethod;
8
+
9
+ class StripeUtils
10
+ {
11
+ /**
12
+ * Checks if the given customer's saved payment method is valid and attached to them on Stripe.
13
+ * If not, attempts to attach it.
14
+ *
15
+ * @param Customer $customer an instance of your Customer model
16
+ *
17
+ * @return bool true if the payment method is valid and attached, false otherwise
18
+ */
19
+ public static function isCustomerPaymentMethodValid(Customer $customer): bool
20
+ {
21
+ $stripeCustomerId = $customer->getMeta('stripe_id');
22
+ $paymentMethodId = $customer->getMeta('stripe_payment_method_id');
23
+
24
+ if (!$stripeCustomerId || !$paymentMethodId) {
25
+ return false;
26
+ }
27
+
28
+ try {
29
+ $pm = PaymentMethod::retrieve($paymentMethodId);
30
+
31
+ return $pm && $pm->customer === $stripeCustomerId;
32
+ } catch (\Exception $e) {
33
+ Log::error('Error verifying or attaching payment method: ' . $e->getMessage());
34
+
35
+ return false;
36
+ }
37
+ }
38
+ }
@@ -15,6 +15,20 @@ use Illuminate\Support\Facades\Route;
15
15
 
16
16
  Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace('Fleetbase\Storefront\Http\Controllers')->group(
17
17
  function ($router) {
18
+ /*
19
+ |--------------------------------------------------------------------------
20
+ | Public/Callback Receivable Storefront API Routes
21
+ |--------------------------------------------------------------------------
22
+ |
23
+ | End-user API routes, these are routes that the SDK and applications will interface with, and require API credentials.
24
+ */
25
+ Route::group(['prefix' => 'v1', 'namespace' => 'v1'], function ($router) {
26
+ // storefront/v1/checkouts
27
+ $router->group(['prefix' => 'checkouts'], function () use ($router) {
28
+ $router->match(['get', 'post'], 'capture-qpay', 'CheckoutController@captureQpayCallback');
29
+ });
30
+ });
31
+
18
32
  /*
19
33
  |--------------------------------------------------------------------------
20
34
  | Consumable Storefront API Routes
@@ -26,6 +40,7 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
26
40
  ->middleware('storefront.api')
27
41
  ->namespace('v1')
28
42
  ->group(function ($router) {
43
+ $router->get('lookup/{id}', 'StoreController@lookup');
29
44
  $router->get('about', 'StoreController@about');
30
45
  $router->get('locations/{id}', 'StoreController@location');
31
46
  $router->get('locations', 'StoreController@locations');
@@ -40,6 +55,8 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
40
55
  $router->group(['prefix' => 'checkouts'], function () use ($router) {
41
56
  $router->get('before', 'CheckoutController@beforeCheckout');
42
57
  $router->post('capture', 'CheckoutController@captureOrder');
58
+ $router->post('stripe-setup-intent', 'CheckoutController@createStripeSetupIntentForCustomer');
59
+ $router->put('stripe-update-payment-intent', 'CheckoutController@updateStripePaymentIntent');
43
60
  });
44
61
 
45
62
  // storefront/v1/service-quotes
@@ -80,6 +97,8 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
80
97
  $router->post('verify-code', 'CustomerController@verifyCode');
81
98
  $router->post('login', 'CustomerController@login');
82
99
  $router->post('request-creation-code', 'CustomerController@requestCustomerCreationCode');
100
+ $router->post('stripe-ephemeral-key', 'CustomerController@getStripeEphemeralKey');
101
+ $router->post('stripe-setup-intent', 'CustomerController@getStripeSetupIntent');
83
102
  });
84
103
 
85
104
  // hotfix! storefront-app sending customer update to /contacts/ route
@@ -598,7 +598,8 @@ storefront:
598
598
  proof-of-delivery-method: Proof of Delivery Method
599
599
  enable-cash-on-delivery: Enable cash on delivery
600
600
  enable-order-pickup: Enable order pickup
601
- enable-tip: Enable tips
601
+ enable-tips: Enable tips
602
+ enable-delivery-tips: Enable delivery tips
602
603
  enable-integrated-vendors: Enable integrated vendors
603
604
  save-changes: Save Changes
604
605
  locations: