Fix: Updates to OrderFullfilmentServices and box now clients, order views and checkout services
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Checkout\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Thrown by CheckoutService::selectBoxNowLocker() when the cart has no
|
||||
* shipping address yet to attach the chosen locker's meta to — the
|
||||
* storefront must call setShippingAddress() first.
|
||||
*/
|
||||
class NoShippingAddressException extends RuntimeException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Cannot select a Box Now locker before a shipping address is set.');
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user