11 KiB
11 KiB
Purchasing & Shopping Cart
Setup
Add the HasShoppingCapabilities trait to your User model (or any model that should be able to purchase products):
use Blax\Shop\Traits\HasShoppingCapabilities;
class User extends Authenticatable
{
use HasShoppingCapabilities;
}
This trait provides methods for:
- Direct product purchases
- Shopping cart management
- Purchase history
- Cart checkout
Direct Purchase
Purchase a Product
$user = auth()->user();
$product = Product::find($productId);
// Product must have a default price
try {
$purchase = $user->purchase($product);
// Purchase successful
return response()->json([
'success' => true,
'purchase_id' => $purchase->id,
'amount' => $purchase->amount,
]);
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage()
], 400);
}
Purchase with Specific Price
$price = ProductPrice::find($priceId);
$purchase = $user->purchase(
$price,
quantity: 2
);
Purchase with Metadata
$purchase = $user->purchase(
$product,
quantity: 1,
meta: [
'gift' => true,
'message' => 'Happy Birthday!',
'gift_recipient' => 'john@example.com',
]
);
Important Notes
- Product must have at least one default price
- Product must not have multiple default prices (will throw
MultiplePurchaseOptionsexception) - If stock management is enabled, sufficient stock must be available
- Product must be visible (published, visible flag, and published_at date)
- Purchase automatically decreases stock if
manage_stockis enabled - Product actions are automatically triggered on purchase
Shopping Cart
Add to Cart
$user = auth()->user();
$product = Product::find($productId);
try {
$cartItem = $user->addToCart($product, quantity: 1);
return response()->json([
'success' => true,
'cart_item' => $cartItem,
'cart_total' => $user->getCartTotal(),
'cart_count' => $user->getCartItemsCount(),
]);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
Add with Parameters
$cartItem = $user->addToCart(
$product,
quantity: 2,
parameters: [
'color' => 'blue',
'size' => 'large',
]
);
Get Cart Items
$cartItems = $user->cartItems()->get();
foreach ($cartItems as $item) {
echo $item->purchasable->getLocalized('name');
echo $item->quantity;
echo $item->price;
echo $item->subtotal;
}
Update Cart Item Quantity
$cartItem = CartItem::find($cartItemId);
try {
$updatedItem = $user->updateCartQuantity($cartItem, quantity: 3);
return response()->json([
'success' => true,
'cart_total' => $user->getCartTotal(),
]);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
Remove from Cart
$cartItem = CartItem::find($cartItemId);
$user->removeFromCart($cartItem);
return response()->json([
'success' => true,
'cart_total' => $user->getCartTotal(),
'cart_count' => $user->getCartItemsCount(),
]);
Clear Cart
$count = $user->clearCart();
return response()->json([
'success' => true,
'removed_items' => $count,
]);
Get Cart Totals
// Get cart total
$total = $user->getCartTotal();
// Get cart items count
$count = $user->getCartItemsCount();
// Get cart stats
$stats = [
'total' => $user->getCartTotal(),
'count' => $user->getCartItemsCount(),
'items' => $user->cartItems()->with('purchasable')->get(),
];
Cart Checkout
Convert Cart to Purchases
try {
$purchases = $user->checkout();
// Checkout successful
// Cart items are now converted to completed purchases
// Cart is marked as converted
return response()->json([
'success' => true,
'purchases' => $purchases,
'total_items' => $purchases->count(),
]);
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage()
], 400);
}
Important Notes
- Checkout validates stock availability for all items
- Creates
ProductPurchaserecords for each cart item - Decreases stock for each item
- Triggers product actions
- Marks cart as converted (
converted_attimestamp) - Removes cart items after successful checkout
Purchase History
Check if User Purchased Product
$product = Product::find($productId);
if ($user->hasPurchased($product)) {
// User has purchased this product
echo "You own this product!";
}
Get All Purchases
// Get all purchases (any status)
$allPurchases = $user->purchases()->get();
// Get only completed purchases
$completedPurchases = $user->completedPurchases()->get();
// Get purchases for specific product
$productPurchases = $user->purchases()
->where('purchasable_id', $product->id)
->where('purchasable_type', Product::class)
->get();
Purchase Statistics
$stats = $user->getPurchaseStats();
// Returns:
// [
// 'total_purchases' => 15,
// 'total_spent' => 450.00,
// 'total_items' => 23,
// 'cart_items' => 2,
// 'cart_total' => 89.99,
// ]
Refunds
Refund a Purchase
$purchase = ProductPurchase::find($purchaseId);
try {
$success = $user->refundPurchase($purchase);
if ($success) {
// Refund successful
// Stock has been returned
// Purchase status changed to 'refunded'
// Product 'refunded' actions triggered
return response()->json([
'success' => true,
'message' => 'Purchase refunded successfully',
]);
}
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage()
], 400);
}
Important Notes
- Only completed purchases can be refunded
- Stock is automatically returned to inventory
- Product actions with event 'refunded' are triggered
Cart Model
Get Current Cart
// Get or create current active cart
$cart = $user->currentCart();
// Cart properties
$cart->session_id; // Session ID for guest carts
$cart->customer_id; // User ID
$cart->customer_type; // User model class
$cart->currency; // Cart currency (default: USD)
$cart->status; // active, abandoned, converted, expired
$cart->converted_at; // When cart was checked out
$cart->expires_at; // Cart expiration date
$cart->last_activity_at; // Last activity timestamp
Cart Relationships
// Get cart items
$items = $cart->items()->get();
// Get cart purchases (if converted)
$purchases = $cart->purchases()->get();
// Get cart customer (user)
$customer = $cart->customer;
Cart Methods
// Get cart total
$total = $cart->getTotal();
// Get total items
$itemCount = $cart->getTotalItems();
// Check if cart is expired
if ($cart->isExpired()) {
// Cart has expired
}
// Check if cart is converted
if ($cart->isConverted()) {
// Cart has been checked out
}
Add Items to Cart Directly
use Blax\Shop\Models\Cart;
$cart = Cart::find($cartId);
$cartItem = $cart->addToCart(
$product, // or $productPrice
quantity: 2,
parameters: ['size' => 'L']
);
Product Purchase Model
Purchase Properties
$purchase = ProductPurchase::find($purchaseId);
$purchase->status; // cart, pending, unpaid, completed, refunded
$purchase->cart_id; // Associated cart ID
$purchase->price_id; // Associated price ID
$purchase->purchasable_id; // Product ID
$purchase->purchasable_type; // Product class
$purchase->purchaser_id; // User ID
$purchase->purchaser_type; // User class
$purchase->quantity; // Quantity purchased
$purchase->amount; // Total amount
$purchase->amount_paid; // Amount paid
$purchase->charge_id; // Payment charge ID
$purchase->meta; // Additional metadata
Purchase Relationships
// Get purchased product
$product = $purchase->purchasable;
// Get purchaser (user)
$user = $purchase->purchaser;
Purchase Scopes
// Get purchases in cart
$cartPurchases = ProductPurchase::inCart()->get();
// Get completed purchases
$completed = ProductPurchase::completed()->get();
// Get purchases from specific cart
$cartPurchases = ProductPurchase::fromCart($cartId)->get();
Stock Reservations
When adding products to cart, stock is automatically reserved:
// Stock is reserved when adding to cart
$cartItem = $user->addToCart($product, quantity: 2);
// Reservation is created automatically
// It expires after configured time (default: 15 minutes)
// Stock is released back when:
// - Reservation expires
// - Cart item is removed
// - Cart is abandoned
Error Handling
Common Exceptions
use Blax\Shop\Exceptions\NotPurchasable;
use Blax\Shop\Exceptions\MultiplePurchaseOptions;
use Blax\Shop\Exceptions\NotEnoughStockException;
try {
$purchase = $user->purchase($product);
} catch (NotPurchasable $e) {
// Product has no default price
} catch (MultiplePurchaseOptions $e) {
// Product has multiple default prices - need to specify which one
$price = $product->prices()->where('currency', 'USD')->first();
$purchase = $user->purchase($price);
} catch (NotEnoughStockException $e) {
// Insufficient stock available
$available = $product->getAvailableStock();
echo "Only {$available} items available";
} catch (\Exception $e) {
// General error
echo $e->getMessage();
}
Complete Example
// Product listing
Route::get('/products', function () {
$products = Product::visible()
->inStock()
->with(['prices' => fn($q) => $q->where('is_default', true)])
->get();
return view('products.index', compact('products'));
});
// Add to cart
Route::post('/cart/add/{product}', function (Product $product) {
$user = auth()->user();
try {
$cartItem = $user->addToCart($product, quantity: 1);
return redirect()->back()->with('success', 'Product added to cart!');
} catch (\Exception $e) {
return redirect()->back()->with('error', $e->getMessage());
}
});
// View cart
Route::get('/cart', function () {
$user = auth()->user();
$cartItems = $user->cartItems()->with('purchasable')->get();
$cartTotal = $user->getCartTotal();
$cartCount = $user->getCartItemsCount();
return view('cart.index', compact('cartItems', 'cartTotal', 'cartCount'));
});
// Checkout
Route::post('/checkout', function () {
$user = auth()->user();
try {
$purchases = $user->checkout();
return redirect()->route('orders.success')
->with('success', 'Order placed successfully!');
} catch (\Exception $e) {
return redirect()->back()->with('error', $e->getMessage());
}
});
// Order history
Route::get('/orders', function () {
$user = auth()->user();
$purchases = $user->completedPurchases()
->with('purchasable')
->orderBy('created_at', 'desc')
->get();
return view('orders.index', compact('purchases'));
});