Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbea4a8d69 |
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [0.29.1] - 2026-09-30
|
||||
### Fixed
|
||||
- Checkout crashed ("Driver [acs] not supported", a 500) when a shipping
|
||||
method pointed at a driver that is no longer registered — e.g. a carrier
|
||||
commented out while its shipping method still exists in the admin. Such
|
||||
methods are now left out of the options offered at checkout
|
||||
(`Modules\Core\Shipping\Support\RegisteredDriverRateResolver`); the admin
|
||||
is unchanged.
|
||||
- The Box Now locker map at checkout only appeared when the shipping
|
||||
method's **code** was exactly `box-now`; a method with any other code
|
||||
(e.g. `boxnow`) never showed it, and switching options cleared the chosen
|
||||
locker. Box Now is now recognized by its driver being a `BoxNowRateDriver`
|
||||
(`Modules\Core\Shipping\Support\BoxNowShipping`), not by any name. The
|
||||
admin order page's Create Shipment (locker + boxes form) and the
|
||||
shipment's locker line use the same check.
|
||||
|
||||
## [0.29.0] - 2026-09-30
|
||||
### Added
|
||||
- **ELTA Courier** carrier (`elta` shipping driver): rates, shipment creation,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "boboko/core",
|
||||
"description": "Core module — authentication and shared panel behaviour",
|
||||
"type": "library",
|
||||
"version": "0.29.0",
|
||||
"version": "0.29.1",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Core\\": "src/"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@boboko/core",
|
||||
"version": "0.29.0",
|
||||
"version": "0.29.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Portable Stimulus controllers and styles for boboko-core's cart + checkout module. Installed as a real npm dependency (file:../boboko-core in dev, a tagged git install in prod) so a consuming app's `npm install` resolves this package's own dependencies (leaflet, @hotwired/stimulus) transitively, the same way `composer update boboko/*` does for PHP. See CONTRIBUTE.md's \"JS/CSS: a real npm package\" section.",
|
||||
|
||||
@@ -123,7 +123,7 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
async selectShipping(event) {
|
||||
this.toggleBoxNowLocker(event.target.value)
|
||||
this.toggleBoxNowLocker(event.target.dataset.boxNow === 'true')
|
||||
|
||||
// Tracked so flush() can await it — nothing else stops "place order"
|
||||
// (a separate, unrelated click) from racing ahead of this request.
|
||||
@@ -136,15 +136,17 @@ export default class extends Controller {
|
||||
// re-renders wholesale on every shipping-option change — so its own
|
||||
// Stimulus controller reconnects fresh each time and has no memory of
|
||||
// which option was previously selected. This is the one place that
|
||||
// knows the newly-chosen option's identifier, so it also owns
|
||||
// showing/hiding the picker.
|
||||
toggleBoxNowLocker(identifier) {
|
||||
// knows the newly-chosen option, so it also owns showing/hiding the
|
||||
// picker. Whether an option is Box Now comes from the server
|
||||
// (data-box-now, by the method's driver) — the option's value is the
|
||||
// merchant-typed method code, which needn't be "box-now".
|
||||
toggleBoxNowLocker(isBoxNow) {
|
||||
const picker = this.shippingOptionsTarget.querySelector('#bbk-box-now-locker')
|
||||
if (!picker) return
|
||||
|
||||
const controller = this.application.getControllerForElementAndIdentifier(picker, 'bbk-box-now-locker')
|
||||
|
||||
if (identifier === 'box-now') {
|
||||
if (isBoxNow) {
|
||||
controller?.show()
|
||||
} else {
|
||||
controller?.hide()
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
$shippingAddress, $shippingOptions come from the controller / page scope.
|
||||
--}}
|
||||
@php($selected = $shippingAddress?->shipping_option)
|
||||
{{-- Box Now by the method's driver, not its (merchant-typed) code --}}
|
||||
@php($boxNowCodes = \Modules\Core\Shipping\Support\BoxNowShipping::codes())
|
||||
|
||||
{{-- Rate resolution needs country (always Greece here) + postcode; until a
|
||||
postcode is saved there's nothing to quote against yet. --}}
|
||||
@@ -34,6 +36,7 @@
|
||||
type="radio"
|
||||
name="shipping_option"
|
||||
value="{{ $option->identifier }}"
|
||||
data-box-now="{{ in_array($option->identifier, $boxNowCodes, true) ? 'true' : 'false' }}"
|
||||
@checked($selected === $option->identifier)
|
||||
data-action="change->bbk-checkout-form#selectShipping"
|
||||
>
|
||||
@@ -73,7 +76,7 @@
|
||||
data-bbk-box-now-locker-select-label-value="{{ __('checkout.page.box_now_locker_select') }}"
|
||||
data-bbk-box-now-locker-selected-label-value="{{ __('checkout.page.box_now_locker_selected') }}"
|
||||
data-bbk-box-now-locker-no-results-value="{{ __('checkout.page.box_now_locker_no_results') }}"
|
||||
@if ($selected !== 'box-now') hidden @endif
|
||||
@if (! in_array($selected, $boxNowCodes, true)) hidden @endif
|
||||
>
|
||||
<p class="bbk-checkout-box-now-locker-label">
|
||||
{{ __('checkout.page.box_now_locker_label') }}
|
||||
|
||||
@@ -29,6 +29,7 @@ use Modules\Core\Payment\Models\PaymentMethod;
|
||||
use Modules\Core\Payment\Services\PaymentDriverRegistry;
|
||||
use Modules\Core\Payment\Services\PaymentMethodCache;
|
||||
use Modules\Core\Shipping\Contracts\SupportsCashCollection;
|
||||
use Modules\Core\Shipping\Support\BoxNowShipping;
|
||||
use Modules\Core\Shipping\Support\FulfillmentType;
|
||||
|
||||
/**
|
||||
@@ -181,7 +182,7 @@ class CheckoutService
|
||||
// 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') {
|
||||
if (! BoxNowShipping::isOption($identifier)) {
|
||||
$address = $cart->shippingAddress;
|
||||
|
||||
if ($address && isset($address->meta['box_now_locker'])) {
|
||||
|
||||
@@ -19,6 +19,7 @@ use Modules\Core\Shipping\Enums\TrackingStatus;
|
||||
use Modules\Core\Shipping\Events\ShipmentStatusUpdatedByCarrier;
|
||||
use Modules\Core\Shipping\Filament\Resources\ShipmentResource;
|
||||
use Modules\Core\Shipping\Models\Shipment;
|
||||
use Modules\Core\Shipping\Support\BoxNowShipping;
|
||||
|
||||
/**
|
||||
* Adds a "Shipments" section to the order page's main column — previously
|
||||
@@ -224,7 +225,7 @@ class OrderShipmentsExtension extends ViewPageExtension
|
||||
{
|
||||
$parts = ['Created '.$record->created_at->format('Y-m-d H:i')];
|
||||
|
||||
if ($record->carrier === 'box-now' && $locker = $record->meta['locker_id'] ?? null) {
|
||||
if (BoxNowShipping::isDriver($record->carrier) && $locker = $record->meta['locker_id'] ?? null) {
|
||||
$parts[] = 'Locker '.$locker;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ use Modules\Core\Shipping\Contracts\SupportsVoucherLookup;
|
||||
use Modules\Core\Shipping\DTOs\ShipmentRequest;
|
||||
use Modules\Core\Shipping\Enums\ExtraService;
|
||||
use Modules\Core\Shipping\Models\Shipment;
|
||||
use Modules\Core\Shipping\Support\BoxNowShipping;
|
||||
use Modules\Core\Shipping\Support\WeightCalculator;
|
||||
use Throwable;
|
||||
|
||||
@@ -122,7 +123,7 @@ class OrderViewExtension extends ViewPageExtension
|
||||
->modalSubmitActionLabel('Create Shipment')
|
||||
->schema(function (Order $record) {
|
||||
$carrier = $this->service()->carrierFor($record);
|
||||
$isBoxNow = $carrier === 'box-now';
|
||||
$isBoxNow = BoxNowShipping::isDriver($carrier);
|
||||
$lockerId = $record->shippingAddress?->meta['box_now_locker']['locationId'] ?? null;
|
||||
|
||||
if ($carrier === 'manual') {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Shipping\Support;
|
||||
|
||||
use Lunar\Shipping\Facades\Shipping;
|
||||
use Lunar\Shipping\Models\ShippingMethod;
|
||||
use Modules\Core\Shipping\Carriers\BoxNow\BoxNowRateDriver;
|
||||
|
||||
/**
|
||||
* Whether something is Box Now — a checkout option, a shipping method, a
|
||||
* driver key — by checking that the driver is a BoxNowRateDriver, never by
|
||||
* comparing names: an option's identifier is the merchant-typed method
|
||||
* code (may well not be "box-now"), and the driver may be registered under
|
||||
* any key.
|
||||
*/
|
||||
class BoxNowShipping
|
||||
{
|
||||
public static function isOption(?string $identifier): bool
|
||||
{
|
||||
return $identifier !== null
|
||||
&& self::isMethod(ShippingMethod::where('code', $identifier)->first());
|
||||
}
|
||||
|
||||
public static function isMethod(?ShippingMethod $method): bool
|
||||
{
|
||||
return self::isDriver($method?->driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shipping driver key (a method's `driver`, a shipment's `carrier`)
|
||||
* that resolves to Box Now. getSupportedDrivers() rather than
|
||||
* Shipping::driver(), which throws for a key no longer registered.
|
||||
*/
|
||||
public static function isDriver(?string $driver): bool
|
||||
{
|
||||
return $driver !== null
|
||||
&& Shipping::getSupportedDrivers()->get($driver) instanceof BoxNowRateDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Codes of every Box Now shipping method — for marking options in bulk.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function codes(): array
|
||||
{
|
||||
return ShippingMethod::all()
|
||||
->filter(fn (ShippingMethod $method) => self::isMethod($method))
|
||||
->pluck('code')
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Shipping\Support;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Lunar\Shipping\Facades\Shipping;
|
||||
use Lunar\Shipping\Resolvers\ShippingRateResolver;
|
||||
|
||||
/**
|
||||
* Lunar's rate resolver, minus rates whose shipping method points at a
|
||||
* driver that isn't registered anymore (e.g. a carrier commented out of
|
||||
* ShippingServiceProvider while its shipping method still exists). Lunar
|
||||
* would go on to call that driver to price the option and throw "Driver
|
||||
* [x] not supported", taking the whole checkout down; here the method is
|
||||
* simply not offered. It stays in the admin as it was.
|
||||
*/
|
||||
class RegisteredDriverRateResolver extends ShippingRateResolver
|
||||
{
|
||||
public function get(): Collection
|
||||
{
|
||||
$registered = Shipping::getSupportedDrivers();
|
||||
|
||||
return parent::get()
|
||||
->filter(fn ($rate) => $registered->has($rate->shippingMethod?->driver))
|
||||
->values();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Modules\Core\Shipping\Support;
|
||||
|
||||
use Lunar\Models\Contracts\Cart as CartContract;
|
||||
use Lunar\Shipping\Managers\ShippingManager as VendorShippingManager;
|
||||
use Lunar\Shipping\Resolvers\ShippingRateResolver;
|
||||
|
||||
/**
|
||||
* Drops lunarphp/table-rate-shipping's own generic drivers (free-shipping,
|
||||
@@ -28,6 +30,15 @@ use Lunar\Shipping\Managers\ShippingManager as VendorShippingManager;
|
||||
*/
|
||||
class ShippingManager extends VendorShippingManager
|
||||
{
|
||||
/**
|
||||
* Rates for a cart, leaving out shipping methods whose driver is no
|
||||
* longer registered (see RegisteredDriverRateResolver).
|
||||
*/
|
||||
public function shippingRates(?CartContract $cart = null): ShippingRateResolver
|
||||
{
|
||||
return new RegisteredDriverRateResolver($cart);
|
||||
}
|
||||
|
||||
public function getSupportedDrivers(): \Illuminate\Support\Collection
|
||||
{
|
||||
return collect($this->customCreators)
|
||||
|
||||
Reference in New Issue
Block a user