E-commerce is one of the most common requirements we encounter at ZIRA Software. While platforms like Magento, WooCommerce, and Shopify work for many businesses, custom Laravel e-commerce solutions offer unmatched flexibility, performance, and control. In this guide, we'll cover building a robust e-commerce platform with Laravel.
Why Laravel for E-commerce?
Advantages:
- Complete control over features and design
- No licensing fees or monthly costs
- Optimized performance for your specific needs
- Easy integration with existing systems
- Scalable architecture
When to choose custom Laravel:
- Unique business requirements
- Complex product configurations
- Multi-vendor marketplaces
- Integration with legacy systems
- Performance-critical applications
Database Schema
Start with a solid database structure:
// Products table
Schema::create('products', function($table) {
$table->increments('id');
$table->string('sku')->unique();
$table->string('name');
$table->text('description');
$table->decimal('price', 10, 2);
$table->decimal('sale_price', 10, 2)->nullable();
$table->integer('quantity')->default(0);
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->softDeletes();
});
// Categories
Schema::create('categories', function($table) {
$table->increments('id');
$table->integer('parent_id')->nullable();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
// Product-Category pivot
Schema::create('category_product', function($table) {
$table->integer('category_id')->unsigned();
$table->integer('product_id')->unsigned();
$table->primary(['category_id', 'product_id']);
});
// Orders
Schema::create('orders', function($table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->string('status'); // pending, processing, shipped, delivered
$table->decimal('subtotal', 10, 2);
$table->decimal('tax', 10, 2);
$table->decimal('shipping', 10, 2);
$table->decimal('total', 10, 2);
$table->string('shipping_address');
$table->string('billing_address');
$table->timestamps();
});
// Order items
Schema::create('order_items', function($table) {
$table->increments('id');
$table->integer('order_id')->unsigned();
$table->integer('product_id')->unsigned();
$table->integer('quantity');
$table->decimal('price', 10, 2);
$table->timestamps();
});
Product Model
<?php
class Product extends Eloquent {
protected $fillable = ['sku', 'name', 'description', 'price', 'quantity'];
protected $softDelete = true;
public function categories()
{
return $this->belongsToMany('Category');
}
public function images()
{
return $this->hasMany('ProductImage');
}
public function reviews()
{
return $this->hasMany('Review');
}
// Check if product is in stock
public function inStock()
{
return $this->quantity > 0;
}
// Get final price (sale price if available)
public function getFinalPriceAttribute()
{
return $this->sale_price ?: $this->price;
}
// Decrease stock after purchase
public function decreaseStock($quantity)
{
if ($this->quantity >= $quantity) {
$this->quantity -= $quantity;
$this->save();
return true;
}
return false;
}
}
Shopping Cart Implementation
Create a cart service for managing shopping cart operations:
<?php
class CartService {
public function add($productId, $quantity = 1)
{
$product = Product::findOrFail($productId);
$cart = Session::get('cart', []);
if (isset($cart[$productId])) {
$cart[$productId]['quantity'] += $quantity;
} else {
$cart[$productId] = [
'product_id' => $product->id,
'name' => $product->name,
'price' => $product->final_price,
'quantity' => $quantity,
];
}
Session::put('cart', $cart);
return $cart;
}
public function remove($productId)
{
$cart = Session::get('cart', []);
if (isset($cart[$productId])) {
unset($cart[$productId]);
Session::put('cart', $cart);
}
return $cart;
}
public function update($productId, $quantity)
{
$cart = Session::get('cart', []);
if (isset($cart[$productId])) {
if ($quantity <= 0) {
return $this->remove($productId);
}
$cart[$productId]['quantity'] = $quantity;
Session::put('cart', $cart);
}
return $cart;
}
public function getCart()
{
return Session::get('cart', []);
}
public function getTotal()
{
$cart = $this->getCart();
$total = 0;
foreach ($cart as $item) {
$total += $item['price'] * $item['quantity'];
}
return $total;
}
public function getItemCount()
{
$cart = $this->getCart();
$count = 0;
foreach ($cart as $item) {
$count += $item['quantity'];
}
return $count;
}
public function clear()
{
Session::forget('cart');
}
}
Cart Controller
<?php
class CartController extends BaseController {
protected $cartService;
public function __construct(CartService $cartService)
{
$this->cartService = $cartService;
}
public function index()
{
$cart = $this->cartService->getCart();
$total = $this->cartService->getTotal();
return View::make('cart.index', compact('cart', 'total'));
}
public function add($productId)
{
$quantity = Input::get('quantity', 1);
try {
$this->cartService->add($productId, $quantity);
return Redirect::back()->with('success', 'Product added to cart');
} catch (Exception $e) {
return Redirect::back()->with('error', 'Error adding product to cart');
}
}
public function update($productId)
{
$quantity = Input::get('quantity');
$this->cartService->update($productId, $quantity);
return Redirect::route('cart.index')
->with('success', 'Cart updated');
}
public function remove($productId)
{
$this->cartService->remove($productId);
return Redirect::route('cart.index')
->with('success', 'Item removed from cart');
}
}
Checkout Process
<?php
class CheckoutController extends BaseController {
public function index()
{
$cart = App::make('CartService')->getCart();
if (empty($cart)) {
return Redirect::route('shop.index')
->with('error', 'Your cart is empty');
}
return View::make('checkout.index');
}
public function process()
{
$cartService = App::make('CartService');
$cart = $cartService->getCart();
// Validate
$validator = Validator::make(Input::all(), [
'shipping_address' => 'required',
'billing_address' => 'required',
'payment_method' => 'required|in:stripe,paypal',
]);
if ($validator->fails()) {
return Redirect::back()->withErrors($validator)->withInput();
}
// Create order
DB::beginTransaction();
try {
$order = Order::create([
'user_id' => Auth::id(),
'status' => 'pending',
'subtotal' => $cartService->getTotal(),
'tax' => $this->calculateTax($cartService->getTotal()),
'shipping' => $this->calculateShipping(),
'total' => $this->calculateOrderTotal($cartService->getTotal()),
'shipping_address' => Input::get('shipping_address'),
'billing_address' => Input::get('billing_address'),
]);
// Add order items
foreach ($cart as $item) {
OrderItem::create([
'order_id' => $order->id,
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
]);
// Decrease stock
$product = Product::find($item['product_id']);
$product->decreaseStock($item['quantity']);
}
// Process payment
$paymentService = App::make('PaymentService');
$paymentResult = $paymentService->charge(
$order->total,
Input::get('payment_method'),
Input::get('payment_token')
);
if ($paymentResult['success']) {
$order->status = 'paid';
$order->transaction_id = $paymentResult['transaction_id'];
$order->save();
DB::commit();
// Clear cart
$cartService->clear();
// Send confirmation email
Mail::send('emails.order-confirmation', ['order' => $order], function($message) {
$message->to(Auth::user()->email)->subject('Order Confirmation');
});
return Redirect::route('order.success', $order->id);
} else {
DB::rollBack();
return Redirect::back()
->with('error', 'Payment failed: ' . $paymentResult['message']);
}
} catch (Exception $e) {
DB::rollBack();
Log::error('Checkout error: ' . $e->getMessage());
return Redirect::back()->with('error', 'An error occurred during checkout');
}
}
private function calculateTax($subtotal)
{
return $subtotal * 0.08; // 8% tax
}
private function calculateShipping()
{
return 10.00; // Flat rate shipping
}
private function calculateOrderTotal($subtotal)
{
return $subtotal + $this->calculateTax($subtotal) + $this->calculateShipping();
}
}
Product Catalog
<?php
class ShopController extends BaseController {
public function index()
{
$products = Product::where('is_active', true)
->with('categories', 'images')
->paginate(12);
$categories = Category::whereNull('parent_id')->get();
return View::make('shop.index', compact('products', 'categories'));
}
public function category($slug)
{
$category = Category::where('slug', $slug)->firstOrFail();
$products = $category->products()
->where('is_active', true)
->with('images')
->paginate(12);
return View::make('shop.category', compact('category', 'products'));
}
public function show($id)
{
$product = Product::with('categories', 'images', 'reviews')
->findOrFail($id);
$related = Product::whereHas('categories', function($query) use ($product) {
$query->whereIn('id', $product->categories->pluck('id'));
})
->where('id', '!=', $product->id)
->take(4)
->get();
return View::make('shop.product', compact('product', 'related'));
}
public function search()
{
$query = Input::get('q');
$products = Product::where('is_active', true)
->where(function($q) use ($query) {
$q->where('name', 'LIKE', "%{$query}%")
->orWhere('description', 'LIKE', "%{$query}%");
})
->paginate(12);
return View::make('shop.search', compact('products', 'query'));
}
}
Inventory Management
// Update stock levels
Product::where('id', $productId)->decrement('quantity', $quantitySold);
// Low stock alert
$lowStockProducts = Product::where('quantity', '<', 10)->get();
// Stock history tracking
Schema::create('stock_movements', function($table) {
$table->increments('id');
$table->integer('product_id')->unsigned();
$table->integer('quantity'); // Positive for additions, negative for sales
$table->string('type'); // purchase, sale, adjustment
$table->text('notes')->nullable();
$table->timestamps();
});
Security Considerations
- Validate all inputs
- Use CSRF protection (Laravel includes this)
- Sanitize product data before display
- Secure payment processing - never store credit cards
- Use HTTPS in production
- Implement rate limiting on checkout
Performance Optimization
// Cache product catalog
$products = Cache::remember('products.featured', 60, function() {
return Product::where('is_featured', true)->with('images')->get();
});
// Eager load relationships
$orders = Order::with('items.product', 'user')->get();
// Optimize images
// Use Intervention Image for thumbnails
$img = Image::make('uploads/product.jpg');
$img->resize(300, 200);
$img->save('uploads/thumbnails/product.jpg');
Conclusion
Building e-commerce platforms with Laravel gives you complete control and flexibility. While it requires more upfront development than using a platform like WooCommerce, the long-term benefits—performance, customization, and scalability—make it worthwhile for businesses with unique requirements.
Need a custom e-commerce solution? Contact ZIRA Software to discuss your project. We've built successful e-commerce platforms processing millions in annual revenue.