stock check, variants in cart, variant buttons in product page

This commit is contained in:
elvira
2026-09-17 21:52:39 +03:00
parent fe55cb5f33
commit afa1993c53
27 changed files with 564 additions and 155 deletions
@@ -23,6 +23,7 @@
use Modules\Core\Checkout\Exceptions\UnknownPaymentTypeException;
use Modules\Core\Checkout\Services\CheckoutService;
use Modules\Core\Payment\Enums\PaymentResultStatus;
use Modules\Core\Payment\Models\PaymentMethod;
/**
* The checkout page — one page, sections (contact / billing / shipping /
@@ -275,6 +276,30 @@ public function placeOrder(string $locale, Request $request): JsonResponse
$cart = $this->cart->current();
// Captured now, before initiatePayment() can place the order — Lunar's
// CartSessionManager::fetchOrCreate() silently swaps the session onto a
// BRAND NEW empty cart the moment the current one hasCompletedOrders()
// (i.e. has an order with placed_at set), which happens synchronously
// for an immediately-captured payment. Any later $this->cart->current()
// call in this same flow (here, or in a subsequent orderStatus() poll
// once the 3-D Secure webhook sets placed_at) would then resolve to
// that fresh, order-less cart instead of the one that was just placed.
// Storing the real cart id ourselves, under our own session key,
// sidesteps CartSession entirely for the rest of the placement flow.
session(['checkout.cart_id' => $cart?->id]);
// Lunar's own ValidateCartForOrderCreation (order_create validator)
// never checks for this — an empty cart with a valid billing address
// sails straight through it and would place a real, zero-line order.
// The disabled "place order" button is only the client-side half of
// this fix; this is the half that actually matters.
if ($cart === null || $this->cart->activeLines($cart)->isEmpty()) {
return response()->json([
'status' => 'invalid',
'message' => __('checkout.page.cart_empty'),
], 422);
}
// The one incomplete-cart case worth a specific message + pointing the
// shopper at the right section: a region resolving 2+ methods needs an
// explicit pick (no auto-select), easy to miss since nothing else on
@@ -312,13 +337,20 @@ public function placeOrder(string $locale, Request $request): JsonResponse
return response()->json(['error' => __('checkout.page.terms_required')], 422);
}
return match ($result->status) {
PaymentResultStatus::Succeeded => $this->orderPlacedResponse($locale),
PaymentResultStatus::Pending => response()->json([
// Pending with no continuation (cash-on-delivery, or any other
// deferred/offline method) means CheckoutService::initiatePayment()
// already created the placed order — money just hasn't changed
// hands yet. Only a Pending WITH a continuation (Stripe's client
// secret) means the shopper still has something to do before the
// order exists as far as the storefront is concerned.
return match (true) {
$result->status === PaymentResultStatus::Succeeded => $this->orderPlacedResponse($locale),
$result->status === PaymentResultStatus::Pending && $result->continuation === null => $this->orderPlacedResponse($locale),
$result->status === PaymentResultStatus::Pending => response()->json([
'status' => 'pending',
'clientSecret' => $result->continuation?->value,
]),
PaymentResultStatus::Failed => response()->json([
default => response()->json([
'status' => 'failed',
'message' => $result->failureReason ?: __('checkout.page.payment_failed'),
'retriable' => $result->retriable,
@@ -350,14 +382,24 @@ public function confirmation(string $locale): View|RedirectResponse
$orderId = session('checkout.order_id');
$order = $orderId
? Order::with(['lines', 'shippingAddress', 'billingAddress'])->find($orderId)
? Order::with(['lines.purchasable.product', 'shippingAddress', 'billingAddress'])->find($orderId)
: null;
if (! $order) {
return redirect()->route('products', $locale);
}
return view('checkout::confirmation', ['order' => $order]);
// Looked up by type rather than a stored relation — the method may since
// have been disabled/deleted, but the order still needs to show what was
// actually used at the time.
$paymentMethodName = PaymentMethod::where('type', $order->meta['payment_method'] ?? null)
->first()
?->translate('name');
return view('checkout::confirmation', [
'order' => $order,
'paymentMethodName' => $paymentMethodName,
]);
}
private function orderPlacedResponse(string $locale): JsonResponse
@@ -373,8 +415,13 @@ private function orderPlacedResponse(string $locale): JsonResponse
private function placedOrder(): ?Order
{
return $this->cart->current()
?->orders()
$cartId = session('checkout.cart_id');
if ($cartId === null) {
return null;
}
return Order::where('cart_id', $cartId)
->whereNotNull('placed_at')
->latest('placed_at')
->first();