Skip to content
Mehedi Hasan Sagor

// blog

Designing a Scalable Laravel E‑Commerce Architecture with Secure Payments

Mehedi Hasan Sagor Nov 5, 2026 1 min read
Designing a Scalable Laravel E‑Commerce Architecture with Secure Payments

Overview\n\nLaunching an e‑commerce platform is more than stitching a product catalog to a checkout button. Startups and agencies need a codebase that can grow with traffic, adapt to new business rules, and stay compliant with payment regulations. This article walks through a pragmatic Laravel architecture, the data stores that complement it, and a proven pattern for integrating payments securely. The goal is to give you concrete decisions you can implement today and a checklist you can hand off to your team.\n\n## Core Architecture Layers\n\nInstead of letting every controller talk directly to the database, split the application into four thin layers:\n\n1. Domain (Models & Value Objects) – Pure business concepts without Laravel‑specific concerns.\n2. Application Services – Orchestrate domain objects, enforce use‑case rules, and return DTOs.\n3. Infrastructure – Repositories, external API clients, and job dispatchers.\n4. Presentation (Controllers / Livewire / API Resources) – Translate HTTP requests to service calls and format responses.\n\nThis separation brings three immediate benefits:\n\n- Testability – Unit tests can target services without bootstrapping the whole framework.\n- Swapability – Replace a payment gateway or search engine without touching the core business logic.\n- Team autonomy – Front‑end developers can work on Livewire components while back‑end engineers evolve services.\n\n### Example: Order Placement Service\n\nphp\n<?php\n\nnamespace App\Services;\n\nuse App\Domain\Order;\nuse App\Repositories\OrderRepository;\nuse App\Payments\PaymentGateway;\nuse Illuminate\Support\Facades\DB;\n\nclass PlaceOrderService\n{\n protected $orders;\n protected $gateway;\n\n public function __construct(OrderRepository $orders, PaymentGateway $gateway)\n {\n $this->orders = $orders;\n $this->gateway = $gateway;\n }\n\n public function execute(array $payload): Order\n {\n return DB::transaction(function () use ($payload) {\n $order = $this->orders->create($payload['order']);\n\n $payment = $this->gateway->authorize(\n $order->total,\n $payload['payment']\n );\n\n $order->payment_id = $payment->id;\n $order->status = 'authorized';\n $order->save();\n\n return $order;\n });\n }\n}\n\n\nThe controller becomes a thin wrapper:\n\nphp\npublic function store(Request $request, PlaceOrderService $service)\n{\n $order = $service->execute($request->validated());\n return new OrderResource($order);\n}\n\n\n## Choosing the Right Data Store\n\n### Relational Database (MySQL / MariaDB)\n\nUse a well‑indexed relational store for core transactional data – orders, customers, inventory. Laravel's migration system makes schema evolution repeatable across environments.\n\nKey indexes:\n- orders.user_id\n- orders.status\n- order_items.product_id\n\n### Cache Layer (Redis)\n\nCache frequently accessed data that does not change per request: product listings, price rules, and user cart snapshots. Laravel's cache façade abstracts Redis, allowing you to swap Memcached later if needed.\n\nphp\nCache::remember('product:'.$id, now()->addMinutes(30), fn() =>\n Product::with('category')->findOrFail($id)\n);\n\n\n### Full‑Text Search (MeiliSearch or Elastic)\n\nFor faceted search, keyword relevance, and typo tolerance, offload to a dedicated search engine. Keep the search index in sync using model observers or queued jobs.\n\nphp\npublic function saved(Order $order)\n{\n dispatch(new SyncOrderToSearch($order));\n}\n\n\n### Event Store (Optional)\n\nIf you anticipate complex audit requirements, consider an append‑only event table that records every state transition. This can be queried for order history without adding extra columns to the primary orders table.\n\n## Payment Integration Patterns\n\nPayments are the most regulated part of an e‑commerce system. Two common integration strategies exist: Redirect (Hosted) Gateways and Direct API integrations.\n\n### 1. Hosted (Redirect) Gateways\n\n- Flow: Customer clicks Pay, is redirected to the provider (Stripe Checkout, PayPal), completes the transaction, and returns to a pre‑defined success URL.\n- Pros: PCI‑SAQ A compliance (you never touch raw card data), quick to implement, provider handles 3‑D Secure.\n- Cons: Limited UI customization, extra round‑trip latency.\n\nImplementation sketch:\n\nphp\npublic function redirectToGateway(Order $order)\n{\n $session = Stripe::checkout()->sessions()->create([\n 'payment_method_types' => ['card'],\n 'line_items' => $order->items->map(fn($item) => [\n 'price_data' => [\n 'currency' => 'usd',\n 'unit_amount' => $item->price * 100,\n 'product_data' => ['name' => $item->product->name],\n ],\n 'quantity' => $item->quantity,\n ])->toArray(),\n 'mode' => 'payment',\n 'success_url' => route('checkout.success', ['order' => $order->id]),\n 'cancel_url' => route('checkout.cancel'),\n ]);\n\n return redirect($session->url);\n}\n\n\n### 2. Direct API Integration\n\n- Flow: Collect card details in a PCI‑compliant front‑end (Stripe Elements, Braintree Hosted Fields), send a token to your backend, and create a charge via the provider's API.\n- Pros: Full control over checkout UI, can store payment methods for future purchases.\n- Cons: Requires SAQ D compliance or a qualified security assessor, more code to maintain.\n\nKey security step: Never store raw PAN. Store only the provider‑generated token or use tokenization services.\n\nphp\npublic function charge(Request $request, Order $order, PaymentGateway $gateway)\n{\n $token = $request->input('payment_token'); // obtained from Stripe Elements\n\n $charge = $gateway->charge($order->total, $token, [\n 'metadata' => ['order_id' => $order->id]\n ]);\n\n $order->payment_id = $charge->id;\n $order->status = 'paid';\n $order->save();\n\n // Fire an event for downstream processes (emails, fulfillment)\n event(new OrderPaid($order));\n\n return response()->json(['status' => 'success']);\n}\n\n\n### Webhook Handling\n\nAll major gateways send asynchronous events (payment succeeded, dispute opened). Use a dedicated route with signed payload verification. Queue the handling to avoid timeouts.\n\nphp\nRoute::post('webhooks/stripe', function (Request $request) {\n $signature = $request->header('Stripe-Signature');\n $payload = $request->getContent();\n\n if (!Stripe::webhook()->verifySignature($payload, $signature, config('services.stripe.webhook_secret'))) {\n abort(400);\n }\n\n dispatch(new ProcessStripeEvent($payload));\n return response()->json(['received' => true]);\n});\n\n\nThe queued job parses the event, updates the order status, and triggers any business rules (e.g., inventory reservation).\n\n## Security Best Practices\n\n1. Input Validation – Use Laravel Form Requests for every public endpoint. Validate price totals on the server; never trust a client‑side amount.\n2. CSRF Protection – Laravel includes middleware; ensure it is enabled for all web routes. API routes should use token‑based auth (Sanctum).\n3. Encryption at Rest – Enable APP_KEY rotation and use Laravel's encrypt() helper for sensitive fields like shipping_address.\n4. Tokenization for Card Data – Store only the provider token. If you need to keep a reference to the payment method, encrypt the token before persisting.\n5. Rate Limiting – Throttle checkout attempts to mitigate abuse.\n\nphp\nRoute::post('checkout', [CheckoutController::class, 'store'])\n ->middleware('throttle:10,1'); // 10 attempts per minute per IP\n\n\n6. Audit Logging – Write immutable logs for payment state changes. Laravel's built‑in logging channels can ship logs to CloudWatch or Papertrail.\n\nphp\nLog::channel('secure')->info('Payment captured', [\n 'order_id' => $order->id,\n 'gateway' => $gateway->name,\n 'status' => $charge->status,\n]);\n\n\n## Deployment & Scaling Considerations\n\n| Concern | Laravel Feature | Recommended Configuration |\n|---------|----------------|---------------------------|\n| Horizontal Scaling | Stateless HTTP layer | Deploy behind a load balancer; store sessions in Redis (SESSION_DRIVER=redis). |\n| Background Work | Queues | Use Laravel Horizon with Redis for visual monitoring; separate queue connections for payments and emails. |\n| Cache Invalidation | Cache Tags | Tag product‑related caches (Cache::tags(['product:'.$id])->flush();) so a price change invalidates only relevant keys. |\n| Zero‑Downtime Deploys | Laravel Octane / FPM | With Octane, pre‑warm workers to avoid cold starts; use rolling deployments (e.g., GitHub Actions + Laravel Forge). |\n| Observability | Telescope / Sentry | Enable Telescope locally; ship errors to Sentry in production. Include transaction IDs for tracing. |\n\n### Database Sharding vs. Read Replicas\n\nFor most startups, read replicas are sufficient – they offload catalog browsing while writes (orders) stay on the primary. Only consider sharding when a single database cannot hold the SKU volume or when you need multi‑tenant isolation at the schema level.\n\n### CDN for Assets\n\nServe product images, CSS, and JavaScript via a CDN (CloudFront, Fastly). Store images on S3 with public-read and configure signed URLs for premium assets.\n\n## Actionable Checklist\n\n- Architectural scaffolding: create Domain, Services, Repositories, Infrastructure directories; bind interfaces in a service provider.\n- Data model: design orders, order_items, payments, products tables with proper indexes.\n- Cache strategy: cache product listings; invalidate on product update using model events.\n- Payment flow: decide between hosted checkout or direct API; implement tokenization and webhook verification.\n- Security hardening: enable CSRF, rate limiting, server‑side total verification, encrypt sensitive columns.\n- Queue setup: configure Redis queue, create jobs for webhook processing, email dispatch, inventory sync.\n- Observability: add Sentry DSN, enable Horizon dashboard, write audit logs for payment events.\n- CI/CD: include database migrations, cache clearing, and queue worker restarts in the deployment pipeline.\n\nBy following these concrete steps, you’ll have a Laravel e‑commerce platform that can handle growth spikes, stay compliant with PCI requirements, and remain maintainable for future feature work. The modular layout also makes it straightforward to swap in new payment providers or replace the search engine without a massive rewrite.\n

  • Laravel
  • Architecture
  • Security
  • Ecommerce
  • Payments