12 KiB
12 KiB
📦 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)
-
Confusione tra PA-API v5 e Creators API v3:
- Se chiami la Product Advertising API v5 (
webservices.amazon.it) inviando unAuthorization: Bearer <token>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 coppiaAccess 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 camposcopedeve esserecreatorsapi::default.
- Se chiami la Product Advertising API v5 (
-
Header
x-marketplaceObbligatorio:- Tutte le chiamate ad Amazon Creators API devono includere l'header HTTP:
x-marketplace: www.amazon.it
- Tutte le chiamate ad Amazon Creators API devono includere l'header HTTP:
⚙️ 2. AMBIENTE .env PRONTO DA COPIARE
Inserisci questo blocco nel file .env del nuovo PMS:
# ==============================================================================
# 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
namespace App\Services\Catalog;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class AmazonCreatorsApiService
{
public function isConfigured(): bool
{
return (bool) config('services.amazon.creators_enabled', false)
&& $this->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:
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catalogo Prodotti Amazon & Referral PMS</title>
<style>
:root {
--bg: #f8fafc;
--card: #ffffff;
--ink: #0f172a;
--muted: #64748b;
--primary: #d97706;
--primary-dark: #b45309;
--border: #e2e8f0;
--radius: 16px;
}
body { margin: 0; font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--ink); padding: 24px; }
.container { max-width: 1100px; margin: 0 auto; }
.header { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 32px; margin-bottom: 24px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; display: flex; flex-direction: column; justify-content: space-between; }
.card img { width: 100%; height: 200px; object-fit: contain; border-radius: 8px; margin-bottom: 12px; }
.card h3 { font-size: 1.1rem; margin: 0 0 8px; color: var(--ink); }
.card .price { font-size: 1.4rem; font-weight: bold; color: var(--primary); margin: 8px 0; }
.btn { display: inline-flex; align-items: center; justify-content: center; padding: 12px 20px; border-radius: 10px; font-weight: bold; text-decoration: none; cursor: pointer; border: none; }
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-dark); }
.btn-secondary { background: #f1f5f9; color: #334155; border: 1px solid var(--border); margin-left: 8px; }
.cart-box { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; margin-top: 32px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🛍️ Catalogo Prodotti & Vetrina Amazon</h1>
<p>Seleziona i prodotti per la tua struttura ricettiva ed acquista direttamente su Amazon con referral dedicato.</p>
</div>
<div class="grid">
<!-- Esempio Prodotto 1 -->
<div class="card">
<div>
<img src="https://m.media-amazon.com/images/I/71TPdaE6y1L._AC_SL1500_.jpg" alt="Epson Stampante">
<h3>EPSON EcoTank ET-2810</h3>
<p>Stampante multifunzione a serbatoi ricaricabili ideale per reception.</p>
<div class="price">189.99 €</div>
</div>
<div>
<a href="https://www.amazon.it/dp/B099KDBV34?tag={{ $associateTag ?? 'tuotag-21' }}" target="_blank" class="btn btn-primary">Vedi su Amazon</a>
<button class="btn btn-secondary cart-add" data-title="EPSON EcoTank ET-2810" data-url="https://www.amazon.it/dp/B099KDBV34?tag={{ $associateTag ?? 'tuotag-21' }}">+ Carrello</button>
</div>
</div>
</div>
<!-- Carrello Interno Integrato -->
<div class="cart-box">
<h2>🛒 Carrello Prodotti Selezionati (<span id="cart-count">0</span>)</h2>
<div id="cart-items" style="margin: 16px 0;">Nessun prodotto selezionato.</div>
<button id="cart-open" class="btn btn-primary">Apri tutti su Amazon</button>
<button id="cart-clear" class="btn btn-secondary">Svuota Carrello</button>
</div>
</div>
<script>
(() => {
const KEY = 'pms_amazon_cart';
const getCart = () => JSON.parse(localStorage.getItem(KEY) || '[]');
const saveCart = (items) => localStorage.setItem(KEY, JSON.stringify(items));
const render = () => {
const items = getCart();
document.getElementById('cart-count').textContent = items.length;
const container = document.getElementById('cart-items');
if (items.length === 0) {
container.innerHTML = '<p>Nessun prodotto selezionato.</p>';
return;
}
container.innerHTML = items.map(i => `<div style="padding:8px 0; border-bottom:1px solid #eee;"><strong>${i.title}</strong></div>`).join('');
};
document.querySelectorAll('.cart-add').forEach(btn => {
btn.addEventListener('click', () => {
const items = getCart();
const url = btn.dataset.url;
if (!items.some(i => i.url === url)) {
items.push({ title: btn.dataset.title, url });
saveCart(items);
render();
}
});
});
document.getElementById('cart-open')?.addEventListener('click', () => {
getCart().forEach(i => window.open(i.url, '_blank'));
});
document.getElementById('cart-clear')?.addEventListener('click', () => {
saveCart([]);
render();
});
render();
})();
</script>
</body>
</html>
🚀 5. TEST & VERIFICA IMMEDIATA
- Salva il file
.envcon le variabili riportate al punto 2. - Inserisci il Service PHP ed il Controller.
- Apri la pagina
/public/amazonnel browser: vedrai la vetrina ed il carrello funzionante con i link referral automatici!