*/ public function syncProduct(Product $product, array $options = []): array { $dryRun = (bool) ($options['dry_run'] ?? false); $item = $this->resolveItemPayload($product, $options); if (! is_array($item) || $item === []) { throw new RuntimeException('Nessun item Amazon trovato per il prodotto selezionato.'); } $asin = $this->extractAsin($item); if ($asin === null) { throw new RuntimeException('La risposta Amazon non contiene un ASIN utilizzabile.'); } $title = $this->extractTitle($item) ?: (string) ($product->name ?? 'Prodotto Amazon'); $marketplace = trim((string) ($options['marketplace'] ?? $this->apiService->getMarketplace())) ?: $this->apiService->getMarketplace(); $associateTag = trim((string) ($options['associate_tag'] ?? $this->apiService->getAssociateTag() ?? '')) ?: null; $detailUrl = $this->extractDetailPageUrl($item); $referralUrl = $detailUrl !== null ? $this->appendAssociateTag($detailUrl, $associateTag) : $this->productOfferService->buildAmazonReferralUrl($product, $associateTag, $this->mapMarketplaceToLocale($marketplace)); $price = $this->extractPriceAmount($item); $compareAt = $this->extractCompareAtAmount($item); $currency = $this->extractCurrency($item) ?? 'EUR'; $availability = $this->extractAvailability($item) ?? 'catalog_synced'; $externalId = $asin; $ean = $this->extractEan($item); $upc = $this->extractUpc($item); $manufacturerCode = $this->extractManufacturerPartNumber($item); $imageUrls = $this->extractImageUrls($item); $imageUrl = $imageUrls[0] ?? null; $payloadSummary = $this->buildAmazonSnapshot($item, [ 'asin' => $asin, 'ean' => $ean, 'upc' => $upc, 'manufacturer_code' => $manufacturerCode, 'title' => $title, 'marketplace' => $marketplace, 'associate_tag' => $associateTag, 'detail_url' => $detailUrl, 'referral_url' => $referralUrl, 'image_url' => $imageUrl, 'image_urls' => $imageUrls, 'price_amount' => $price, 'price_compare_at' => $compareAt, 'currency' => $currency, 'availability' => $availability, 'search_query' => trim((string) ($options['search_query'] ?? '')), ]); $identifiersCreated = 0; $mediaCreated = 0; $offer = null; if (! $dryRun) { $identifiersCreated += $this->upsertIdentifier($product, 'asin', $asin, 'amazon_creators_api', true) ? 1 : 0; if ($ean !== null) { $identifiersCreated += $this->upsertIdentifier($product, 'ean', $ean, 'amazon_creators_api', false) ? 1 : 0; } if ($upc !== null) { $identifiersCreated += $this->upsertIdentifier($product, 'upc', $upc, 'amazon_creators_api', false) ? 1 : 0; } if ($manufacturerCode !== null) { $identifiersCreated += $this->upsertIdentifier($product, 'mpn', $manufacturerCode, 'amazon_creators_api', false) ? 1 : 0; } foreach (array_values(array_slice($imageUrls, 0, 12)) as $index => $remoteUrl) { $mediaCreated += $this->upsertRemoteMedia( $product, $remoteUrl, $title, $index, [ 'provider' => 'amazon_creators_api', 'role' => $index === 0 ? 'primary' : 'gallery', ] ) ? 1 : 0; } $offer = $this->productOfferService->syncOffer($product, [ 'source_type' => 'amazon_creators_api', 'source_name' => 'Amazon Creators API', 'external_product_id' => $externalId, 'title' => $title, 'currency' => $currency, 'price_amount' => $price, 'price_compare_at' => $compareAt, 'availability' => $availability, 'external_url' => $detailUrl, 'referral_url' => $referralUrl, 'referral_code' => $associateTag, 'is_internal' => false, 'is_active' => true, 'meta' => [ 'marketplace' => $marketplace, 'sync_source' => (string) ($options['source'] ?? 'api'), 'payload' => $payloadSummary, ], ]); } return [ 'asin' => $asin, 'title' => $title, 'detail_url' => $detailUrl, 'referral_url' => $referralUrl, 'image_url' => $imageUrl, 'image_urls' => $imageUrls, 'price_amount' => $price, 'price_compare_at' => $compareAt, 'currency' => $currency, 'availability' => $availability, 'ean' => $ean, 'upc' => $upc, 'manufacturer_code' => $manufacturerCode, 'identifiers_created' => $identifiersCreated, 'media_created' => $mediaCreated, 'offer_id' => $offer instanceof ProductOffer ? (int) $offer->id : null, 'source' => (string) ($options['source'] ?? 'api'), 'dry_run' => $dryRun, ]; } /** @return array> */ public function searchCatalog(string $query, int $limit = 8, array $options = []): array { $query = trim($query); if ($query === '') { throw new RuntimeException('Inserisci una query Amazon da cercare.'); } $response = $this->apiService->searchItems($query, [ 'marketplace' => (string) ($options['marketplace'] ?? $this->apiService->getMarketplace()), ]); return $this->extractItems($response) ->take(max(1, $limit)) ->map(function (array $item): array { $asin = $this->extractAsin($item); $ean = $this->extractEan($item); $upc = $this->extractUpc($item); $manufacturerCode = $this->extractManufacturerPartNumber($item); $existingProduct = $this->findExistingProduct($asin, $ean, $upc, $manufacturerCode); $detailUrl = $this->extractDetailPageUrl($item); $associateTag = $this->apiService->getAssociateTag(); return [ 'asin' => $asin, 'ean' => $ean, 'upc' => $upc, 'manufacturer_code' => $manufacturerCode, 'title' => $this->extractTitle($item) ?: 'Prodotto Amazon', 'brand' => $this->extractBrand($item), 'price_amount' => $this->extractPriceAmount($item), 'price_compare_at' => $this->extractCompareAtAmount($item), 'currency' => $this->extractCurrency($item) ?? 'EUR', 'availability' => $this->extractAvailability($item) ?? '', 'detail_url' => $detailUrl, 'referral_url' => $detailUrl !== null ? $this->appendAssociateTag($detailUrl, $associateTag) : null, 'image_url' => $this->extractPrimaryImageUrl($item), 'existing_product_id' => $existingProduct?->id, 'existing_label' => $existingProduct instanceof Product ? trim((string) ($existingProduct->internal_code ?? '') . ' ยท ' . (string) ($existingProduct->name ?? '')) : '', 'item_payload' => $item, ]; }) ->values() ->all(); } /** @return array{product: Product, sync: array, created: bool} */ public function importItemForSupplier(Fornitore $fornitore, array $item, array $options = []): array { $item = $this->enrichItem($item, $options); $asin = $this->extractAsin($item); if ($asin === null) { throw new RuntimeException('Il risultato Amazon selezionato non contiene un ASIN valido.'); } $ean = $this->extractEan($item); $upc = $this->extractUpc($item); $manufacturerCode = $this->extractManufacturerPartNumber($item); $title = $this->extractTitle($item) ?: 'Prodotto Amazon'; $brand = $this->extractBrand($item); $model = $this->extractModel($item); $description = $this->buildDescriptionFromAmazon($item); $product = $this->findExistingProduct($asin, $ean, $upc, $manufacturerCode); $created = false; $marketplace = trim((string) ($options['marketplace'] ?? $this->apiService->getMarketplace())) ?: $this->apiService->getMarketplace(); $associateTag = trim((string) ($options['associate_tag'] ?? ($this->apiService->getAssociateTag() ?? ''))) ?: null; $detailUrl = $this->extractDetailPageUrl($item); $referralUrl = $detailUrl !== null ? $this->appendAssociateTag($detailUrl, $associateTag) : null; $snapshot = $this->buildAmazonSnapshot($item, [ 'asin' => $asin, 'ean' => $ean, 'upc' => $upc, 'manufacturer_code' => $manufacturerCode, 'title' => $title, 'brand' => $brand, 'model' => $model, 'marketplace' => $marketplace, 'associate_tag' => $associateTag, 'detail_url' => $detailUrl, 'referral_url' => $referralUrl, 'search_query' => trim((string) ($options['search_query'] ?? '')), 'sync_source' => (string) ($options['source'] ?? 'amazon_catalog_search_ui'), ]); if (! $product instanceof Product) { $product = Product::query()->create([ 'default_fornitore_id' => (int) $fornitore->id, 'type' => 'product', 'canonical_key' => $this->buildCanonicalKey($title, $asin, $ean), 'name' => $title, 'brand' => $brand, 'model' => $model, 'description' => $description, 'track_serials' => false, 'is_active' => true, 'meta' => [ 'source' => 'amazon_creators_api', 'catalog' => [ 'publication_mode' => 'internal_only', 'public_showcase' => false, ], 'verification' => [ 'status' => 'pending_review', 'channel' => 'amazon_catalog_search_ui', 'updated_at' => now()->toIso8601String(), ], 'amazon' => $snapshot, ], ]); $created = true; } else { $meta = is_array($product->meta ?? null) ? $product->meta : []; $meta['source'] = $meta['source'] ?? 'amazon_creators_api'; $meta['catalog'] = array_replace([ 'publication_mode' => 'internal_only', 'public_showcase' => false, ], is_array($meta['catalog'] ?? null) ? $meta['catalog'] : []); $meta['verification'] = [ 'status' => 'pending_review', 'channel' => 'amazon_catalog_search_ui', 'updated_at' => now()->toIso8601String(), ]; $meta['amazon'] = array_filter(array_replace( is_array($meta['amazon'] ?? null) ? $meta['amazon'] : [], $snapshot ), static fn(mixed $value): bool => $value !== null && $value !== ''); $product->fill([ 'default_fornitore_id' => $product->default_fornitore_id ?: (int) $fornitore->id, 'name' => trim((string) $product->name) !== '' ? $product->name : $title, 'brand' => trim((string) ($product->brand ?? '')) !== '' ? $product->brand : $brand, 'model' => trim((string) ($product->model ?? '')) !== '' ? $product->model : $model, 'description' => trim((string) ($product->description ?? '')) !== '' ? $product->description : $description, 'meta' => $meta, ]); $product->save(); } $sync = $this->syncProduct($product, [ 'item' => $item, 'source' => (string) ($options['source'] ?? 'amazon_catalog_search_ui'), 'marketplace' => $marketplace, 'associate_tag' => (string) ($associateTag ?? ''), 'search_query' => trim((string) ($options['search_query'] ?? '')), ]); return [ 'product' => $product->fresh(), 'sync' => $sync, 'created' => $created, ]; } /** @param array> $rows * @return array */ public function importSearchResultsForSupplier(Fornitore $fornitore, array $rows, array $options = []): array { $created = 0; $updated = 0; $skipped = 0; $errors = []; foreach ($rows as $row) { $item = is_array($row['item_payload'] ?? null) ? $row['item_payload'] : null; if (! is_array($item) || $item === []) { $skipped++; continue; } try { $result = $this->importItemForSupplier($fornitore, $item, [ 'source' => (string) ($options['source'] ?? 'amazon_catalog_search_bulk_ui'), 'marketplace' => (string) ($options['marketplace'] ?? $this->apiService->getMarketplace()), 'associate_tag' => (string) ($options['associate_tag'] ?? ($this->apiService->getAssociateTag() ?? '')), 'search_query' => trim((string) ($options['search_query'] ?? '')), ]); if (! empty($result['created'])) { $created++; } else { $updated++; } } catch (RuntimeException $e) { $errors[] = $e->getMessage(); } } return [ 'created' => $created, 'updated' => $updated, 'skipped' => $skipped, 'errors' => array_values(array_unique($errors)), 'processed' => $created + $updated, ]; } /** @return array */ private function resolveItemPayload(Product $product, array $options): array { if (is_array($options['item'] ?? null)) { return $options['item']; } if (is_array($options['response'] ?? null)) { $candidate = $this->pickFirstItem((array) $options['response']); return is_array($candidate) ? $candidate : []; } $asin = trim((string) ($options['asin'] ?? $this->findIdentifierValue($product, ['asin']) ?? '')); if ($asin !== '') { $response = $this->apiService->getItems([$asin], [ 'marketplace' => (string) ($options['marketplace'] ?? $this->apiService->getMarketplace()), ]); return $this->pickFirstItem($response) ?? []; } $query = trim((string) ($options['query'] ?? $this->findIdentifierValue($product, ['ean']) ?? $this->buildSearchQuery($product))); if ($query === '') { return []; } $response = $this->apiService->searchItems($query, [ 'marketplace' => (string) ($options['marketplace'] ?? $this->apiService->getMarketplace()), ]); return $this->pickFirstItem($response) ?? []; } /** @return Collection> */ private function extractItems(array $payload): Collection { foreach ([ 'items', 'itemsResult.items', 'searchResult.items', 'getItemsResult.items', 'ItemsResult.Items', 'SearchResult.Items', 'GetItemsResult.Items', 'data.items', 'data.searchItems.items', 'data.getItems.items', ] as $path) { $items = data_get($payload, $path); if (is_array($items)) { return collect($items)->filter(static fn(mixed $item): bool => is_array($item))->values(); } } return collect(isset($payload['asin']) || isset($payload['ASIN']) ? [$payload] : []); } /** @return array|null */ private function pickFirstItem(array $payload): ?array { foreach ([ 'items', 'itemsResult.items', 'searchResult.items', 'getItemsResult.items', 'ItemsResult.Items', 'SearchResult.Items', 'GetItemsResult.Items', 'data.items', 'data.searchItems.items', 'data.getItems.items', ] as $path) { $items = data_get($payload, $path); if (is_array($items) && isset($items[0]) && is_array($items[0])) { return $items[0]; } } return isset($payload['asin']) || isset($payload['ASIN']) ? $payload : null; } private function extractAsin(array $item): ?string { foreach (['asin', 'ASIN', 'itemId', 'ItemId'] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } private function extractEan(array $item): ?string { foreach ([ 'ean', 'EAN', 'ExternalIds.EANs.DisplayValues.0', 'externalIds.eans.0', 'itemInfo.externalIds.eans.displayValues.0', 'identifiers.ean', ] as $path) { $value = preg_replace('/\s+/', '', trim((string) data_get($item, $path, ''))); if (is_string($value) && $value !== '') { return $value; } } return null; } private function extractUpc(array $item): ?string { foreach ([ 'UPC', 'upc', 'ExternalIds.UPCs.DisplayValues.0', 'externalIds.upcs.0', 'itemInfo.externalIds.upcs.displayValues.0', ] as $path) { $value = preg_replace('/\s+/', '', trim((string) data_get($item, $path, ''))); if (is_string($value) && $value !== '') { return $value; } } return null; } private function extractTitle(array $item): ?string { foreach (['title', 'Title', 'ItemInfo.Title.DisplayValue', 'itemInfo.title.displayValue'] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } private function extractBrand(array $item): ?string { foreach (['brand', 'Brand', 'ItemInfo.ByLineInfo.Brand.DisplayValue', 'itemInfo.byLineInfo.brand.displayValue'] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } private function extractModel(array $item): ?string { foreach ([ 'model', 'Model', 'ItemInfo.ManufactureInfo.Model.DisplayValue', 'itemInfo.manufactureInfo.model.displayValue', 'ItemInfo.Classifications.ProductGroup.DisplayValue', 'itemInfo.classifications.productGroup.displayValue', ] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } private function extractManufacturerPartNumber(array $item): ?string { foreach ([ 'manufacturer_code', 'mpn', 'MPN', 'ItemInfo.ManufactureInfo.ItemPartNumber.DisplayValue', 'itemInfo.manufactureInfo.itemPartNumber.displayValue', ] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } private function extractDetailPageUrl(array $item): ?string { foreach (['detailPageURL', 'DetailPageURL', 'detailPageUrl', 'url'] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } private function extractPrimaryImageUrl(array $item): ?string { return $this->extractImageUrls($item)[0] ?? null; } /** @return array */ private function extractImageUrls(array $item): array { $urls = []; foreach ([ 'Images.Primary.Large.URL', 'Images.Primary.Medium.URL', 'Images.Primary.Small.URL', 'Images.Variants.*.Large.URL', 'Images.Variants.*.Medium.URL', 'Images.Variants.*.Small.URL', 'images.primary.large.url', 'images.primary.medium.url', 'images.primary.small.url', 'images.variants.*.large.url', 'images.variants.*.medium.url', 'images.variants.*.small.url', 'images.*.url', 'image', ] as $path) { $value = data_get($item, $path); foreach (Arr::wrap($value) as $candidate) { $url = trim((string) $candidate); if ($url !== '' && str_starts_with($url, 'http')) { $urls[] = $url; } } } return array_values(array_unique($urls)); } private function extractPriceAmount(array $item): ?float { foreach ([ 'Offers.Listings.0.Price.Amount', 'Offers.Summaries.0.LowestPrice.Amount', 'offersV2.listings.0.price.money.amount', 'offers.listings.0.price.amount', 'offers.summaries.0.lowestPrice.amount', 'price.amount', ] as $path) { $value = data_get($item, $path); if (is_numeric($value)) { return round((float) $value, 2); } } return null; } private function extractCompareAtAmount(array $item): ?float { foreach ([ 'Offers.Listings.0.SavingBasis.Amount', 'offersV2.listings.0.price.savingBasis.money.amount', 'offers.listings.0.savingBasis.amount', 'price.compareAt.amount', ] as $path) { $value = data_get($item, $path); if (is_numeric($value)) { return round((float) $value, 2); } } return null; } private function extractCurrency(array $item): ?string { foreach ([ 'Offers.Listings.0.Price.Currency', 'Offers.Summaries.0.LowestPrice.Currency', 'offersV2.listings.0.price.money.currency', 'offers.listings.0.price.currency', 'offers.summaries.0.lowestPrice.currency', 'price.currency', ] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return strtoupper($value); } } return null; } private function extractAvailability(array $item): ?string { foreach ([ 'Offers.Listings.0.Availability.Message', 'offers.listings.0.availability.message', 'availability', ] as $path) { $value = trim((string) data_get($item, $path, '')); if ($value !== '') { return $value; } } return null; } /** @return array */ private function extractFeaturePoints(array $item): array { $features = []; foreach ([ 'ItemInfo.Features.DisplayValues', 'itemInfo.features.displayValues', 'features', 'featureBullets', ] as $path) { $value = data_get($item, $path); foreach (Arr::wrap($value) as $candidate) { $text = trim((string) $candidate); if ($text !== '') { $features[] = $text; } } } return array_values(array_unique($features)); } private function buildDescriptionFromAmazon(array $item): ?string { $features = array_slice($this->extractFeaturePoints($item), 0, 8); return $features !== [] ? implode(PHP_EOL, $features) : null; } /** @param array $overrides * @return array */ private function buildAmazonSnapshot(array $item, array $overrides = []): array { $snapshot = array_filter([ 'asin' => $this->extractAsin($item), 'ean' => $this->extractEan($item), 'upc' => $this->extractUpc($item), 'title' => $this->extractTitle($item), 'brand' => $this->extractBrand($item), 'model' => $this->extractModel($item), 'manufacturer_code' => $this->extractManufacturerPartNumber($item), 'detail_url' => $this->extractDetailPageUrl($item), 'image_url' => $this->extractPrimaryImageUrl($item), 'image_urls' => $this->extractImageUrls($item), 'price_amount' => $this->extractPriceAmount($item), 'price_compare_at' => $this->extractCompareAtAmount($item), 'currency' => $this->extractCurrency($item), 'availability' => $this->extractAvailability($item), 'feature_points' => array_slice($this->extractFeaturePoints($item), 0, 8), 'synced_at' => now()->toIso8601String(), ], static fn(mixed $value): bool => $value !== null && $value !== '' && $value !== []); return array_filter(array_replace($snapshot, $overrides), static fn(mixed $value): bool => $value !== null && $value !== '' && $value !== []); } private function appendAssociateTag(string $url, ?string $associateTag): string { $associateTag = $associateTag !== null ? trim($associateTag) : ''; if ($associateTag === '') { return $url; } $separator = str_contains($url, '?') ? '&' : '?'; return str_contains($url, 'tag=') ? $url : ($url . $separator . 'tag=' . rawurlencode($associateTag)); } private function mapMarketplaceToLocale(string $marketplace): string { return match (Str::lower(trim($marketplace))) { 'www.amazon.de' => 'de', 'www.amazon.fr' => 'fr', 'www.amazon.es' => 'es', 'www.amazon.co.uk' => 'uk', default => 'it', }; } /** @param array $types */ private function findIdentifierValue(Product $product, array $types): ?string { $value = ProductIdentifier::query() ->where('product_id', (int) $product->id) ->whereIn('code_type', $types) ->orderByDesc('is_primary') ->orderBy('id') ->value('code_value'); $value = trim((string) $value); return $value !== '' ? $value : null; } private function buildSearchQuery(Product $product): string { $parts = array_values(array_unique(array_filter([ trim((string) ($product->brand ?? '')), trim((string) ($product->model ?? '')), trim((string) ($product->name ?? '')), ], static fn(string $value): bool => $value !== ''))); return Str::limit(implode(' ', $parts), 120, ''); } private function findExistingProduct(?string $asin, ?string $ean, ?string $upc = null, ?string $manufacturerCode = null): ?Product { $codes = array_values(array_filter([ $this->normalizeCode($asin), $this->normalizeCode($ean), $this->normalizeCode($upc), $this->normalizeCode($manufacturerCode), ])); if ($codes === []) { return null; } $productId = ProductIdentifier::query() ->whereIn('normalized_code', $codes) ->orderByDesc('is_primary') ->value('product_id'); return $productId ? Product::query()->find((int) $productId) : null; } /** @param array $options */ private function enrichItem(array $item, array $options = []): array { $asin = $this->extractAsin($item); if ($asin === null) { return $item; } $needsEnrichment = $this->extractManufacturerPartNumber($item) === null || $this->extractEan($item) === null || $this->extractPrimaryImageUrl($item) === null || $this->extractBrand($item) === null || $this->extractPriceAmount($item) === null; if (! $needsEnrichment) { return $item; } try { $response = $this->apiService->getItems([$asin], [ 'marketplace' => (string) ($options['marketplace'] ?? $this->apiService->getMarketplace()), ]); } catch (RuntimeException) { return $item; } $richItem = $this->pickFirstItem($response); return is_array($richItem) ? $richItem : $item; } private function buildCanonicalKey(string $title, ?string $asin, ?string $ean): string { $base = Str::slug(Str::limit($title, 80, '')); $seed = $this->normalizeCode($asin) ?: $this->normalizeCode($ean) ?: Str::upper(Str::random(8)); $key = Str::limit(($base !== '' ? $base : 'amazon-item') . '-' . Str::lower($seed), 190, ''); if (! Product::query()->where('canonical_key', $key)->exists()) { return $key; } return Str::limit($key . '-' . Str::lower(Str::random(4)), 190, ''); } private function normalizeCode(?string $value): ?string { $value = trim((string) $value); if ($value === '') { return null; } $normalized = strtoupper(preg_replace('/[^A-Z0-9]+/', '', $value) ?? ''); return $normalized !== '' ? $normalized : null; } private function upsertIdentifier(Product $product, string $type, string $value, string $source, bool $isPrimary): bool { $value = trim($value); if ($value === '') { return false; } $normalized = strtoupper(preg_replace('/[^A-Z0-9]+/', '', $value) ?? ''); if ($normalized === '') { return false; } $identifier = ProductIdentifier::query()->firstOrNew([ 'product_id' => (int) $product->id, 'code_type' => $type, 'normalized_code' => $normalized, ]); $created = ! $identifier->exists; $identifier->code_role = $type; $identifier->code_value = $value; $identifier->source = $source; $identifier->source_reference = $value; $identifier->is_primary = $isPrimary; $identifier->payload = ['provider' => 'amazon']; $identifier->save(); return $created; } private function upsertRemoteMedia(Product $product, string $url, string $title, int $sortOrder = 0, array $meta = []): bool { $url = trim($url); if ($url === '' || ! str_starts_with($url, 'http')) { return false; } $media = ProductMedia::query()->firstOrNew([ 'product_id' => (int) $product->id, 'media_type' => 'image', 'disk' => 'remote-url', 'path' => $url, ]); $created = ! $media->exists; $media->title = $title; $media->sort_order = max(0, $sortOrder); $media->meta = array_replace([ 'provider' => 'amazon_creators_api', 'remote' => true, ], $meta); $media->save(); return $created; } }