checkout process - start

This commit is contained in:
elvira
2026-09-08 20:41:49 +03:00
parent 7577426f49
commit 2d85591a43
12 changed files with 923 additions and 6 deletions
@@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Checkout;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Lunar\Models\Country;
use Modules\Core\Cart\Services\CartService;
use Modules\Core\Checkout\Exceptions\InvalidShippingOptionException;
use Modules\Core\Checkout\Services\CheckoutService;
/**
* The checkout page itself — one page, sections (contact / billing / shipping
* / shipping method), reviewed against boboko-core's CheckoutService. Deliberately
* stops short of payment for this slice (see project memory): every action here
* ends in a redirect back to `show()`, which just re-renders against whatever
* state the cart/CheckoutService is now in — there's no client-side state to
* keep in sync.
*
* Guest-only for now — the Contact section's login/register tab is UI only,
* not wired to Modules\Core\Auth\Services\UserOtpService yet.
*/
class CheckoutController extends Controller
{
public function __construct(
private readonly CartService $cart,
private readonly CheckoutService $checkout,
) {}
public function show(string $locale): View
{
$cart = $this->cart->current();
$lines = $cart ? $this->cart->activeLines($cart) : collect();
$shippingAddress = $cart?->shippingAddress;
$billingAddress = $cart?->billingAddress;
// Rate quoting needs a shipping address to resolve against (postcode/
// country for ACS/BoxNow live rates, table-rate-shipping's zones) —
// nothing to offer yet on a cart with no address saved.
$shippingOptions = $shippingAddress
? $this->checkout->getShippingOptions()
: collect();
return view('checkout::page', [
'cart' => $cart,
'lines' => $lines,
'billingAddress' => $billingAddress,
'shippingAddress' => $shippingAddress,
'shippingOptions' => $shippingOptions,
'countries' => Country::orderBy('name')->get(['id', 'name']),
]);
}
/**
* One form, two CheckoutService calls — billing and shipping are always
* both set together here. When "same as billing" is checked the shipping
* fields are disabled client-side (so the browser never submits them) and
* the billing values are reused for shipping too, plus delivery
* instructions.
*/
public function saveAddress(string $locale, Request $request): RedirectResponse
{
$sameAsBilling = $request->boolean('same_as_billing');
$rules = [
'contact_email' => ['required', 'email'],
'billing_first_name' => ['required', 'string', 'max:255'],
'billing_last_name' => ['required', 'string', 'max:255'],
'billing_company_name' => ['nullable', 'string', 'max:255'],
'billing_tax_identifier' => ['nullable', 'string', 'max:255'],
'billing_line_one' => ['required', 'string', 'max:255'],
'billing_line_two' => ['nullable', 'string', 'max:255'],
'billing_city' => ['required', 'string', 'max:255'],
'billing_state' => ['nullable', 'string', 'max:255'],
'billing_postcode' => ['required', 'string', 'max:20'],
'billing_country_id' => ['required', 'integer', 'exists:'.(new Country)->getTable().',id'],
'billing_contact_phone' => ['nullable', 'string', 'max:50'],
'shipping_delivery_instructions' => ['nullable', 'string', 'max:1000'],
];
if (! $sameAsBilling) {
$rules += [
'shipping_first_name' => ['required', 'string', 'max:255'],
'shipping_last_name' => ['required', 'string', 'max:255'],
'shipping_company_name' => ['nullable', 'string', 'max:255'],
'shipping_line_one' => ['required', 'string', 'max:255'],
'shipping_line_two' => ['nullable', 'string', 'max:255'],
'shipping_city' => ['required', 'string', 'max:255'],
'shipping_state' => ['nullable', 'string', 'max:255'],
'shipping_postcode' => ['required', 'string', 'max:20'],
'shipping_country_id' => ['required', 'integer', 'exists:'.(new Country)->getTable().',id'],
'shipping_contact_phone' => ['nullable', 'string', 'max:50'],
];
}
$data = $request->validate($rules);
$billing = [
'first_name' => $data['billing_first_name'],
'last_name' => $data['billing_last_name'],
'company_name' => $data['billing_company_name'] ?? null,
'tax_identifier' => $data['billing_tax_identifier'] ?? null,
'line_one' => $data['billing_line_one'],
'line_two' => $data['billing_line_two'] ?? null,
'city' => $data['billing_city'],
'state' => $data['billing_state'] ?? null,
'postcode' => $data['billing_postcode'],
'country_id' => $data['billing_country_id'],
'contact_email' => $data['contact_email'],
'contact_phone' => $data['billing_contact_phone'] ?? null,
];
$shipping = $sameAsBilling
? [...$billing, 'delivery_instructions' => $data['shipping_delivery_instructions'] ?? null]
: [
'first_name' => $data['shipping_first_name'],
'last_name' => $data['shipping_last_name'],
'company_name' => $data['shipping_company_name'] ?? null,
'line_one' => $data['shipping_line_one'],
'line_two' => $data['shipping_line_two'] ?? null,
'city' => $data['shipping_city'],
'state' => $data['shipping_state'] ?? null,
'postcode' => $data['shipping_postcode'],
'country_id' => $data['shipping_country_id'],
'contact_email' => $data['contact_email'],
'contact_phone' => $data['shipping_contact_phone'] ?? null,
'delivery_instructions' => $data['shipping_delivery_instructions'] ?? null,
];
$this->checkout->setBillingAddress($billing);
$this->checkout->setShippingAddress($shipping);
return redirect()->route('checkout.show', $locale);
}
public function selectShippingOption(string $locale, Request $request): RedirectResponse
{
$data = $request->validate([
'shipping_option' => ['required', 'string'],
]);
try {
$this->checkout->selectShippingOption($data['shipping_option']);
} catch (InvalidShippingOptionException) {
return back()->withErrors([
'shipping_option' => __('checkout.page.shipping_option_invalid'),
]);
}
return redirect()->route('checkout.show', $locale);
}
}