Ecommerce Orders
Ecommerce Orders
UltraViolet Pro includes a comprehensive order management system that handles the complete order lifecycle from cart to fulfillment.
Order Management
Order Lifecycle
The order system follows a structured workflow:
- Cart → Customer adds items to cart
- Checkout → Customer provides shipping and payment info
- Pending → Order is created and awaiting payment
- Processing → Payment confirmed, order being prepared
- Shipped → Order has been shipped to customer
- Delivered → Order has been delivered
- Cancelled → Order was cancelled (if applicable)
Order Status Management
// Order status constants
class OrderStatus
{
const PENDING = 'pending';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
const CANCELLED = 'cancelled';
const REFUNDED = 'refunded';
}
// Payment status constants
class PaymentStatus
{
const PENDING = 'pending';
const PAID = 'paid';
const FAILED = 'failed';
const REFUNDED = 'refunded';
const PARTIALLY_REFUNDED = 'partially_refunded';
}
Creating Orders
Cart to Order Conversion
// Convert cart to order
public function createOrderFromCart($cart, $user, $shippingAddress, $billingAddress, $paymentMethod)
{
DB::beginTransaction();
try {
// Create order
$order = Order::create([
'order_number' => $this->generateOrderNumber(),
'user_id' => $user->id,
'status' => OrderStatus::PENDING,
'subtotal' => $cart->subtotal,
'tax_amount' => $cart->tax_amount,
'shipping_amount' => $cart->shipping_amount,
'discount_amount' => $cart->discount_amount,
'total_amount' => $cart->total,
'currency' => 'USD',
'shipping_address' => $shippingAddress,
'billing_address' => $billingAddress,
'payment_method' => $paymentMethod,
'payment_status' => PaymentStatus::PENDING,
]);
// Create order items
foreach ($cart->items as $cartItem) {
$order->items()->create([
'product_id' => $cartItem->product_id,
'product_variant_id' => $cartItem->product_variant_id,
'product_name' => $cartItem->product->name,
'product_sku' => $cartItem->product->sku,
'quantity' => $cartItem->quantity,
'unit_price' => $cartItem->unit_price,
'total_price' => $cartItem->total_price,
'product_data' => $cartItem->product->toArray(), // Store product snapshot
]);
}
// Update inventory
$this->updateInventory($order);
// Clear cart
$cart->items()->delete();
$cart->delete();
DB::commit();
// Send order confirmation email
$user->notify(new OrderConfirmation($order));
return $order;
} catch (Exception $e) {
DB::rollback();
throw $e;
}
}
// Generate unique order number
private function generateOrderNumber()
{
do {
$orderNumber = 'ORD-' . date('Y') . '-' . strtoupper(Str::random(6));
} while (Order::where('order_number', $orderNumber)->exists());
return $orderNumber;
}
Order Items Model
class OrderItem extends Model
{
protected $fillable = [
'order_id', 'product_id', 'product_variant_id',
'product_name', 'product_sku', 'quantity',
'unit_price', 'total_price', 'product_data'
];
protected $casts = [
'product_data' => 'array',
'unit_price' => 'decimal:2',
'total_price' => 'decimal:2',
];
public function order()
{
return $this->belongsTo(Order::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
public function variant()
{
return $this->belongsTo(ProductVariant::class, 'product_variant_id');
}
}
Order Display and Management
Order List View
<div class="orders-list">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Orders</h2>
<div class="order-filters">
<select class="form-select d-inline-block w-auto" id="statusFilter" onchange="filterOrders()">
<option value="">All Orders</option>
<option value="pending">Pending</option>
<option value="processing">Processing</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Order #</th>
<th>Customer</th>
<th>Date</th>
<th>Status</th>
<th>Payment</th>
<th>Total</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($orders as $order)
<tr>
<td>
<a href="route-admin.orders.show-order.html" class="text-decoration-none">
{{ $order->order_number }}
</a>
</td>
<td>
<div>
<div class="fw-bold">{{ $order->user->name }}</div>
<small class="text-muted">{{ $order->user->email }}</small>
</div>
</td>
<td>{{ $order->created_at->format('M d, Y') }}</td>
<td>
<span class="badge bg-{{ $order->status_color }}">
{{ ucfirst($order->status) }}
</span>
</td>
<td>
<span class="badge bg-{{ $order->payment_status_color }}">
{{ ucfirst($order->payment_status) }}
</span>
</td>
<td class="fw-bold">${{ $order->total_amount }}</td>
<td>
<div class="btn-group btn-group-sm">
<a href="route-admin.orders.show-order.html"
class="btn btn-outline-primary" title="View">
<i class="bi bi-eye"></i>
</a>
<button class="btn btn-outline-secondary"
onclick="updateOrderStatus({{ $order->id }})" title="Update Status">
<i class="bi bi-pencil"></i>
</button>
<a href="route-admin.orders.invoice-order.html"
class="btn btn-outline-info" title="Invoice">
<i class="bi bi-file-text"></i>
</a>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $orders->links() }}
</div>
Order Detail View
<div class="order-details">
<div class="row">
<div class="col-md-8">
<!-- Order Information -->
<div class="card dashboard-margin">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Order #{{ $order->order_number }}</h5>
<div class="order-status">
<span class="badge bg-{{ $order->status_color }} fs-6">
{{ ucfirst($order->status) }}
</span>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6>Order Information</h6>
<table class="table table-sm">
<tr>
<td><strong>Order Date:</strong></td>
<td>{{ $order->created_at->format('M d, Y H:i') }}</td>
</tr>
<tr>
<td><strong>Order Number:</strong></td>
<td>{{ $order->order_number }}</td>
</tr>
<tr>
<td><strong>Payment Method:</strong></td>
<td>{{ ucfirst($order->payment_method) }}</td>
</tr>
<tr>
<td><strong>Payment Status:</strong></td>
<td>
<span class="badge bg-{{ $order->payment_status_color }}">
{{ ucfirst($order->payment_status) }}
</span>
</td>
</tr>
</table>
</div>
<div class="col-md-6">
<h6>Customer Information</h6>
<table class="table table-sm">
<tr>
<td><strong>Name:</strong></td>
<td>{{ $order->user->name }}</td>
</tr>
<tr>
<td><strong>Email:</strong></td>
<td>{{ $order->user->email }}</td>
</tr>
<tr>
<td><strong>Phone:</strong></td>
<td>{{ $order->user->phone ?? 'N/A' }}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<!-- Order Items -->
<div class="card dashboard-margin">
<div class="card-header">
<h5 class="mb-0">Order Items</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Product</th>
<th>SKU</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
@foreach($order->items as $item)
<tr>
<td>
<div class="d-flex align-items-center">
<img src="{{ $item->product_data['primary_image'] ?? '/images/placeholder.jpg' }}"
class="me-3" style="width: 50px; height: 50px; object-fit: cover;">
<div>
<div class="fw-bold">{{ $item->product_name }}</div>
@if($item->variant)
<small class="text-muted">{{ $item->variant->attributes }}</small>
@endif
</div>
</div>
</td>
<td>{{ $item->product_sku }}</td>
<td>{{ $item->quantity }}</td>
<td>${{ $item->unit_price }}</td>
<td class="fw-bold">${{ $item->total_price }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<!-- Order Summary -->
<div class="card dashboard-margin">
<div class="card-header">
<h5 class="mb-0">Order Summary</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<tr>
<td>Subtotal:</td>
<td class="text-end">${{ $order->subtotal }}</td>
</tr>
<tr>
<td>Shipping:</td>
<td class="text-end">${{ $order->shipping_amount }}</td>
</tr>
<tr>
<td>Tax:</td>
<td class="text-end">${{ $order->tax_amount }}</td>
</tr>
@if($order->discount_amount > 0)
<tr>
<td>Discount:</td>
<td class="text-end text-success">-${{ $order->discount_amount }}</td>
</tr>
@endif
<tr class="table-active">
<td><strong>Total:</strong></td>
<td class="text-end"><strong>${{ $order->total_amount }}</strong></td>
</tr>
</table>
</div>
</div>
<!-- Shipping Address -->
<div class="card dashboard-margin">
<div class="card-header">
<h5 class="mb-0">Shipping Address</h5>
</div>
<div class="card-body">
<address>
{{ $order->shipping_address['name'] }}<br>
{{ $order->shipping_address['address_line_1'] }}<br>
@if($order->shipping_address['address_line_2'])
{{ $order->shipping_address['address_line_2'] }}<br>
@endif
{{ $order->shipping_address['city'] }}, {{ $order->shipping_address['state'] }} {{ $order->shipping_address['postal_code'] }}<br>
{{ $order->shipping_address['country'] }}
</address>
</div>
</div>
<!-- Order Actions -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">Order Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-primary" onclick="updateOrderStatus({{ $order->id }})">
Update Status
</button>
<a href="route-admin.orders.invoice-order.html" class="btn btn-outline-primary">
Generate Invoice
</a>
<button class="btn btn-outline-info" onclick="sendOrderUpdate({{ $order->id }})">
Send Update Email
</button>
@if($order->status !== 'cancelled')
<button class="btn btn-outline-danger" onclick="cancelOrder({{ $order->id }})">
Cancel Order
</button>
@endif
</div>
</div>
</div>
</div>
</div>
</div>
Order Status Management
Status Update Interface
<!-- Order Status Update Modal -->
<div class="modal fade" id="statusUpdateModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Update Order Status</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="statusUpdateForm">
@csrf
<div class="mb-3">
<label for="orderStatus" class="form-label">Order Status</label>
<select class="form-select" id="orderStatus" name="status">
<option value="pending">Pending</option>
<option value="processing">Processing</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
<div class="mb-3">
<label for="paymentStatus" class="form-label">Payment Status</label>
<select class="form-select" id="paymentStatus" name="payment_status">
<option value="pending">Pending</option>
<option value="paid">Paid</option>
<option value="failed">Failed</option>
<option value="refunded">Refunded</option>
</select>
</div>
<div class="mb-3">
<label for="trackingNumber" class="form-label">Tracking Number</label>
<input type="text" class="form-control" id="trackingNumber" name="tracking_number">
</div>
<div class="mb-3">
<label for="statusNotes" class="form-label">Notes</label>
<textarea class="form-control" id="statusNotes" name="notes" rows="3"></textarea>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="sendEmail" name="send_email" checked>
<label class="form-check-label" for="sendEmail">
Send email notification to customer
</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveStatusUpdate()">Update Status</button>
</div>
</div>
</div>
</div>
<script>
function updateOrderStatus(orderId) {
// Fetch current order data
fetch(`/api/orders/${orderId}`)
.then(response => response.json())
.then(data => {
document.getElementById('orderStatus').value = data.status;
document.getElementById('paymentStatus').value = data.payment_status;
document.getElementById('trackingNumber').value = data.tracking_number || '';
// Show modal
const modal = new bootstrap.Modal(document.getElementById('statusUpdateModal'));
modal.show();
// Store order ID for form submission
document.getElementById('statusUpdateForm').dataset.orderId = orderId;
});
}
function saveStatusUpdate() {
const form = document.getElementById('statusUpdateForm');
const orderId = form.dataset.orderId;
const formData = new FormData(form);
fetch(`/api/orders/${orderId}/status`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('statusUpdateModal'));
modal.hide();
// Reload page or update UI
location.reload();
} else {
alert('Error updating order status: ' + data.message);
}
});
}
</script>
Order Status Controller
// Order status update controller
public function updateStatus(Request $request, Order $order)
{
$validated = $request->validate([
'status' => 'required|in:pending,processing,shipped,delivered,cancelled',
'payment_status' => 'required|in:pending,paid,failed,refunded',
'tracking_number' => 'nullable|string|max:255',
'notes' => 'nullable|string',
'send_email' => 'boolean'
]);
$oldStatus = $order->status;
// Update order
$order->update([
'status' => $validated['status'],
'payment_status' => $validated['payment_status'],
'tracking_number' => $validated['tracking_number'],
'notes' => $validated['notes']
]);
// Create status history entry
$order->statusHistory()->create([
'status' => $validated['status'],
'payment_status' => $validated['payment_status'],
'notes' => $validated['notes'],
'changed_by' => auth()->id(),
'changed_at' => now()
]);
// Send email notification if requested
if ($request->boolean('send_email') && $oldStatus !== $validated['status']) {
$order->user->notify(new OrderStatusUpdate($order, $oldStatus));
}
// Update inventory if order is cancelled
if ($validated['status'] === 'cancelled' && $oldStatus !== 'cancelled') {
$this->restoreInventory($order);
}
return response()->json([
'success' => true,
'message' => 'Order status updated successfully'
]);
}
Order Analytics and Reporting
Order Statistics
// Order analytics
class OrderAnalytics
{
public function getOrderStats($period = '30_days')
{
$dateRange = $this->getDateRange($period);
return [
'total_orders' => Order::whereBetween('created_at', $dateRange)->count(),
'total_revenue' => Order::whereBetween('created_at', $dateRange)
->where('payment_status', 'paid')
->sum('total_amount'),
'average_order_value' => Order::whereBetween('created_at', $dateRange)
->where('payment_status', 'paid')
->avg('total_amount'),
'orders_by_status' => Order::whereBetween('created_at', $dateRange)
->groupBy('status')
->selectRaw('status, count(*) as count')
->pluck('count', 'status'),
'revenue_by_day' => Order::whereBetween('created_at', $dateRange)
->where('payment_status', 'paid')
->groupBy(DB::raw('DATE(created_at)'))
->selectRaw('DATE(created_at) as date, SUM(total_amount) as revenue')
->orderBy('date')
->get()
];
}
private function getDateRange($period)
{
switch ($period) {
case '7_days':
return [now()->subDays(7), now()];
case '30_days':
return [now()->subDays(30), now()];
case '90_days':
return [now()->subDays(90), now()];
case '1_year':
return [now()->subYear(), now()];
default:
return [now()->subDays(30), now()];
}
}
}
Order Dashboard
<div class="order-dashboard">
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-bg-primary">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>{{ $stats['total_orders'] }}</h4>
<p class="mb-0">Total Orders</p>
</div>
<div class="align-self-center">
<i class="bi bi-cart-check fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-bg-success">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>${{ number_format($stats['total_revenue'], 2) }}</h4>
<p class="mb-0">Total Revenue</p>
</div>
<div class="align-self-center">
<i class="bi bi-currency-dollar fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>${{ number_format($stats['average_order_value'], 2) }}</h4>
<p class="mb-0">Average Order Value</p>
</div>
<div class="align-self-center">
<i class="bi bi-graph-up fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4>{{ $stats['orders_by_status']['pending'] ?? 0 }}</h4>
<p class="mb-0">Pending Orders</p>
</div>
<div class="align-self-center">
<i class="bi bi-clock fs-1"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Revenue Chart -->
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>Revenue Trend</h5>
</div>
<div class="card-body">
<canvas id="revenueChart" height="100"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>Orders by Status</h5>
</div>
<div class="card-body">
<canvas id="statusChart"></canvas>
</div>
</div>
</div>
</div>
</div>
This comprehensive order management system provides everything needed to handle the complete order lifecycle in an ecommerce application, from creation to fulfillment and analytics.