From dcdc998eee0de65a7d52b7368c2419ff2b1a4c2c Mon Sep 17 00:00:00 2001 From: Konstantinos Arvanitakis Date: Fri, 18 Sep 2026 00:52:38 +0300 Subject: [PATCH] Feat: Updating Shipping Method with new variables, for correct box env vars --- config/shippingCarriers/boxnow.php | 14 +++++++ .../ShippingMethodListExtension.php | 16 ++++--- .../ShippingMethodResourceExtension.php | 22 ++++++++-- .../Filament/Pages/ManageShippingRates.php | 42 ++++++++++++++++++- 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/config/shippingCarriers/boxnow.php b/config/shippingCarriers/boxnow.php index 672b95f..9a011ad 100644 --- a/config/shippingCarriers/boxnow.php +++ b/config/shippingCarriers/boxnow.php @@ -13,12 +13,25 @@ | | Set these via environment variables — never commit real values. | +| Box Now has two environments (see their Partner API manual, section 2): +| Stage/Sandbox for testing, Production once live. Each has its own +| client_id/client_secret pair and its own base_url/location_api_url — +| there is no shared "switch an env var" flag, since stage credentials +| don't work against the production host or vice versa. +| | BOXNOW_BASE_URL Root REST endpoint for delivery-requests/parcels. | BOXNOW_LOCATION_API_URL Separate, faster endpoint for origins/destinations | lookups (Box Now recommends this over the main | base URL for those two calls specifically). | BOXNOW_CLIENT_ID OAuth2 client id. | BOXNOW_CLIENT_SECRET OAuth2 client secret. +| BOXNOW_PARTNER_ID Numeric partnerId Box Now issues alongside your +| credentials. NOT used for REST API authentication +| (BoxNowClient authenticates with client_id/ +| client_secret alone) — this is only consumed by +| the client-side Destination Map widget config +| (_bn_map_widget_config.partnerId), confirmed +| against Box Now's own WooCommerce plugin source. | BOXNOW_ORIGIN_LOCATION_ID Your warehouse's Box Now locationId, used as | the pickup origin on every delivery request. | BOXNOW_SENDER_* Static sender contact details reused on every @@ -33,6 +46,7 @@ return [ 'client_id' => env('BOXNOW_CLIENT_ID'), 'client_secret' => env('BOXNOW_CLIENT_SECRET'), + 'partner_id' => env('BOXNOW_PARTNER_ID'), 'origin_location_id' => env('BOXNOW_ORIGIN_LOCATION_ID'), diff --git a/src/Shipping/Extensions/ShippingMethodListExtension.php b/src/Shipping/Extensions/ShippingMethodListExtension.php index 3eb5b4b..61ad91c 100644 --- a/src/Shipping/Extensions/ShippingMethodListExtension.php +++ b/src/Shipping/Extensions/ShippingMethodListExtension.php @@ -12,11 +12,15 @@ use Lunar\Shipping\Filament\Resources\ShippingMethodResource; /** * ListShippingMethod::getDefaultHeaderActions() builds its CreateAction's - * form inline (calling ShippingMethodResource::getDriverFormComponent() - * directly, a hardcoded 2-option Select) rather than through the resource's - * own extendForm() pipeline, so ShippingMethodResourceExtension's driver - * fix never reaches it. Re-declares the same create-action form with a - * dynamic driver Select instead. + * form inline (calling ShippingMethodResource::getDriverFormComponent()/ + * getNameFormComponent() directly, hardcoded vendor components) rather + * than through the resource's own extendForm() pipeline, so neither + * ShippingMethodResourceExtension's driver Select nor its translated + * `name` field ever reached this action — `name`'s plain-string TextInput + * in particular used to insert a raw string into the now-JSON `name` + * column, crashing with a Postgres "invalid input syntax for type json" + * error on every create. Re-declares the same create-action form with + * both fixes reapplied instead. */ class ShippingMethodListExtension extends BaseExtension { @@ -25,7 +29,7 @@ class ShippingMethodListExtension extends BaseExtension foreach ($actions as $action) { if ($action instanceof CreateAction) { $action->schema([ - ShippingMethodResource::getNameFormComponent(), + ShippingMethodResourceExtension::translatedNameField(), Group::make([ ShippingMethodResource::getCodeFormComponent(), $this->driverSelect(), diff --git a/src/Shipping/Extensions/ShippingMethodResourceExtension.php b/src/Shipping/Extensions/ShippingMethodResourceExtension.php index 3506fa9..1da2c8a 100644 --- a/src/Shipping/Extensions/ShippingMethodResourceExtension.php +++ b/src/Shipping/Extensions/ShippingMethodResourceExtension.php @@ -43,7 +43,7 @@ class ShippingMethodResourceExtension extends ResourceExtension { return array_map(function (Component $component) { if (method_exists($component, 'getName') && $component->getName() === 'name') { - return $this->translatedNameField(); + return self::translatedNameField(); } if (in_array(HasChildComponents::class, class_uses_recursive($component), true)) { @@ -68,14 +68,28 @@ class ShippingMethodResourceExtension extends ResourceExtension * the model attribute is a string on the way in and out, only ever * an array while Filament's schema state holds it. */ - private function translatedNameField(): TranslatedText + /** + * Also called directly by Modules\Core\Shipping\Extensions\ + * ShippingMethodListExtension — the create action's form is built + * inline by the vendor's ListShippingMethod page rather than through + * this class's own extendForm() pipeline, so it needs the same + * translated field wired in separately. + */ + public static function translatedNameField(): TranslatedText { $field = TranslatedText::make('name') ->label('Name') ->required() ->afterStateHydrated(function (TranslatedText $component, $state) { - $decoded = json_decode((string) $state, true); - $component->state(is_array($decoded) ? $decoded : []); + // On create there is no record yet, so Filament hydrates + // this from the field's own default/current state — already + // an array (or null), never the raw JSON string edit gets + // from the model attribute. Only decode when it's a string. + if (is_string($state)) { + $state = json_decode($state, true); + } + + $component->state(is_array($state) ? $state : []); }) ->dehydrateStateUsing(fn ($state) => json_encode(is_array($state) ? $state : [])); diff --git a/src/Shipping/Filament/Pages/ManageShippingRates.php b/src/Shipping/Filament/Pages/ManageShippingRates.php index 000c2cf..b0fa633 100644 --- a/src/Shipping/Filament/Pages/ManageShippingRates.php +++ b/src/Shipping/Filament/Pages/ManageShippingRates.php @@ -4,6 +4,7 @@ namespace Modules\Core\Shipping\Filament\Pages; use Filament\Schemas\Schema; use Filament\Schemas\Components\Utilities\Get; +use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; @@ -11,6 +12,7 @@ use Illuminate\Database\Eloquent\Model; use Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages\ManageShippingRates as BaseManageShippingRates; use Lunar\Shipping\Models\ShippingMethod; use Lunar\Shipping\Models\ShippingRate; +use Modules\Core\Shipping\Support\ShippingMethodName; /** * Bound in place of the vendor ManageShippingRates page via the container @@ -33,6 +35,16 @@ use Lunar\Shipping\Models\ShippingRate; * null-guard, which crashes on any rate with no basePrices row — routine * for a live rate that has never had a fallback price configured. Same * logic, just null-safe. + * + * Also replaces the vendor's `shipping_method_id` Select, which uses + * ->relationship(titleAttribute: 'name') — Filament builds that option + * list with `orderBy('name')`/`pluck('name', ...)` against the DB, but + * ShippingMethod.name is now a locale-keyed JSON column (see database/ + * migrations/..._make_shipping_methods_name_translatable.php) that + * Postgres has no default ordering operator for, crashing with + * "could not identify an ordering operator for type json" the moment + * this page loads. Resolved app-side instead via ShippingMethodName, + * same as every other read site for this column. */ class ManageShippingRates extends BaseManageShippingRates { @@ -41,10 +53,30 @@ class ManageShippingRates extends BaseManageShippingRates $schema = parent::form($schema); return $schema->components( - $this->labelPriceFieldsAsFallbackWhenLive($schema->getComponents()) + $this->labelPriceFieldsAsFallbackWhenLive( + $this->replaceShippingMethodField($schema->getComponents()) + ) ); } + private function replaceShippingMethodField(array $components): array + { + return array_map(function ($component) { + if (method_exists($component, 'getName') && $component->getName() === 'shipping_method_id') { + return Select::make('shipping_method_id') + ->label($component->getLabel()) + ->required() + ->live() + ->options(fn () => ShippingMethod::all() + ->mapWithKeys(fn (ShippingMethod $method) => [$method->id => ShippingMethodName::resolve($method)])) + ->searchable() + ->columnSpan(2); + } + + return $component; + }, $components); + } + private function labelPriceFieldsAsFallbackWhenLive(array $components): array { $isLive = fn (Get $get) => static::methodChargeBy($get('shipping_method_id')) === 'live'; @@ -86,6 +118,14 @@ class ManageShippingRates extends BaseManageShippingRates return $table->columns( array_map(function ($column) { + if (method_exists($column, 'getName') && $column->getName() === 'shippingMethod.name') { + return TextColumn::make('shippingMethod.name') + ->label(__('lunarpanel.shipping::relationmanagers.shipping_rates.table.shipping_method.label')) + ->state(fn (ShippingRate $record) => $record->shippingMethod + ? ShippingMethodName::resolve($record->shippingMethod) + : null); + } + if (method_exists($column, 'getName') && $column->getName() === 'basePrices.0') { return TextColumn::make('basePrices.0') ->label(__('lunarpanel.shipping::relationmanagers.shipping_rates.table.price.label'))