listingPage->build( $listing, fn (array $query) => route('products', $query), ); return view('products.index', $data); } public function show(string $locale, int $id) { $product = $this->products->getById($id); abort_if($product === null, Response::HTTP_NOT_FOUND); $collection = $product['collections'][0] ?? null; $variantsData = $this->products->variantSummaries($product); $firstVariant = $product['variants'][0] ?? null; $option = $firstVariant['options'][0]['option'] ?? null; // The "Color" option type is the only one that writes a hex code into // meta (see boboko/core's ColorOptionType) — its presence is how we // tell a color option (swatches) from any other option (buttons). $optionIsColor = collect($product['variants']) ->contains(fn (array $variant) => !empty($variant['options'][0]['meta']['hex'] ?? null)); return view('product.show', [ 'collection' => $collection, 'product' => $product, 'option' => $option, 'optionIsColor' => $optionIsColor, 'variantsData' => $variantsData, ]); } /** * A storefront-owned, checkout-module-independent stock check — the * product page's "Add to cart" calls this first and only submits to the * checkout module's own add-to-cart endpoint once this says `ok`. Reads * the live Eloquent ProductVariant directly (not the Meilisearch index * ProductService otherwise reads from, which can lag behind an actual * sale until the next reindex) via the SAME method Lunar's own * CartLineStock validator calls, so this can never disagree with what * the module's own server-side check would decide. */ public function checkStock(string $locale, Request $request): JsonResponse { $data = $request->validate([ 'variant' => ['required', 'integer'], 'quantity' => ['nullable', 'integer', 'min:1'], ]); $variant = ProductVariant::find($data['variant']); $quantity = $data['quantity'] ?? 1; if ($variant === null) { return response()->json(['ok' => true]); } return response()->json([ 'ok' => $variant->canBeFulfilledAtQuantity($quantity), 'stock' => $variant->purchasable === 'always' ? null : $variant->getTotalInventory(), ]); } }