diff --git a/docs/ops/amazon_creators_pms_integration_pack.md b/docs/ops/amazon_creators_pms_integration_pack.md new file mode 100644 index 0000000..c2c7a1d --- /dev/null +++ b/docs/ops/amazon_creators_pms_integration_pack.md @@ -0,0 +1,314 @@ +# πŸ“¦ PACCHETTO COMPLETO INTEGRAZIONE AMAZON API & VETRINA PRODOTTI PER PMS + +> **Documento di Specifica e Codice Pronto all'Uso per Agent / Sviluppatori** +> *Risoluzione Errore HTTP 403/404 + Configurazione `.env` + Service PHP + Pagina Pubblica Vetrina (Carrello & Referral)* + +--- + +## πŸ“Œ 1. PERCHΓ‰ RICEVEVI ERRORE HTTP 403 / 404 (DIAGNOSI TECNICA) + +1. **Confusione tra PA-API v5 e Creators API v3**: + - Se chiami la **Product Advertising API v5 (`webservices.amazon.it`)** inviando un `Authorization: Bearer ` tramite OAuth, **Amazon RIFIUTA la richiesta con HTTP 403**! PA-API v5 richiede **obbligatoriamente la firma AWS Signature Version 4 (AWS4-HMAC-SHA256)** con la coppia `Access Key` + `Secret Key`. + - Se usate invece la nuova **Amazon Creators API (v3.2)** via OAuth `client_credentials`, l'endpoint base Γ¨ `https://creators-api.amazon.com/v3/` ed il campo `scope` deve essere `creatorsapi::default`. + +2. **Header `x-marketplace` Obbligatorio**: + - Tutte le chiamate ad Amazon Creators API devono includere l'header HTTP: + `x-marketplace: www.amazon.it` + +--- + +## βš™οΈ 2. AMBIENTE `.env` PRONTO DA COPIARE + +Inserisci questo blocco nel file `.env` del nuovo PMS: + +```env +# ============================================================================== +# AMAZON INTEGRATION (CREATORS API & PA-API v5) +# ============================================================================== +AMAZON_REFERRAL_TAG="tuotag-21" +AMAZON_MARKETPLACE="www.amazon.it" + +# --- OPZIONE A: Amazon Creators API (OAuth 2.0 Client Credentials) --- +AMAZON_CREATORS_ENABLED=true +AMAZON_CREATORS_TOKEN_URL="https://api.amazon.com/auth/o2/token" +AMAZON_CREATORS_API_BASE_URL="https://creators-api.amazon.com" +AMAZON_CREATORS_CREDENTIAL_ID="amzn1.application-oa2-client.YOUR_CLIENT_ID" +AMAZON_CREATORS_CREDENTIAL_SECRET="YOUR_CLIENT_SECRET" +AMAZON_CREATORS_CREDENTIAL_VERSION="v3.2" +AMAZON_CREATORS_SEARCH_PATH="v3/searchItems" +AMAZON_CREATORS_GET_ITEMS_PATH="v3/getItems" +AMAZON_CREATORS_TIMEOUT=20 + +# --- OPZIONE B: Amazon PA-API 5.0 (AWS SigV4 Header Signing) --- +AMAZON_PAAPI5_HOST="webservices.amazon.it" +AMAZON_PAAPI5_REGION="eu-west-1" +AMAZON_PAAPI5_SEARCH_URL="https://webservices.amazon.it/paapi5/searchitems" +AMAZON_PAAPI5_GET_ITEMS_URL="https://webservices.amazon.it/paapi5/getitems" +``` + +--- + +## πŸ’» 3. CODICE PHP SERVICE (`AmazonCreatorsApiService.php`) + +Salva in `app/Services/Catalog/AmazonCreatorsApiService.php`: + +```php +credentialId() !== '' + && $this->credentialSecret() !== '' + && $this->tokenUrl() !== '' + && $this->apiBaseUrl() !== ''; + } + + public function getMarketplace(): string + { + return trim((string) config('services.amazon.marketplace', 'www.amazon.it')) ?: 'www.amazon.it'; + } + + public function getAssociateTag(): ?string + { + $tag = trim((string) config('services.amazon.referral_tag', '')); + return $tag !== '' ? $tag : null; + } + + public function searchItems(string $keywords, array $parameters = []): array + { + $keywords = trim($keywords); + if ($keywords === '') { + throw new RuntimeException('Keywords Amazon vuote.'); + } + + return $this->requestOperation('searchItems', array_filter(array_replace([ + 'keywords' => $keywords, + 'partnerTag' => $this->getAssociateTag(), + ], $parameters), static fn(mixed $value): bool => $value !== null && $value !== '')); + } + + public function getItems(array $identifiers, array $parameters = []): array + { + $identifiers = array_values(array_filter(array_map( + static fn(mixed $value): string => trim((string) $value), + $identifiers + ), static fn(string $value): bool => $value !== '')); + + if ($identifiers === []) { + throw new RuntimeException('Specificare almeno un ASIN Amazon.'); + } + + return $this->requestOperation('getItems', array_filter(array_replace([ + 'itemIds' => $identifiers, + 'partnerTag' => $this->getAssociateTag(), + 'resources' => [ + 'images.primary.large', + 'itemInfo.title', + 'offersV2.listings.price' + ] + ], $parameters), static fn(mixed $value): bool => $value !== null && $value !== '')); + } + + private function credentialId(): string + { + return trim((string) config('services.amazon.creators_credential_id', '')); + } + + private function credentialSecret(): string + { + return trim((string) config('services.amazon.creators_credential_secret', '')); + } + + private function tokenUrl(): string + { + return trim((string) config('services.amazon.creators_token_url', '')); + } + + private function apiBaseUrl(): string + { + return rtrim(trim((string) config('services.amazon.creators_api_base_url', '')), '/'); + } + + private function requestOperation(string $operation, array $payload): array + { + if (! $this->isConfigured()) { + throw new RuntimeException('Amazon Creators API non configurata in ambiente.'); + } + + $path = trim((string) Arr::get(config('services.amazon.creators_paths', []), $operation, ''), '/'); + + $response = Http::timeout(20) + ->acceptJson() + ->asJson() + ->withToken($this->getAccessToken()) + ->withHeaders([ + 'x-marketplace' => $this->getMarketplace(), + ]) + ->post($this->apiBaseUrl() . '/' . $path, $payload); + + if (! $response->successful()) { + throw new RuntimeException('Creators API error HTTP ' . $response->status() . ': ' . $response->body()); + } + + return $response->json() ?: []; + } + + private function getAccessToken(): string + { + $cacheKey = 'amazon-creators-token:' . sha1($this->credentialId()); + $cached = Cache::get($cacheKey); + if (is_string($cached) && trim($cached) !== '') { + return $cached; + } + + $response = Http::timeout(20)->asJson()->post($this->tokenUrl(), [ + 'grant_type' => 'client_credentials', + 'client_id' => $this->credentialId(), + 'client_secret' => $this->credentialSecret(), + 'scope' => 'creatorsapi::default', + ]); + + if (! $response->successful()) { + throw new RuntimeException('Token OAuth Amazon fallito: ' . $response->body()); + } + + $token = (string) ($response->json('access_token') ?? ''); + $expires = (int) ($response->json('expires_in') ?? 3600); + Cache::put($cacheKey, $token, now()->addSeconds($expires - 60)); + + return $token; + } +} +``` + +--- + +## 🎨 4. PAGINA PUBBLICA VETRINA HTML/BLADE CON CARRELLO E REFERRAL + +Salva in `resources/views/public/amazon-referral.blade.php`: + +```html + + + + + + Catalogo Prodotti Amazon & Referral PMS + + + +
+
+

πŸ›οΈ Catalogo Prodotti & Vetrina Amazon

+

Seleziona i prodotti per la tua struttura ricettiva ed acquista direttamente su Amazon con referral dedicato.

+
+ +
+ +
+
+ Epson Stampante +

EPSON EcoTank ET-2810

+

Stampante multifunzione a serbatoi ricaricabili ideale per reception.

+
189.99 €
+
+
+ Vedi su Amazon + +
+
+
+ + +
+

πŸ›’ Carrello Prodotti Selezionati (0)

+
Nessun prodotto selezionato.
+ + +
+
+ + + + +``` + +--- + +## πŸš€ 5. TEST & VERIFICA IMMEDIATA + +1. Salva il file `.env` con le variabili riportate al punto 2. +2. Inserisci il Service PHP ed il Controller. +3. Apri la pagina `/public/amazon` nel browser: vedrai la vetrina ed il carrello funzionante con i link referral automatici!