Table of Contents
System Overview
Core Features
- Product Management
- Shopping Cart
- Order Processing
- Payment Integration
- Inventory Management
- User Management
Technical Features
- DDD Layered Architecture
- Domain Event Driven
- External Payment Gateway Integration
- Inventory Deduction Transaction Processing
- Order State Machine
Complete Directory Tree
app/
├── controller/
│ ├── api/
│ │ └── v1/
│ │ ├── ProductController.php # Product API
│ │ ├── CartController.php # Shopping Cart API
│ │ ├── OrderController.php # Order API
│ │ └── PaymentController.php # Payment API
│ └── web/
│ ├── ProductController.php # Product Pages
│ └── CheckoutController.php # Checkout Pages
│
├── model/
│ └── eloquent/
│ ├── Product.php # Product Model
│ ├── Order.php # Order Model
│ ├── OrderItem.php # Order Item Model
│ ├── Cart.php # Cart Model
│ ├── Inventory.php # Inventory Model
│ └── Payment.php # Payment Record Model
│
├── service/
│ ├── product/
│ │ ├── CreateProductService.php # Create Product
│ │ ├── UpdateProductService.php # Update Product
│ │ └── SearchProductService.php # Search Products
│ ├── cart/
│ │ ├── AddToCartService.php # Add to Cart
│ │ └── CheckoutCartService.php # Cart Checkout
│ ├── order/
│ │ ├── CreateOrderService.php # Create Order
│ │ ├── CancelOrderService.php # Cancel Order
│ │ ├── RefundOrderService.php # Refund Order
│ │ └── CompleteOrderService.php # Complete Order
│ └── payment/
│ ├── ProcessPaymentService.php # Process Payment
│ └── HandlePaymentCallbackService.php # Payment Callback
│
├── domain/
│ ├── product/
│ │ ├── entity/
│ │ │ ├── Product.php # Product Entity
│ │ │ └── ProductCategory.php # Product Category
│ │ ├── vo/ # Value Objects
│ │ │ ├── ProductSku.php # SKU
│ │ │ ├── Price.php # Price
│ │ │ └── Stock.php # Stock Quantity
│ │ └── event/
│ │ ├── ProductCreated.php
│ │ └── ProductOutOfStock.php
│ │
│ ├── order/
│ │ ├── entity/
│ │ │ ├── Order.php # Order Entity
│ │ │ └── OrderItem.php # Order Item
│ │ ├── enum/ # Enums
│ │ │ └── OrderStatus.php # Order Status
│ │ ├── vo/ # Value Objects
│ │ │ ├── ShippingAddress.php # Shipping Address
│ │ │ └── OrderNumber.php # Order Number
│ │ ├── event/
│ │ │ ├── OrderCreated.php
│ │ │ ├── OrderPaid.php
│ │ │ ├── OrderShipped.php
│ │ │ ├── OrderCancelled.php
│ │ │ └── OrderCompleted.php
│ │ └── rule/
│ │ ├── OrderCancellationRule.php # Cancellation Rule
│ │ └── RefundRule.php # Refund Rule
│ │
│ ├── payment/
│ │ ├── entity/
│ │ │ └── Payment.php # Payment Entity
│ │ ├── enum/ # Enums
│ │ │ ├── PaymentMethod.php # Payment Method
│ │ │ └── PaymentStatus.php # Payment Status
│ │ ├── vo/ # Value Objects
│ │ │ └── TransactionId.php # Transaction ID
│ │ └── event/
│ │ ├── PaymentInitiated.php
│ │ ├── PaymentSucceeded.php
│ │ └── PaymentFailed.php
│ │
│ ├── inventory/
│ │ ├── entity/
│ │ │ └── Inventory.php # Inventory Entity
│ │ ├── vo/ # Value Objects
│ │ │ └── StockLevel.php # Stock Level
│ │ ├── event/
│ │ │ ├── StockReserved.php # Stock Reserved
│ │ │ ├── StockReleased.php # Stock Released
│ │ │ └── StockDeducted.php # Stock Deducted
│ │ └── rule/
│ │ └── StockReservationRule.php # Stock Reservation Rule
│ │
│ └── shared/
│ └── vo/ # Value Objects
│ ├── Money.php # Money
│ ├── Email.php # Email
│ └── PhoneNumber.php # Phone Number
│
├── contract/
│ ├── repository/
│ │ ├── ProductRepositoryInterface.php
│ │ ├── OrderRepositoryInterface.php
│ │ ├── CartRepositoryInterface.php
│ │ ├── InventoryRepositoryInterface.php
│ │ └── PaymentRepositoryInterface.php
│ ├── gateway/
│ │ ├── PaymentGatewayInterface.php # Payment Gateway Interface
│ │ ├── SmsGatewayInterface.php # SMS Gateway Interface
│ │ └── EmailGatewayInterface.php # Email Gateway Interface
│ └── service/
│ └── NotificationServiceInterface.php # Notification Service Interface
│
├── infrastructure/
│ ├── repository/
│ │ └── eloquent/
│ │ ├── EloquentProductRepository.php
│ │ ├── EloquentOrderRepository.php
│ │ ├── EloquentCartRepository.php
│ │ ├── EloquentInventoryRepository.php
│ │ └── EloquentPaymentRepository.php
│ ├── gateway/
│ │ ├── payment/
│ │ │ ├── StripePaymentGateway.php # Stripe Payment
│ │ │ ├── AlipayPaymentGateway.php # Alipay
│ │ │ └── WechatPaymentGateway.php # WeChat Pay
│ │ ├── sms/
│ │ │ └── TwilioSmsGateway.php
│ │ └── email/
│ │ └── SendGridEmailGateway.php
│ └── cache/
│ └── RedisProductCache.php # Product Cache
│
├── middleware/
│ ├── auth/
│ │ └── AuthenticateMiddleware.php
│ └── rate_limit/
│ └── ApiRateLimitMiddleware.php
│
├── process/
│ ├── task/
│ │ ├── OrderTimeoutTask.php # Order Timeout Handler
│ │ └── InventorySyncTask.php # Inventory Sync
│ └── queue/
│ ├── OrderNotificationConsumer.php # Order Notification Consumer
│ └── PaymentCallbackConsumer.php # Payment Callback Consumer
│
└── support/
├── exception/
│ ├── ProductNotFoundException.php
│ ├── InsufficientStockException.php
│ ├── OrderNotFoundException.php
│ └── PaymentFailedException.php
└── helper/
└── order_helper.phpModule Breakdown
1. Product Module
Features:
- Product CRUD
- Product Search
- Category Management
- SKU Management
Core Classes:
domain/product/entity/Product.phpservice/product/CreateProductService.phpcontroller/api/v1/ProductController.php
2. Order Module
Features:
- Order Creation
- Order Status Transitions
- Order Cancellation/Refund
- Order Query
Core Classes:
domain/order/entity/Order.phpservice/order/CreateOrderService.phpdomain/order/rule/OrderCancellationRule.php
3. Payment Module
Features:
- Payment Initiation
- Payment Callback Handling
- Multiple Payment Methods Support
- Payment Status Synchronization
Core Classes:
domain/payment/entity/Payment.phpservice/payment/ProcessPaymentService.phpinfrastructure/gateway/payment/StripePaymentGateway.php
4. Inventory Module
Features:
- Stock Reservation
- Stock Deduction
- Stock Release
- Inventory Sync
Core Classes:
domain/inventory/entity/Inventory.phpdomain/inventory/rule/StockReservationRule.php
5. Shopping Cart Module
Features:
- Add Products
- Modify Quantity
- Remove Products
- Checkout
Core Classes:
service/cart/AddToCartService.phpservice/cart/CheckoutCartService.php
Directory Responsibilities
controller/ - HTTP Entry Layer
Responsibility: Handle HTTP requests, validate input, call service layer, return responses
Example: controller/api/v1/OrderController.php
service/ - Application Service Layer
Responsibility: Use case orchestration, transaction management, call domain and infrastructure layers
Example: service/order/CreateOrderService.php
domain/ - Domain Layer
Responsibility: Core business logic, business rules, domain events
Example: domain/order/entity/Order.php
contract/ - Interface Definition Layer
Responsibility: Define repository, gateway, and service interfaces
Example: contract/gateway/PaymentGatewayInterface.php
infrastructure/ - Infrastructure Layer
Responsibility: Implement interfaces, data persistence, external service integration
Example: infrastructure/gateway/payment/StripePaymentGateway.php
Key Code Examples
1. Order Entity
<?php
declare(strict_types=1);
namespace app\domain\order\entity;
use app\domain\order\enum\OrderStatus;
use app\domain\order\vo\OrderNumber;
use app\domain\order\vo\ShippingAddress;
use app\domain\shared\vo\Money;
use app\domain\order\event\OrderCreated;
use app\domain\order\event\OrderPaid;
use app\domain\order\event\OrderCancelled;
use app\domain\order\exception\InvalidOrderOperationException;
final class Order
{
private array $domainEvents = [];
private function __construct(
private readonly int $id,
private readonly OrderNumber $orderNumber,
private readonly int $userId,
private array $items,
private Money $totalAmount,
private OrderStatus $status,
private readonly ShippingAddress $shippingAddress,
private readonly \DateTimeImmutable $createdAt
) {
}
public static function create(
int $userId,
array $items,
ShippingAddress $shippingAddress
): self {
if (empty($items)) {
throw new InvalidOrderOperationException('Order must have at least one item');
}
$order = new self(
id: 0,
orderNumber: OrderNumber::generate(),
userId: $userId,
items: $items,
totalAmount: Money::zero(),
status: OrderStatus::Pending,
shippingAddress: $shippingAddress,
createdAt: new \DateTimeImmutable()
);
$order->calculateTotal();
$order->recordEvent(new OrderCreated($order));
return $order;
}
public function calculateTotal(): void
{
$total = array_reduce(
$this->items,
fn (Money $carry, OrderItem $item) => $carry->add($item->subtotal()),
Money::zero()
);
$this->totalAmount = $total;
}
public function markAsPaid(): void
{
if (!$this->status->canBePaid()) {
throw new InvalidOrderOperationException('Only pending orders can be marked as paid');
}
$this->status = OrderStatus::Paid;
$this->recordEvent(new OrderPaid($this));
}
public function cancel(): void
{
if (!$this->status->canBeCancelled()) {
throw new InvalidOrderOperationException('Order cannot be cancelled in current status');
}
$this->status = OrderStatus::Cancelled;
$this->recordEvent(new OrderCancelled($this));
}
// Getters
public function id(): int
{
return $this->id;
}
public function orderNumber(): OrderNumber
{
return $this->orderNumber;
}
public function userId(): int
{
return $this->userId;
}
public function totalAmount(): Money
{
return $this->totalAmount;
}
public function status(): OrderStatus
{
return $this->status;
}
public function items(): array
{
return $this->items;
}
private function recordEvent(object $event): void
{
$this->domainEvents[] = $event;
}
public function releaseEvents(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}2. Create Order Service
<?php
declare(strict_types=1);
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
use app\contract\repository\ProductRepositoryInterface;
use app\contract\repository\InventoryRepositoryInterface;
use app\domain\order\entity\Order;
use app\domain\order\entity\OrderItem;
use app\domain\order\vo\ShippingAddress;
use app\domain\shared\vo\Money;
use support\Db;
final class CreateOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly ProductRepositoryInterface $productRepository,
private readonly InventoryRepositoryInterface $inventoryRepository
) {
}
public function handle(int $userId, array $items, array $shippingAddressData): Order
{
return Db::transaction(function () use ($userId, $items, $shippingAddressData) {
// 1. Validate products and inventory
$orderItems = [];
foreach ($items as $item) {
$product = $this->productRepository->findById($item['product_id']);
if ($product === null) {
throw new \RuntimeException("Product {$item['product_id']} not found");
}
// Reserve inventory
$inventory = $this->inventoryRepository->findByProductId($product->id());
$inventory->reserve($item['quantity']);
$this->inventoryRepository->save($inventory);
$orderItems[] = OrderItem::create(
productId: $product->id(),
productName: $product->name(),
price: $product->price(),
quantity: $item['quantity']
);
}
// 2. Create order
$shippingAddress = ShippingAddress::fromArray($shippingAddressData);
$order = Order::create($userId, $orderItems, $shippingAddress);
// 3. Save order
$this->orderRepository->save($order);
return $order;
});
}
}3. Payment Gateway Interface
<?php
declare(strict_types=1);
namespace app\contract\gateway;
use app\domain\payment\entity\Payment;
use app\domain\payment\enum\PaymentMethod;
interface PaymentGatewayInterface
{
/**
* Create payment
*/
public function createPayment(
string $orderNumber,
int $amount,
PaymentMethod $method
): Payment;
/**
* Query payment status
*/
public function queryPayment(string $transactionId): Payment;
/**
* Refund payment
*/
public function refund(string $transactionId, int $amount): bool;
/**
* Verify callback signature
*/
public function verifyCallback(array $data): bool;
}4. Stripe Payment Gateway Implementation
<?php
declare(strict_types=1);
namespace app\infrastructure\gateway\payment;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\payment\entity\Payment;
use app\domain\payment\enum\PaymentMethod;
use app\domain\payment\enum\PaymentStatus;
use app\domain\payment\vo\TransactionId;
use app\domain\shared\vo\Money;
use Stripe\StripeClient;
final class StripePaymentGateway implements PaymentGatewayInterface
{
private StripeClient $stripe;
public function __construct(string $apiKey)
{
$this->stripe = new StripeClient($apiKey);
}
public function createPayment(
string $orderNumber,
int $amount,
PaymentMethod $method
): Payment {
$paymentIntent = $this->stripe->paymentIntents->create([
'amount' => $amount,
'currency' => 'usd',
'metadata' => [
'order_number' => $orderNumber,
],
]);
return Payment::create(
transactionId: TransactionId::fromString($paymentIntent->id),
orderNumber: $orderNumber,
amount: Money::fromCents($amount),
method: $method,
status: PaymentStatus::Pending
);
}
public function queryPayment(string $transactionId): Payment
{
$paymentIntent = $this->stripe->paymentIntents->retrieve($transactionId);
return Payment::reconstitute(
transactionId: TransactionId::fromString($paymentIntent->id),
orderNumber: $paymentIntent->metadata['order_number'],
amount: Money::fromCents($paymentIntent->amount),
method: PaymentMethod::CreditCard,
status: $this->mapStatus($paymentIntent->status)
);
}
public function refund(string $transactionId, int $amount): bool
{
$refund = $this->stripe->refunds->create([
'payment_intent' => $transactionId,
'amount' => $amount,
]);
return $refund->status === 'succeeded';
}
public function verifyCallback(array $data): bool
{
// Stripe webhook signature verification
$signature = $data['signature'] ?? '';
$payload = $data['payload'] ?? '';
try {
\Stripe\Webhook::constructEvent(
$payload,
$signature,
config('stripe.webhook_secret')
);
return true;
} catch (\Exception $e) {
return false;
}
}
private function mapStatus(string $stripeStatus): PaymentStatus
{
return match ($stripeStatus) {
'succeeded' => PaymentStatus::Succeeded,
'processing' => PaymentStatus::Processing,
'canceled' => PaymentStatus::Cancelled,
'requires_payment_method' => PaymentStatus::Failed,
default => PaymentStatus::Pending,
};
}
}5. Order Controller
<?php
declare(strict_types=1);
namespace app\controller\api\v1;
use app\service\order\CreateOrderService;
use app\service\order\CancelOrderService;
use app\contract\repository\OrderRepositoryInterface;
use support\Request;
use support\Response;
final class OrderController
{
public function __construct(
private readonly CreateOrderService $createOrderService,
private readonly CancelOrderService $cancelOrderService,
private readonly OrderRepositoryInterface $orderRepository
) {
}
/**
* Create order
*/
public function create(Request $request): Response
{
$validated = $this->validate($request, [
'items' => 'required|array',
'items.*.product_id' => 'required|integer',
'items.*.quantity' => 'required|integer|min:1',
'shipping_address' => 'required|array',
'shipping_address.name' => 'required|string',
'shipping_address.phone' => 'required|string',
'shipping_address.address' => 'required|string',
]);
$order = $this->createOrderService->handle(
userId: $request->user()->id,
items: $validated['items'],
shippingAddressData: $validated['shipping_address']
);
return json([
'success' => true,
'data' => [
'order_id' => $order->id(),
'order_number' => $order->orderNumber()->value(),
'total_amount' => $order->totalAmount()->toCents(),
'status' => $order->status()->value(),
],
]);
}
/**
* Cancel order
*/
public function cancel(Request $request, int $orderId): Response
{
$this->cancelOrderService->handle(
orderId: $orderId,
userId: $request->user()->id
);
return json([
'success' => true,
'message' => 'Order cancelled successfully',
]);
}
/**
* Get order details
*/
public function show(Request $request, int $orderId): Response
{
$order = $this->orderRepository->findById($orderId);
if ($order === null || $order->userId() !== $request->user()->id) {
return json([
'success' => false,
'message' => 'Order not found',
], 404);
}
return json([
'success' => true,
'data' => [
'id' => $order->id(),
'order_number' => $order->orderNumber()->value(),
'total_amount' => $order->totalAmount()->toCents(),
'status' => $order->status()->value(),
'items' => array_map(
fn ($item) => [
'product_id' => $item->productId(),
'product_name' => $item->productName(),
'price' => $item->price()->toCents(),
'quantity' => $item->quantity(),
'subtotal' => $item->subtotal()->toCents(),
],
$order->items()
),
],
]);
}
}6. Stock Reservation Rule
<?php
declare(strict_types=1);
namespace app\domain\inventory\rule;
use app\domain\inventory\entity\Inventory;
use app\domain\inventory\exception\InsufficientStockException;
final class StockReservationRule
{
/**
* Validate if stock is sufficient
*/
public function validate(Inventory $inventory, int $quantity): void
{
if ($inventory->availableStock() < $quantity) {
throw new InsufficientStockException(
"Insufficient stock for product {$inventory->productId()}. " .
"Available: {$inventory->availableStock()}, Requested: {$quantity}"
);
}
}
/**
* Check if overselling is allowed
*/
public function allowOverselling(Inventory $inventory): bool
{
// Some products allow overselling (e.g., virtual products)
return $inventory->isVirtual();
}
}Dependency Injection Configuration
<?php
// config/container.php
use app\contract\repository\OrderRepositoryInterface;
use app\contract\repository\ProductRepositoryInterface;
use app\contract\repository\InventoryRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
use app\infrastructure\repository\eloquent\EloquentOrderRepository;
use app\infrastructure\repository\eloquent\EloquentProductRepository;
use app\infrastructure\repository\eloquent\EloquentInventoryRepository;
use app\infrastructure\gateway\payment\StripePaymentGateway;
return [
// Repository bindings
OrderRepositoryInterface::class => EloquentOrderRepository::class,
ProductRepositoryInterface::class => EloquentProductRepository::class,
InventoryRepositoryInterface::class => EloquentInventoryRepository::class,
// Gateway bindings
PaymentGatewayInterface::class => function () {
return new StripePaymentGateway(config('stripe.api_key'));
},
];Best Practices
1. Order Status Transitions
Use state machine pattern to manage order status:
- pending -> paid -> shipped -> completed
- pending -> cancelled
- paid -> refunded
2. Inventory Management
- Reserve stock when placing order (reserve)
- Deduct stock after payment success (deduct)
- Release stock when order cancelled (release)
3. Payment Integration
- Use strategy pattern to support multiple payment methods
- Process payment callbacks asynchronously
- Implement idempotency to prevent duplicate payments
4. Transaction Handling
- Order creation and inventory reservation in same transaction
- Use domain events to decouple inter-module communication
5. Performance Optimization
- Product information caching
- Inventory query optimization
- Order list pagination