2026-07-03 13:46:54 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Auto-create Customer for User
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
| When enabled, creating a User automatically creates and attaches a bare
|
|
|
|
|
| Customer record (storefront passwordless-signup convention). Shops that
|
|
|
|
|
| drive the relationship the other way around (creating a Customer first,
|
|
|
|
|
| then a User for portal login) should set this to false.
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'auto_create_customer_for_user' => true,
|
|
|
|
|
|
2026-09-25 20:22:38 +03:00
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Privacy / GDPR data-subject requests
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
| 'providers' lists every Modules\Core\Privacy\Contracts\PersonalDataProvider
|
|
|
|
|
| that should be consulted for right-of-access/right-of-erasure requests. A
|
|
|
|
|
| module never needs to be known to core in advance — it just adds its own
|
|
|
|
|
| provider class here, the same way config('lunar.search.indexers') maps a
|
|
|
|
|
| model to its indexer. See docs/privacy.md.
|
|
|
|
|
|
|
|
|
|
|
| 'grace_period_days' is how long an erasure request stays cancellable
|
|
|
|
|
| (account deactivated, not yet erased) before it's actually processed by
|
|
|
|
|
| the privacy:process-erasure-requests scheduled command.
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'privacy' => [
|
|
|
|
|
'providers' => [
|
|
|
|
|
// ActivityLogDataProvider MUST run before AddressDataProvider —
|
|
|
|
|
// it resolves which activity_log rows belong to this customer
|
|
|
|
|
// (including ones keyed by an Address id) before
|
|
|
|
|
// AddressDataProvider hard-deletes those Address rows. See that
|
|
|
|
|
// provider's own class docblock.
|
|
|
|
|
\Modules\Core\Logging\Privacy\ActivityLogDataProvider::class,
|
|
|
|
|
\Modules\Core\Customer\Privacy\CustomerDataProvider::class,
|
|
|
|
|
\Modules\Core\Customer\Privacy\AddressDataProvider::class,
|
|
|
|
|
\Modules\Core\Order\Privacy\OrderDataProvider::class,
|
|
|
|
|
\Modules\Core\Cart\Privacy\CartDataProvider::class,
|
|
|
|
|
\Modules\Core\Review\Privacy\ReviewDataProvider::class,
|
|
|
|
|
\Modules\Core\Payment\Privacy\PaymentDataProvider::class,
|
|
|
|
|
\Modules\Core\Auth\Privacy\UserSessionDataProvider::class,
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
'grace_period_days' => 30,
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Cart Abandonment Threshold
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
| How long a cart (that hasn't converted to a placed order) can go without
|
|
|
|
|
| activity before Modules\Core\Cart\Filament\Resources\CartResource treats
|
|
|
|
|
| it as "Abandoned" rather than "Ongoing". Anything DateInterval::createFromDateString()
|
|
|
|
|
| accepts works, e.g. '1 hour', '30 minutes', '2 days'.
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'cart' => [
|
|
|
|
|
'abandoned_after' => '1 hour',
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|----------------------------------------------------------------------
|
|
|
|
|
| Unrecoverable Cap
|
|
|
|
|
|----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
| Beyond this age, a stale cart stops being treated as an active
|
|
|
|
|
| "Abandoned Cart"/"Abandoned Checkout" (Modules\Core\Cart\Services\
|
|
|
|
|
| CartLifecycleService) — too old to be a realistic recovery target
|
|
|
|
|
| (pricing/stock/tax likely stale by then). This is about the
|
|
|
|
|
| abandoned-cart pipeline only, not data retention — no rows are
|
|
|
|
|
| deleted or pruned based on this value.
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'unrecoverable_after' => '90 days',
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Order Return Window
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
| How many days after a carrier order is delivered (Order::fulfillment_status
|
|
|
|
|
| becomes 'return_window_open') before Modules\Core\Order\Commands\
|
|
|
|
|
| CloseExpiredReturnWindows auto-completes it, if no return was requested.
|
|
|
|
|
| Store-pickup orders have no return-window step and are unaffected by
|
|
|
|
|
| this value (see Modules\Core\Order\Listeners\CompleteOrderOnPickedUp).
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'order' => [
|
|
|
|
|
'return_window_days' => 14,
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Storefront OTP Login
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
| Modules\Core\Auth\Services\UserOtpService's passwordless login.
|
|
|
|
|
| max_attempts caps how many wrong codes a shopper can guess against ONE
|
|
|
|
|
| generated code before it's invalidated outright. generation_limit/
|
|
|
|
|
| generation_decay_minutes cap how often a NEW code can be requested for
|
|
|
|
|
| the same email — independent of max_attempts, since generating a fresh
|
|
|
|
|
| code also resets the guess count, so an attempt cap alone doesn't stop
|
|
|
|
|
| an attacker from just requesting a new code every few tries. This same
|
|
|
|
|
| limit is also what stands between a malicious/careless caller and
|
|
|
|
|
| mail-bombing one inbox.
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'auth' => [
|
|
|
|
|
'otp' => [
|
|
|
|
|
'max_attempts' => 5,
|
|
|
|
|
'generation_limit' => 3,
|
|
|
|
|
'generation_decay_minutes' => 10,
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// Modules\Core\Customer\Services\CustomerEmailChangeService — same
|
|
|
|
|
// shape/reasoning as auth.otp above, independent limits since this
|
|
|
|
|
// is a separate flow (changing an existing account's login email,
|
|
|
|
|
// not logging in).
|
|
|
|
|
'email_change' => [
|
|
|
|
|
'max_attempts' => 5,
|
|
|
|
|
'generation_limit' => 3,
|
|
|
|
|
'generation_decay_minutes' => 10,
|
|
|
|
|
'expiry_minutes' => 10,
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
|
2026-07-03 13:46:54 +00:00
|
|
|
];
|