Skip to content

Commit ea0b519

Browse files
Actions (#83)
1 parent fb53d38 commit ea0b519

File tree

8 files changed

+314
-131
lines changed

8 files changed

+314
-131
lines changed

docs/extending/php-apis/actions.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
title: Actions
3+
---
4+
5+
Cargo provides PHP actions for performing cart operations outside of Antlers tags - useful when working with Livewire components, custom controllers, or event listeners.
6+
7+
This page documents the available actions along with their parameters.
8+
9+
## AddToCart
10+
11+
The `AddToCart` action is responsible for adding products to the cart.
12+
13+
It'll validate stock, check for prerequisite products, check if the product already exists in the customer's cart and finally add a line item to the cart.
14+
15+
```php
16+
use DuncanMcClean\Cargo\Cart\Actions\AddToCart;
17+
use DuncanMcClean\Cargo\Facades\Cart;
18+
19+
$cart = Cart::current();
20+
21+
app(AddToCart::class)->handle(
22+
$cart,
23+
$product,
24+
$variant,
25+
$quantity,
26+
$data
27+
);
28+
29+
$cart->save();
30+
```
31+
32+
| Parameter | Description |
33+
|------------|------------------------------------------------------------------------------------------------------------------------|
34+
| `cart` | Instance of [`Cart`](/extending/php-apis/carts) |
35+
| `product` | Instance of `Product`. You may convert an entry object into a product via `Product::fromEntry($entry)` |
36+
| `variant` | Optional. Instance of `ProductVariant`. You can do `$product->productVariant($key)` to get a product variant instance. |
37+
| `quantity` | Optional. Line item quantity. Defaults to `1` |
38+
| `data` | Optional. Laravel [`Collection`](https://laravel.com/docs/master/collections) instance containing line item data. |
39+
40+
## PrerequisiteProductsCheck
41+
42+
The `PrerequisiteProductsCheck` action is responsible for ensuring that the customer has purchased the specified "prerequisite products" in the past.
43+
44+
A `ValidationException` will be thrown when a customer is missing from the cart or the customer hasn't purchased the prerequisite products.
45+
46+
```php
47+
use DuncanMcClean\Cargo\Cart\Actions\PrerequisiteProductsCheck;
48+
use DuncanMcClean\Cargo\Facades\Cart;
49+
50+
$cart = Cart::current();
51+
52+
app(PrerequisiteProductsCheck::class)->handle($cart, $product);
53+
54+
$cart->save();
55+
```
56+
57+
| Parameter | Description |
58+
|------------|------------------------------------------------------------------------------------------------------------------------|
59+
| `cart` | Instance of [`Cart`](/extending/php-apis/carts) |
60+
| `product` | Instance of `Product`. You may convert an entry object into a product via `Product::fromEntry($entry)` |
61+
62+
## UpdateDiscounts
63+
64+
Typically run during the Checkout process, the `UpdateDiscounts` action is responsible for updating the "redemption count" on discounts, and dispatching the [`DiscountRedeemed`](/extending/events/list#discountredeemed) event.
65+
66+
```php
67+
use DuncanMcClean\Cargo\Discounts\Actions\UpdateDiscounts;
68+
69+
app(UpdateDiscounts::class)->handle($order);
70+
```
71+
72+
| Parameter | Description |
73+
|-----------|---------------------------------------------------|
74+
| `order` | Instance of [`Order`](/extending/php-apis/orders) |
75+
76+
## UpdateStock
77+
78+
Typically run during the Checkout process, the `UpdateStock` action is responsible for updating the [stock counters](/docs/products#inventory--stock-tracking) on products and variants. It also dispatches various [stock-related events](/extending/events/list#productnostockremaining).
79+
80+
```php
81+
use DuncanMcClean\Cargo\Products\Actions\UpdateStock;
82+
83+
app(UpdateStock::class)->handle($order);
84+
```
85+
86+
| Parameter | Description |
87+
|-----------|---------------------------------------------------|
88+
| `order` | Instance of [`Order`](/extending/php-apis/orders) |
89+
90+
## ValidateStock
91+
92+
The `ValidateStock` action is responsible for ensuring that products have sufficient stock to fulfill the customer's order.
93+
94+
A `ValidationException` will be thrown when there's insufficient stock to fulfill the customer's order.
95+
96+
```php
97+
use DuncanMcClean\Cargo\Products\Actions\ValidateStock;
98+
99+
app(ValidateStock::class)->handle(
100+
$lineItem,
101+
$product,
102+
$variant,
103+
$quantity,
104+
);
105+
```
106+
107+
| Parameter | Description |
108+
|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
109+
| `lineItem` | Instance of `LineItem`. Can be `null` when the `$product` parameter is present. |
110+
| `product` | Instance of `Product`. You may convert an entry object into a product via `Product::fromEntry($entry)`. Can be `null` when the `$lineItem` parameter is present. |
111+
| `variant` | Required when dealing with a variant product. Instance of `ProductVariant`. You can do `$product->productVariant($key)` to get a product variant instance. |
112+
| `quantity` | Line item quantity. Can be `null` when the `$lineItem` parameter is present.

src/Cart/Actions/AddToCart.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace DuncanMcClean\Cargo\Cart\Actions;
4+
5+
use DuncanMcClean\Cargo\Contracts\Cart\Cart;
6+
use DuncanMcClean\Cargo\Contracts\Products\Product;
7+
use DuncanMcClean\Cargo\Orders\LineItem;
8+
use DuncanMcClean\Cargo\Products\Actions\ValidateStock;
9+
use DuncanMcClean\Cargo\Products\ProductVariant;
10+
use Illuminate\Support\Collection;
11+
12+
class AddToCart
13+
{
14+
public function handle(
15+
Cart $cart,
16+
Product $product,
17+
?ProductVariant $variant = null,
18+
?int $quantity = 1,
19+
?Collection $data = null,
20+
): void {
21+
if (! $data) {
22+
$data = collect();
23+
}
24+
25+
app(ValidateStock::class)->handle(
26+
product: $product,
27+
variant: $variant,
28+
quantity: $quantity
29+
);
30+
31+
app(PrerequisiteProductsCheck::class)->handle($cart, $product);
32+
33+
$productAlreadyInCart = $this->isProductAlreadyInCart($cart, $product, $variant, $data);
34+
35+
if ($productAlreadyInCart->count() > 0) {
36+
$lineItem = $productAlreadyInCart->first();
37+
38+
$cart->lineItems()->update(
39+
id: $lineItem->id(),
40+
data: $lineItem->data()->merge($data)->merge([
41+
'quantity' => (int) $lineItem->quantity() + ($quantity ?? 1),
42+
])->all()
43+
);
44+
} else {
45+
$cart->lineItems()->create($data->merge([
46+
'product' => $product->id(),
47+
'variant' => $variant?->key(),
48+
'quantity' => $quantity ?? 1,
49+
])->all());
50+
}
51+
}
52+
53+
private function isProductAlreadyInCart(Cart $cart, Product $product, ?ProductVariant $variant, Collection $data): Collection
54+
{
55+
return $cart->lineItems()
56+
->where('product', $product->id())
57+
->when($variant, fn ($collection) => $collection->where('variant', $variant?->key()))
58+
->when(config('statamic.cargo.carts.unique_metadata', false), function ($collection) use ($data) {
59+
return $collection->filter(function (LineItem $lineItem) use ($data) {
60+
foreach ($data as $key => $value) {
61+
if ($lineItem->get($key) !== $value) {
62+
return false;
63+
}
64+
}
65+
66+
return true;
67+
});
68+
});
69+
}
70+
}

src/Http/Controllers/Concerns/HandlePrerequisiteProducts.php renamed to src/Cart/Actions/PrerequisiteProductsCheck.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
<?php
22

3-
namespace DuncanMcClean\Cargo\Http\Controllers\Concerns;
3+
namespace DuncanMcClean\Cargo\Cart\Actions;
44

55
use DuncanMcClean\Cargo\Contracts\Cart\Cart;
6+
use DuncanMcClean\Cargo\Contracts\Products\Product;
67
use DuncanMcClean\Cargo\Customers\GuestCustomer;
78
use DuncanMcClean\Cargo\Facades\Order;
8-
use DuncanMcClean\Cargo\Products\Product;
9-
use Illuminate\Http\Request;
109
use Illuminate\Validation\ValidationException;
1110

12-
trait HandlePrerequisiteProducts
11+
class PrerequisiteProductsCheck
1312
{
14-
protected function handlePrerequisiteProducts(Request $request, Cart $cart, Product $product): Cart
13+
public function handle(Cart $cart, Product $product): void
1514
{
1615
if ($prerequisiteProduct = $product->prerequisite_product) {
1716
if (! $cart->customer() || $cart->customer() instanceof GuestCustomer) {
@@ -37,7 +36,5 @@ protected function handlePrerequisiteProducts(Request $request, Cart $cart, Prod
3736
]);
3837
}
3938
}
40-
41-
return $cart;
4239
}
4340
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace DuncanMcClean\Cargo\Discounts\Actions;
4+
5+
use DuncanMcClean\Cargo\Contracts\Orders\Order;
6+
use DuncanMcClean\Cargo\Events\DiscountRedeemed;
7+
use DuncanMcClean\Cargo\Facades\Discount;
8+
9+
class UpdateDiscounts
10+
{
11+
public function handle(Order $order): void
12+
{
13+
collect($order->get('discount_breakdown'))->each(function ($discount) use ($order) {
14+
$discount = Discount::find($discount['discount']);
15+
$discount->set('redemptions_count', $discount->get('redemptions_count', 0) + 1)->saveQuietly();
16+
17+
DiscountRedeemed::dispatch($discount, $order);
18+
});
19+
}
20+
}

src/Http/Controllers/CartLineItemsController.php

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,39 @@
22

33
namespace DuncanMcClean\Cargo\Http\Controllers;
44

5+
use DuncanMcClean\Cargo\Cart\Actions\AddToCart;
56
use DuncanMcClean\Cargo\Facades\Cart;
67
use DuncanMcClean\Cargo\Facades\Product;
78
use DuncanMcClean\Cargo\Http\Requests\Cart\AddLineItemRequest;
89
use DuncanMcClean\Cargo\Http\Requests\Cart\UpdateLineItemRequest;
910
use DuncanMcClean\Cargo\Http\Resources\API\CartResource;
10-
use DuncanMcClean\Cargo\Orders\LineItem;
11+
use DuncanMcClean\Cargo\Products\Actions\ValidateStock;
1112
use Illuminate\Http\Request;
1213
use Statamic\Exceptions\NotFoundHttpException;
1314

1415
class CartLineItemsController
1516
{
16-
use Concerns\HandlePrerequisiteProducts, Concerns\HandlesCustomerInformation, Concerns\ValidatesStock;
17+
use Concerns\HandlesCustomerInformation;
1718

1819
public function store(AddLineItemRequest $request)
1920
{
2021
$cart = Cart::current();
2122
$product = Product::find($request->product);
23+
$variant = $request->variant ? $product->variant($request->variant) : null;
2224

2325
$data = $request->collect()->except([
2426
'_token', '_method', '_redirect', '_error_redirect', 'product', 'variant', 'quantity', 'first_name', 'last_name', 'email', 'customer',
2527
]);
2628

27-
$this->validateStock($request, $cart);
29+
app(AddToCart::class)->handle(
30+
$cart,
31+
$product,
32+
$variant,
33+
$request->quantity,
34+
$data
35+
);
2836

2937
$cart = $this->handleCustomerInformation($request, $cart);
30-
$cart = $this->handlePrerequisiteProducts($request, $cart, $product);
31-
32-
$productIsAlreadyInCart = $cart->lineItems()
33-
->where('product', $product->id())
34-
->when($request->get('variant'), function ($collection) use ($request) {
35-
return $collection->where('variant', $request->get('variant'));
36-
})
37-
->when(config('statamic.cargo.carts.unique_metadata', false), function ($collection) use ($data) {
38-
return $collection->filter(function (LineItem $lineItem) use ($data) {
39-
foreach ($data as $key => $value) {
40-
if ($lineItem->get($key) !== $value) {
41-
return false;
42-
}
43-
}
44-
});
45-
});
46-
47-
if ($productIsAlreadyInCart->count() > 0) {
48-
$lineItem = $productIsAlreadyInCart->first();
49-
50-
$cart->lineItems()->update(
51-
id: $lineItem->id(),
52-
data: $lineItem->data()->merge($data)->merge([
53-
'quantity' => (int) $lineItem->quantity() + ($request->quantity ?? 1),
54-
])->all()
55-
);
56-
} else {
57-
$cart->lineItems()->create($data->merge([
58-
'product' => $request->product,
59-
'variant' => $request->variant,
60-
'quantity' => $request->quantity ?? 1,
61-
])->all());
62-
}
6338

6439
$cart->save();
6540

@@ -81,7 +56,17 @@ public function update(UpdateLineItemRequest $request, string $lineItem)
8156
'_token', '_method', '_redirect', '_error_redirect', 'product', 'variant', 'quantity', 'first_name', 'last_name'.'email', 'customer',
8257
]);
8358

84-
$this->validateStock($request, $cart, $lineItem);
59+
$variant = $lineItem->variant();
60+
61+
if ($request->variant) {
62+
$variant = $lineItem->product()->variant($request->variant);
63+
}
64+
65+
app(ValidateStock::class)->handle(
66+
lineItem: $lineItem,
67+
variant: $variant,
68+
quantity: $request->quantity ?? $lineItem->quantity,
69+
);
8570

8671
$cart->lineItems()->update(
8772
id: $lineItem->id(),

0 commit comments

Comments
 (0)