Fix: Move carrier live-pricing choice onto ShippingMethod.charge_by

Reverts the earlier per-rate pricing_mode column in favor of extending Lunar's existing charge_by field (cart_total/weight) with a third "live" option, gated by a SupportsLivePricing capability check on the driver. Adds a shared ResolvesFixedPricing trait so any carrier driver can fall back to Lunar's normal price-break resolution, matching the vendor ShipBy driver's own charge_by handling instead of introducing a separate mechanism. Also fixes an incorrect Get() path in the admin form that silently hid the new "live" option.
This commit is contained in:
2026-07-19 02:24:10 +03:00
parent 4acabe4185
commit d34e450526
6 changed files with 148 additions and 155 deletions
@@ -2,16 +2,13 @@
namespace Modules\Core\Shipping\Filament\Pages;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Lunar\Shipping\Facades\Shipping;
use Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages\ManageShippingRates as BaseManageShippingRates;
use Lunar\Shipping\Models\ShippingMethod;
use Lunar\Shipping\Models\ShippingRate;
use Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages\ManageShippingRates as BaseManageShippingRates;
use Modules\Core\Shipping\Contracts\SupportsLivePricing;
/**
* Bound in place of the vendor ManageShippingRates page via the container
@@ -20,11 +17,13 @@ use Modules\Core\Shipping\Contracts\SupportsLivePricing;
* ShippingZoneResource::getPages() — is untouched; the container simply
* hands back this subclass whenever the vendor class is resolved.
*
* Adds a per-rate "Pricing" toggle (live API vs. fixed price) for methods
* whose driver supports live pricing (see SupportsLivePricing). Rates on
* methods without live pricing behave exactly as the vendor page always did
* — no toggle shown, price fields always required, vendor save logic used
* as-is.
* Hides the price / price-break fields for a rate whose method has
* charge_by = "live" (see ShippingMethodResourceExtension, which adds that
* option to methods whose driver supports live pricing) — those fields
* would otherwise be dead configuration the driver never reads. Pricing
* strategy (cart_total / weight / live) stays entirely on the Shipping
* Method, matching Lunar's own existing charge_by convention; nothing new
* is stored on the rate itself.
*/
class ManageShippingRates extends BaseManageShippingRates
{
@@ -33,56 +32,13 @@ class ManageShippingRates extends BaseManageShippingRates
$form = parent::form($form);
return $form->schema(
$this->insertPricingModeFieldAfterShippingMethod(
$this->hidePriceFieldsWhenLive($form->getComponents())
)
$this->hidePriceFieldsWhenLive($form->getComponents())
);
}
/**
* Insert the "Pricing" field immediately after "shipping_method_id" so
* it reads as a pair, rather than appending it elsewhere in the form.
*/
private function insertPricingModeFieldAfterShippingMethod(array $components): array
{
$result = [];
foreach ($components as $component) {
$result[] = $component;
if (method_exists($component, 'getName') && $component->getName() === 'shipping_method_id') {
$result[] = $this->pricingModeField();
}
}
return $result;
}
private function pricingModeField(): Select
{
return Select::make('pricing_mode')
->label('Pricing')
->options([
'live' => 'Use live API pricing',
'fixed' => 'Use a fixed price',
])
->default('live')
->live()
->visible(fn (Get $get) => static::methodHasLivePricing($get('shipping_method_id')))
->columnSpan(2);
}
/**
* Hide the vendor's price / price-break fields whenever this rate is
* set to live pricing — they'd otherwise be dead configuration,
* silently ignored by the driver. Only the base "price" field was ever
* required by the vendor form; the "prices" repeater (price breaks) is
* always optional, so its required() state is left untouched.
*/
private function hidePriceFieldsWhenLive(array $components): array
{
$isFixedOrNotLiveCapable = fn (Get $get) => ! static::methodHasLivePricing($get('shipping_method_id'))
|| $get('pricing_mode') === 'fixed';
$isNotLive = fn (Get $get) => static::methodChargeBy($get('shipping_method_id')) !== 'live';
foreach ($components as $component) {
if (! method_exists($component, 'getName')) {
@@ -90,14 +46,11 @@ class ManageShippingRates extends BaseManageShippingRates
}
if ($component->getName() === 'price') {
$component->visible($isFixedOrNotLiveCapable)
->required($isFixedOrNotLiveCapable)
->dehydrated(true);
$component->visible($isNotLive)->required($isNotLive)->dehydrated(true);
}
if ($component->getName() === 'prices') {
$component->visible($isFixedOrNotLiveCapable)
->dehydrated(true);
$component->visible($isNotLive)->dehydrated(true);
}
}
@@ -114,7 +67,7 @@ class ManageShippingRates extends BaseManageShippingRates
return TextColumn::make('basePrices.0')
->label(__('lunarpanel.shipping::relationmanagers.shipping_rates.table.price.label'))
->formatStateUsing(function ($state, ShippingRate $record) {
if (static::methodHasLivePricing($record->shipping_method_id) && $record->pricing_mode === 'live') {
if (static::methodChargeBy($record->shipping_method_id) === 'live') {
return 'Live API pricing';
}
@@ -129,37 +82,23 @@ class ManageShippingRates extends BaseManageShippingRates
protected static function saveShippingRate(?ShippingRate $shippingRate = null, array $data = []): void
{
$isLive = static::methodHasLivePricing($data['shipping_method_id'] ?? $shippingRate?->shipping_method_id)
&& ($data['pricing_mode'] ?? 'live') === 'live';
$shippingRate->pricing_mode = $isLive ? 'live' : 'fixed';
$shippingRate->save();
if ($isLive) {
if (static::methodChargeBy($data['shipping_method_id'] ?? $shippingRate?->shipping_method_id) === 'live') {
return;
}
parent::saveShippingRate($shippingRate, $data);
}
protected static function methodHasLivePricing(ShippingMethod|int|string|null $method): bool
protected static function methodChargeBy(ShippingMethod|int|string|null $method): ?string
{
if (blank($method)) {
return false;
return null;
}
if (! $method instanceof ShippingMethod) {
$method = ShippingMethod::find($method);
}
if (! $method) {
return false;
}
try {
return Shipping::driver($method->driver) instanceof SupportsLivePricing;
} catch (\InvalidArgumentException) {
return false;
}
return $method?->data['charge_by'] ?? null;
}
}