Skip to content

Exercise: review this checkout flow

Status: Complete. Last reviewed 2026-08-28.

Rank findings by severity. Discuss concurrency, authorization, transaction boundaries, external effects, Eloquent behavior, retries, money representation and observability.

Assume requests, worker/process failures, and client retries can overlap; the database and payment provider do not share a transaction; and the snippet is the complete controller method under review. Authentication middleware, route scoping, policies, model constraints/casts, provider idempotency semantics, inventory rules, accepted payment-data boundary, and surrounding exception handling are unknown. Ask for evidence rather than silently treating unknown controls as present or absent.

public function checkout(Request $request): JsonResponse
{
$order = Order::find($request->integer('order_id'));
if ($order->total == $request->input('total')) {
foreach ($order->items as $item) {
$stock = Product::find($item->product_id)->stock;
Product::whereKey($item->product_id)->update([
'stock' => $stock - $item->quantity,
]);
}
Http::post(config('payments.url') . '/charge', [
'card' => $request->input('card'),
'amount' => $order->total,
]);
$order->update(['status' => 'paid']);
Mail::to($order->user->email)->send(new Receipt($order));
}
return response()->json($order);
}

Questions:

  1. Which single issue can cause the largest direct loss?
  2. Which fixes require database constraints or atomic operations rather than validation?
  3. Where should idempotency be established?
  4. What can and cannot share one database transaction?
  5. Which behaviors belong in feature, integration and concurrency tests?

Provide a severity-ranked review with confidence for each finding. Separate what the snippet supports from missing evidence; state the violated invariant and a concrete failure sequence for blockers. Propose the smallest safe repair, tests and production/rollout checks, then finish with explicitly non-blocking maintainability or style feedback.

Compare your review with the discussion only after answering.