, price?: array} $options */ public function __construct(private readonly int $packageId, private readonly array $options = []) {} public function handle(PaddleCatalogService $catalog): void { $package = Package::query()->find($this->packageId); if (! $package) { return; } $dryRun = (bool) ($this->options['dry_run'] ?? false); $productOverrides = Arr::get($this->options, 'product', []); $priceOverrides = Arr::get($this->options, 'price', []); if ($dryRun) { $this->storeDryRunSnapshot($catalog, $package, $productOverrides, $priceOverrides); return; } $package->forceFill([ 'paddle_sync_status' => 'syncing', ])->save(); try { $productResponse = $package->paddle_product_id ? $catalog->updateProduct($package->paddle_product_id, $package, $productOverrides) : $catalog->createProduct($package, $productOverrides); $productId = (string) ($productResponse['id'] ?? $package->paddle_product_id); if (! $productId) { throw new PaddleException('Paddle product ID missing after sync.'); } $package->paddle_product_id = $productId; $priceResponse = $package->paddle_price_id ? $catalog->updatePrice($package->paddle_price_id, $package, array_merge($priceOverrides, ['product_id' => $productId])) : $catalog->createPrice($package, $productId, $priceOverrides); $priceId = (string) ($priceResponse['id'] ?? $package->paddle_price_id); if (! $priceId) { throw new PaddleException('Paddle price ID missing after sync.'); } $package->forceFill([ 'paddle_price_id' => $priceId, 'paddle_sync_status' => 'synced', 'paddle_synced_at' => now(), 'paddle_snapshot' => [ 'product' => $productResponse, 'price' => $priceResponse, 'payload' => [ 'product' => $catalog->buildProductPayload($package, $productOverrides), 'price' => $catalog->buildPricePayload($package, $productId, $priceOverrides), ], ], ])->save(); } catch (Throwable $exception) { Log::channel('paddle-sync')->error('Paddle package sync failed', [ 'package_id' => $package->id, 'message' => $exception->getMessage(), 'exception' => $exception, ]); $package->forceFill([ 'paddle_sync_status' => 'failed', 'paddle_synced_at' => now(), 'paddle_snapshot' => array_merge($package->paddle_snapshot ?? [], [ 'error' => [ 'message' => $exception->getMessage(), 'class' => $exception::class, ], ]), ])->save(); throw $exception; } } /** * @param array $productOverrides * @param array $priceOverrides */ protected function storeDryRunSnapshot(PaddleCatalogService $catalog, Package $package, array $productOverrides, array $priceOverrides): void { $productPayload = $catalog->buildProductPayload($package, $productOverrides); $pricePayload = $catalog->buildPricePayload( $package, $package->paddle_product_id ?: ($priceOverrides['product_id'] ?? 'pending'), $priceOverrides ); $package->forceFill([ 'paddle_sync_status' => 'dry-run', 'paddle_synced_at' => now(), 'paddle_snapshot' => [ 'dry_run' => true, 'payload' => [ 'product' => $productPayload, 'price' => $pricePayload, ], ], ])->save(); Log::channel('paddle-sync')->info('Paddle package dry-run snapshot generated', [ 'package_id' => $package->id, ]); } }