generated from boboko/starter
158 lines
6.7 KiB
PHP
158 lines
6.7 KiB
PHP
<?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);
|
||
|
|
}
|
||
|
|
}
|