<?php
declare(strict_types=1);
namespace App\Actions\Checkout;
use App\Models\Order;
use App\Models\User;
use App\DTOs\CheckoutData;
use App\Events\OrderPlacedEvent;
use App\Services\Payment\MpesaPaymentGateway;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
final class ProcessCheckoutAction
{
public function __construct(
private readonly MpesaPaymentGateway $paymentGateway
) {}
public function execute(User $customer, CheckoutData $data): Order
{
return DB::transaction(function () use ($customer, $data): Order {
$order = Order::create([
'user_id' => $customer->id,
'total_amount' => $data->totalAmount,
'status' => 'pending_payment',
'shipping_address' => $data->shippingAddress,
]);
foreach ($data->items as $item) {
$order->items()->create([
'product_id' => $item->productId,
'quantity' => $item->quantity,
'unit_price' => $item->unitPrice,
]);
$item->product->decrementStock($item->quantity);
}
$stkResponse = $this->paymentGateway->initiateStkPush(
phone: $data->phoneNumber,
amount: $data->totalAmount,
accountReference: "ORD-{$order->id}",
transactionDesc: "Payment for Order #{$order->id}"
);
$order->update([
'merchant_request_id' => $stkResponse->merchantRequestId,
'checkout_request_id' => $stkResponse->checkoutRequestId,
]);
event(new OrderPlacedEvent($order));
Log::info("Checkout completed for Order #{$order->id}");
return $order;
});
}
}
Why Tackling Technical Debt Early Determines System Longevity
In high-velocity engineering teams, the easiest path is often adding another if-else block to an already bloated 2,000-line controller or running raw queries directly inside Blade templates. While this delivers immediate features, it creates compounding architectural debt that eventually grinds feature velocity to a halt.
"Do the hard things first" means addressing data modeling bottlenecks, strict boundary isolation, and domain boundaries before layering business features on top.
1. Identifying the "God Model" Anti-Pattern
When an Eloquent model like User or Order handles database persistence, external billing webhooks, PDF generation, email dispatching, and complex validation, any small change risks catastrophic regressions.
2. The Refactoring Strategy: Step-by-Step Isolation
- Extract Form Requests: Remove all
$request->validate()and authorization checks from controller actions. - Isolate Domain Actions: Encapsulate multi-step write operations inside dedicated, invokable Action classes (e.g.
RegisterTenantAction). - Introduce Value Objects: Replace primitive obsession with immutable, validated Value Objects.
- Event-Driven Decoupling: Fire Domain Events so ancillary side-effects execute asynchronously via queued listeners.
Accelerate Your Engineering Career
Get monthly masterclasses, exclusive code snippets, and direct architectural reviews.
Become a Supporter • KES 500 / Month