lockPurchase($purchase); if ($purchase->payment_method === null) { throw ValidationException::withMessages([ 'payment_method' => __('api.purchase.payment_method_required'), ]); } if ($this->isTerminal($purchase)) { return $this->loadPurchase($purchase); } $purchase->update([ 'status' => Purchase::STATUS_PENDING_PAYMENT, 'total' => $purchase->calculateCurrentTotalAmount(), ]); return $this->loadPurchase($purchase); }); } public function submitForReview(Purchase $purchase): Purchase { return DB::transaction(function () use ($purchase): Purchase { $purchase = $this->lockPurchase($purchase); if ($purchase->status === Purchase::STATUS_PAID) { return $this->loadPurchase($purchase); } if ( $purchase->status !== Purchase::STATUS_PENDING_PAYMENT || ($purchase->expires_at !== null && $purchase->expires_at->isPast()) ) { throw ValidationException::withMessages([ 'purchase' => __('api.purchase.not_available_for_review'), ]); } $purchase->update(['expires_at' => null]); $this->reservations->syncPurchaseExpiration($purchase); return $this->loadPurchase($purchase); }); } public function confirm(Purchase $purchase): void { DB::transaction(function () use ($purchase): void { $purchase = $this->lockPurchase($purchase); if ($purchase->status === Purchase::STATUS_PAID) { return; } if (in_array($purchase->status, [ Purchase::STATUS_CANCELLED, Purchase::STATUS_REJECTED, Purchase::STATUS_EXPIRED, ], true)) { throw ValidationException::withMessages([ 'purchase' => __('api.purchase.cannot_confirm'), ]); } if ($purchase->items()->exists()) { $cart = $purchase->cart()->withTrashed()->lockForUpdate()->first(); if ($cart?->status === 'converted' && $cart->trashed()) { return; } throw ValidationException::withMessages([ 'items' => __('api.purchase.inconsistent_reservation'), ]); } $cart = $purchase->cart()->lockForUpdate()->first(); if ($cart === null || $cart->status !== 'checkout') { throw ValidationException::withMessages([ 'items' => __('api.purchase.inconsistent_reservation'), ]); } $cartItems = $cart->items()->orderBy('id')->lockForUpdate()->get(); if ($cartItems->isEmpty()) { throw ValidationException::withMessages([ 'items' => __('api.purchase.inconsistent_reservation'), ]); } $this->loadCartItems($cartItems); $purchase->items()->createMany( $this->snapshots->fromCartItems($cartItems), ); foreach ($cartItems as $cartItem) { $selection = $cartItem->selectedItem(); if ($selection === null) { throw ValidationException::withMessages([ 'items' => __('api.purchase.inconsistent_reservation'), ]); } try { $this->reservations->commit($cartItem, $selection); } catch (\InvalidArgumentException) { throw ValidationException::withMessages([ 'items' => __('api.purchase.inconsistent_reservation'), ]); } } $this->sourceCart->finalize($purchase); }); } private function isTerminal(Purchase $purchase): bool { return in_array($purchase->status, [ Purchase::STATUS_PAID, Purchase::STATUS_CANCELLED, Purchase::STATUS_REJECTED, Purchase::STATUS_EXPIRED, ], true); } private function lockPurchase(Purchase $purchase): Purchase { /** @var Purchase */ return Purchase::query()->lockForUpdate()->findOrFail($purchase->getKey()); } private function loadPurchase(Purchase $purchase): Purchase { return $purchase->load([ 'items.imageAttachment', 'cart.items.catalogItem.inventory', 'cart.items.catalogItem.attachments', 'cart.items.variant.inventory', 'cart.items.variant.attachments', 'cart.items.variant.definitions.itemAttribute.attribute', 'cart.items.variant.eventDates', 'cart.items.variant.eventDate', ]); } /** @param Collection $cartItems */ private function loadCartItems(Collection $cartItems): void { $cartItems->load([ 'catalogItem.inventory', 'catalogItem.attachments', 'variant.inventory', 'variant.attachments', 'variant.catalogItem', 'variant.definitions.itemAttribute.attribute', 'variant.eventDates', 'variant.eventDate', ]); } }