Fix: Updates to OrderFullfilmentServices and box now clients, order views and checkout services

This commit is contained in:
2026-09-18 00:54:43 +03:00
parent dcdc998eee
commit 12aaa43f10
15 changed files with 450 additions and 18 deletions
+84 -1
View File
@@ -17,6 +17,7 @@ use Modules\Core\Checkout\Events\RecoveryConsentSet;
use Modules\Core\Checkout\Events\ShippingAddressSet;
use Modules\Core\Checkout\Events\ShippingOptionSelected;
use Modules\Core\Checkout\Exceptions\InvalidShippingOptionException;
use Modules\Core\Checkout\Exceptions\NoShippingAddressException;
use Modules\Core\Checkout\Exceptions\TermsNotAcceptedException;
use Modules\Core\Checkout\Exceptions\UnknownPaymentTypeException;
use Modules\Core\Payment\DTOs\PaymentResult;
@@ -49,9 +50,35 @@ class CheckoutService
private readonly PaymentMethodCache $paymentMethods,
) {}
/**
* Lunar\Actions\Carts\AddAddress (behind Cart::setShippingAddress())
* always deletes the cart's existing shipping address row and inserts
* a brand new one — it has no notion of "update in place." Every field
* on the new row therefore starts blank, including `meta`, which is
* where selectBoxNowLocker() stores the shopper's chosen locker. Since
* the checkout page autosaves the address form on every field change
* (not just once), any edit made after picking a locker — even an
* unrelated one, like delivery instructions — silently wiped the
* locker choice by recreating the row out from under it.
*
* Carries the previous row's box_now_locker forward onto the new one
* so the two features don't stomp on each other, without needing
* Lunar's own AddAddress action to change. The old row's meta is read
* BEFORE Lunar deletes it, since afterward there's nothing left to
* read.
*/
public function setShippingAddress(array|Addressable $address): Cart
{
$cart = $this->cart->currentOrCreate()->setShippingAddress($address);
$cartBefore = $this->cart->currentOrCreate();
$boxNowLocker = $cartBefore->shippingAddress?->meta['box_now_locker'] ?? null;
$cart = $cartBefore->setShippingAddress($address);
if ($boxNowLocker !== null) {
$newAddress = $cart->shippingAddress;
$newAddress->meta = [...($newAddress->meta?->toArray() ?? []), 'box_now_locker' => $boxNowLocker];
$newAddress->save();
}
Event::dispatch(new ShippingAddressSet($cart, $address));
@@ -141,11 +168,67 @@ class CheckoutService
$cart = $cartBefore->setShippingOption($option);
// Switching away from Box Now leaves a stale box_now_locker on the
// address's meta (see setShippingAddress()'s own docblock for why
// it survives address-row recreation) — irrelevant while a
// different method is selected, but wrong if the shopper later
// switches BACK to Box Now and it resurfaces as if still chosen,
// possibly for a locker that no longer exists/fits. Cleared here,
// the one place that knows the method just changed.
if ($identifier !== 'box-now') {
$address = $cart->shippingAddress;
if ($address && isset($address->meta['box_now_locker'])) {
$meta = $address->meta->toArray();
unset($meta['box_now_locker']);
$address->meta = $meta;
$address->save();
}
}
Event::dispatch(new ShippingOptionSelected($cart, $option));
return $cart;
}
/**
* Records the shopper's chosen Box Now locker on the cart's shipping
* address (Cart\Addresses::shippingAddress()->meta['box_now_locker']),
* not on the cart itself — Lunar\Pipelines\Order\Creation\
* CreateOrderAddresses copies every cart address's full attributes
* (meta included) onto the new order address when the order is placed,
* so this is what Modules\Core\Shipping\Carriers\BoxNow\
* BoxNowFulfillmentService and Modules\Core\Shipping\Extensions\
* OrderViewExtension already expect to find at
* $order->shippingAddress->meta['box_now_locker']['locationId'].
*
* No validation against Box Now's own /destinations list here — this
* mirrors setShippingAddress()'s leniency (see its own docblock/the
* class-level note on required-field enforcement happening at the
* payment gate, not mid-checkout). An invalid/stale locationId still
* surfaces later, at BoxNowFulfillmentService::createShipment() time.
*
* @throws NoShippingAddressException if the cart has no shipping
* address yet
*/
public function selectBoxNowLocker(array $locker): Cart
{
$cart = $this->cart->currentOrCreate();
$address = $cart->shippingAddress;
if (! $address) {
throw new NoShippingAddressException();
}
$address->meta = [
...($address->meta?->toArray() ?? []),
'box_now_locker' => $locker,
];
$address->save();
return $cart;
}
/**
* Every payment method currently offered to the storefront, ordered by
* Modules\Core\Payment\Models\PaymentMethod::position — a row is