schema( $this->replaceChargeByField( $this->replaceDriverField($form->getComponents()) ) ); } /** * Extend the vendor's cart_total/weight charge_by Select with a third * "live" option — only offered when the currently selected driver * supports live pricing (see SupportsLivePricing). Picking it is what * tells the driver to call its carrier API instead of resolving a * price break. */ private function replaceChargeByField(array $components): array { return array_map(function (Component $component) { if (method_exists($component, 'getName') && $component->getName() === 'charge_by') { return $this->chargeBySelect(); } if (in_array(HasChildComponents::class, class_uses_recursive($component), true)) { $component->schema( $this->replaceChargeByField($component->getChildComponents()) ); } return $component; }, $components); } private function chargeBySelect(): Select { return Select::make('charge_by') ->label('Charge by') ->options(function (Get $get) { $options = [ 'cart_total' => 'Cart Total', 'weight' => 'Weight', ]; // "charge_by" is nested inside a Group with // ->statePath('data'), while "driver" sits one level up, at // the form root. Note: an *absolute* path here would need to // additionally account for the page's own form wrapper // (EditRecord::getFormStatePath() === 'data'), which relative // paths never cross — so "../driver" (relative) is the // correct, page-independent way to reach it, not an // absolute 'driver' string. if ($this->driverSupportsLivePricing($get('../driver'))) { $options['live'] = 'Live API pricing'; } return $options; }) ->live(); } private function driverSupportsLivePricing(?string $driver): bool { if (! $driver) { return false; } try { return Shipping::driver($driver) instanceof SupportsLivePricing; } catch (\InvalidArgumentException) { return false; } } public function extendTable(Table $table): Table { return $table->columns( array_map(function ($column) { if (method_exists($column, 'getName') && $column->getName() === 'driver') { return $this->driverColumn(); } return $column; }, $table->getColumns()) ); } private function driverColumn(): TextColumn { return TextColumn::make('driver') ->label('Type') ->formatStateUsing(fn ($state) => $this->driverLabel($state)); } private function driverLabel(string $key): string { $driver = collect(Shipping::getSupportedDrivers())->get($key); return $driver?->name() ?? $key; } /** * Recursively walk the form tree and replace the hardcoded driver * Select (nested inside Section > Group) with one listing every * registered driver, built-in or custom. * * @param array $components * @return array */ private function replaceDriverField(array $components): array { return array_map(function (Component $component) { if (method_exists($component, 'getName') && $component->getName() === 'driver') { return $this->driverSelect(); } if (in_array(HasChildComponents::class, class_uses_recursive($component), true)) { $component->schema( $this->replaceDriverField($component->getChildComponents()) ); } return $component; }, $components); } private function driverSelect(): Select { return Select::make('driver') ->label('Type') ->options(fn () => collect(Shipping::getSupportedDrivers()) ->mapWithKeys(fn ($driver, $key) => [$key => $driver->name()])) ->default('flat-rate') ->live(); } }