generated from boboko/starter
137 lines
5.2 KiB
PHP
137 lines
5.2 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Http\Controllers\Account;
|
||
|
|
|
||
|
|
use App\Http\Controllers\Controller;
|
||
|
|
use Illuminate\Http\RedirectResponse;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\Auth;
|
||
|
|
use Illuminate\Validation\Rule;
|
||
|
|
use Illuminate\View\View;
|
||
|
|
use Lunar\Models\Country;
|
||
|
|
use Lunar\Models\State;
|
||
|
|
use Modules\Core\Customer\Services\CustomerAccountService;
|
||
|
|
use Modules\Core\Privacy\Services\PrivacyService;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The profile page: name, invoice details, the one address, and account
|
||
|
|
* deletion. The customer keeps a single address, flagged as both the
|
||
|
|
* shipping and billing default, so allowing several later is a UI change
|
||
|
|
* only (the data is already Lunar's own addresses table).
|
||
|
|
*/
|
||
|
|
class AccountController extends Controller
|
||
|
|
{
|
||
|
|
// Single-country store, same as CheckoutController::STORE_COUNTRY_ISO3.
|
||
|
|
private const STORE_COUNTRY_ISO3 = 'GRC';
|
||
|
|
|
||
|
|
private const ADDRESS_FIELDS = ['line_one', 'city', 'postcode', 'state', 'contact_phone'];
|
||
|
|
|
||
|
|
public function __construct(
|
||
|
|
private readonly CustomerAccountService $account,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
public function show(string $locale, Request $request): View
|
||
|
|
{
|
||
|
|
$user = $request->user();
|
||
|
|
$customer = $this->account->customer($user);
|
||
|
|
|
||
|
|
return view('account.show', [
|
||
|
|
'user' => $user,
|
||
|
|
'customer' => $customer,
|
||
|
|
'address' => $this->defaultAddress($user),
|
||
|
|
'regions' => State::where('country_id', $this->storeCountry()->id)->orderBy('name')->get(['id', 'name']),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function update(string $locale, Request $request): RedirectResponse
|
||
|
|
{
|
||
|
|
$user = $request->user();
|
||
|
|
$country = $this->storeCountry();
|
||
|
|
|
||
|
|
// The address is all-or-nothing: typing any part of it makes the rest
|
||
|
|
// (and the name, which Lunar requires on every address) required.
|
||
|
|
$anyAddressField = implode(',', self::ADDRESS_FIELDS);
|
||
|
|
|
||
|
|
$data = $request->validate([
|
||
|
|
'first_name' => ['nullable', 'string', 'max:255', 'required_with:'.$anyAddressField],
|
||
|
|
'last_name' => ['nullable', 'string', 'max:255', 'required_with:'.$anyAddressField],
|
||
|
|
'invoice' => ['boolean'],
|
||
|
|
'company_name' => ['nullable', 'string', 'max:255', 'required_if_accepted:invoice'],
|
||
|
|
'tax_identifier' => ['nullable', 'digits:9', 'required_if_accepted:invoice'],
|
||
|
|
'line_one' => ['nullable', 'string', 'max:255', 'required_with:'.$anyAddressField],
|
||
|
|
'city' => ['nullable', 'string', 'max:255', 'required_with:'.$anyAddressField],
|
||
|
|
'postcode' => ['nullable', 'regex:/^\d{3}\s?\d{2}$/', 'required_with:'.$anyAddressField],
|
||
|
|
'state' => [
|
||
|
|
'nullable',
|
||
|
|
'string',
|
||
|
|
'required_with:'.$anyAddressField,
|
||
|
|
Rule::exists((new State)->getTable(), 'name')->where('country_id', $country->id),
|
||
|
|
],
|
||
|
|
'contact_phone' => ['nullable', 'string', 'max:30'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$invoice = $request->boolean('invoice');
|
||
|
|
|
||
|
|
$customer = $this->account->updateProfile($user, [
|
||
|
|
'first_name' => $data['first_name'] ?? null,
|
||
|
|
'last_name' => $data['last_name'] ?? null,
|
||
|
|
'company_name' => $invoice ? $data['company_name'] : null,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Written directly: core's updateProfile() allowlists `vat_no`, but
|
||
|
|
// Lunar's column is `tax_identifier`, so it can't go through there yet.
|
||
|
|
$customer->update(['tax_identifier' => $invoice ? $data['tax_identifier'] : null]);
|
||
|
|
|
||
|
|
if (filled($data['line_one'] ?? null)) {
|
||
|
|
$addressData = [
|
||
|
|
...collect($data)->only(self::ADDRESS_FIELDS)->all(),
|
||
|
|
'first_name' => $data['first_name'],
|
||
|
|
'last_name' => $data['last_name'],
|
||
|
|
'country_id' => $country->id,
|
||
|
|
'contact_email' => $user->email,
|
||
|
|
'shipping_default' => true,
|
||
|
|
'billing_default' => true,
|
||
|
|
];
|
||
|
|
|
||
|
|
$existing = $this->defaultAddress($user);
|
||
|
|
|
||
|
|
$existing
|
||
|
|
? $this->account->updateAddress($user, $existing->id, $addressData)
|
||
|
|
: $this->account->createAddress($user, $addressData);
|
||
|
|
}
|
||
|
|
|
||
|
|
return redirect()->route('account')->with('status', __('storefront.account.saved'));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Self-service deletion: opens core's 30-day grace-period erasure request
|
||
|
|
* (which blocks the login right away) and logs out. Logging back in within
|
||
|
|
* the grace period cancels it; see core's docs/privacy.md.
|
||
|
|
*/
|
||
|
|
public function destroy(string $locale, Request $request, PrivacyService $privacy): RedirectResponse
|
||
|
|
{
|
||
|
|
$user = $request->user();
|
||
|
|
|
||
|
|
$privacy->requestErasureForUser($user, $user);
|
||
|
|
|
||
|
|
Auth::logout();
|
||
|
|
|
||
|
|
$request->session()->invalidate();
|
||
|
|
$request->session()->regenerateToken();
|
||
|
|
|
||
|
|
return redirect()->route('login')->with('status', __('storefront.account.deletion_requested'));
|
||
|
|
}
|
||
|
|
|
||
|
|
private function defaultAddress($user)
|
||
|
|
{
|
||
|
|
$addresses = collect($this->account->addresses($user));
|
||
|
|
|
||
|
|
return $addresses->firstWhere('shipping_default', true) ?? $addresses->first();
|
||
|
|
}
|
||
|
|
|
||
|
|
private function storeCountry(): Country
|
||
|
|
{
|
||
|
|
return Country::where('iso3', self::STORE_COUNTRY_ISO3)->firstOrFail();
|
||
|
|
}
|
||
|
|
}
|