strcmp($a['sku'], $b['sku'])); $hash = hash('sha256', json_encode($items, JSON_THROW_ON_ERROR)); try { return DB::transaction(function () use ($actor, $items, $key, $hash) { if ($existing = Quote::where('dealer_id', $actor->dealer_id)->where('request_key', $key)->first()) return [$this->sameRequest($existing, $hash), false]; $lines = []; $subtotal = 0; foreach ($items as $item) { $product = Product::whereKey($item['sku'])->where('active', true)->first(); if (!$product || $product->stock < $item['quantity']) throw ValidationException::withMessages(['items' => 'Requested quantity is unavailable.']); $lines[] = ['sku' => $product->sku, 'quantity' => $item['quantity'], 'unit_price_cents' => $product->price_cents]; $subtotal += $product->price_cents * $item['quantity']; } $quote = Quote::create(['dealer_id' => $actor->dealer_id, 'user_id' => $actor->id, 'status' => 'pending', 'subtotal_cents' => $subtotal, 'request_key' => $key, 'request_hash' => $hash, 'expires_at' => now()->addDay()]); $quote->lines()->createMany($lines); $this->audit($actor, 'quote_requested', $quote->id); return [$quote, true]; }, 3); } catch (QueryException $error) { // A parallel identical insert may have won the unique key race. if (in_array((string) $error->getCode(), ['23000', '23505']) && ($existing = Quote::where('dealer_id', $actor->dealer_id)->where('request_key', $key)->first())) return [$this->sameRequest($existing, $hash), false]; throw $error; } } private function sameRequest(Quote $quote, string $hash): Quote { abort_unless(hash_equals($quote->request_hash, $hash), 409, 'Idempotency key was already used for a different request.'); return $quote; } public function approve(User $actor, Quote $quote): Quote { return DB::transaction(function () use ($actor, $quote) { $locked = Quote::whereKey($quote->id)->lockForUpdate()->firstOrFail(); abort_if($locked->expires_at->isPast(), 409, 'Quote expired. Request a fresh quote.'); if ($locked->status === 'approved') return $locked; abort_unless($locked->status === 'pending', 409, 'Quote is no longer pending.'); $locked->update(['status' => 'approved']); $this->audit($actor, 'quote_approved', $locked->id); return $locked; }, 3); } public function order(User $actor, Quote $quote, string $key): array { return DB::transaction(function () use ($actor, $quote, $key) { $locked = Quote::whereKey($quote->id)->lockForUpdate()->firstOrFail(); if ($existing = DemoOrder::where('dealer_id', $actor->dealer_id)->where('request_key', $key)->first()) { abort_unless($existing->quote_id === $locked->id, 409, 'Idempotency key was already used for another quote.'); return [$existing, false]; } abort_unless($locked->status === 'approved', 409, 'An approved quote is required.'); abort_if($locked->expires_at->isPast(), 409, 'Quote expired. Request a fresh quote.'); foreach ($locked->lines()->orderBy('sku')->get() as $line) { $product = Product::whereKey($line->sku)->lockForUpdate()->firstOrFail(); abort_unless($product->active && $product->stock >= $line->quantity, 409, 'Stock changed. Request a fresh quote.'); $product->decrement('stock', $line->quantity); } $order = DemoOrder::create(['quote_id' => $locked->id, 'dealer_id' => $actor->dealer_id, 'total_cents' => $locked->subtotal_cents, 'request_key' => $key, 'status' => 'synthetic_accepted']); $locked->update(['status' => 'converted']); $this->audit($actor, 'synthetic_order_accepted', $order->id); return [$order, true]; }, 3); } private function audit(User $actor, string $event, string $entity): void { AuditEvent::create(['dealer_id' => $actor->dealer_id, 'actor_id' => $actor->id, 'event_code' => $event, 'entity_id' => $entity, 'occurred_at' => now()]); } }