import refreshTokenHelper from "../../../utils/refreshTokenHelper" import forceLogoutHelper from "../../../utils/forceLogoutHelper" import errorHandlingHelper from "../../../utils/errorHandlingHelper" import fetchHelper from "../../../utils/fetchHelper" const LWA_TOKEN_URL = 'https://api.amazon.com/auth/o2/token' const SP_API_BASE_URL = 'https://sellingpartnerapi-eu.amazon.com' // Test various SP-API endpoints to determine accessible resources const testEndpoints = [ { name: 'Feeds', path: '/feeds/2021-06-30/feeds?pageSize=1', description: 'Feeds API (list feeds)' }, { name: 'Orders', path: '/orders/v0/orders?MarketplaceIds=A1PA6795UKMFR9&CreatedAfter=2099-01-01T00:00:00Z&MaxResultsPerPage=1', description: 'Orders API' }, { name: 'Reports', path: '/reports/2021-06-30/reports?pageSize=1', description: 'Reports API' }, { name: 'Catalog Items', path: '/catalog/2022-04-01/items?marketplaceIds=A1PA6795UKMFR9&keywords=test&pageSize=1', description: 'Catalog Items API' }, { name: 'Sellers', path: '/sellers/v1/marketplaceParticipations', description: 'Sellers API (marketplace participations)' }, ] // Feed type access tests const testFeedTypes = [ { name: 'UPLOAD_VAT_INVOICE', feedType: 'UPLOAD_VAT_INVOICE', description: 'Tax Invoicing (Restricted) - required for invoice uploads' }, ] const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const body = await readBody(event) const orderSourceId = body.orderSourceId if (!orderSourceId) { return { success: false, message: 'No order source ID provided' } } // Fetch order source credentials const orderSource: any = await fetchHelper( event, `models/c_ordersource/${orderSourceId}`, 'GET', token, null ) if (!orderSource) { return { success: false, message: 'Order source not found' } } const clientId = orderSource.marketplace_key const clientSecret = orderSource.marketplace_secret const refreshToken = orderSource.marketplace_token if (!clientId || !clientSecret || !refreshToken) { return { success: false, message: 'Missing Amazon SP-API credentials. Ensure Client ID (marketplace_key), Client Secret (marketplace_secret), and Refresh Token (marketplace_token) are set.', details: { hasClientId: !!clientId, hasClientSecret: !!clientSecret, hasRefreshToken: !!refreshToken, } } } // Step 1: Get LWA access token let accessToken: string try { const lwaResponse = await fetch(LWA_TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: clientId, client_secret: clientSecret }) }) if (!lwaResponse.ok) { const errorText = await lwaResponse.text() return { success: false, message: `LWA authentication failed (${lwaResponse.status}): ${errorText}`, step: 'authentication' } } const lwaData: any = await lwaResponse.json() accessToken = lwaData.access_token } catch (err: any) { return { success: false, message: `LWA authentication error: ${err.message}`, step: 'authentication' } } // Step 2: Test marketplace participations (most basic endpoint) let marketplaces: any[] = [] try { const sellersResponse = await fetch(`${SP_API_BASE_URL}/sellers/v1/marketplaceParticipations`, { method: 'GET', headers: { 'x-amz-access-token': accessToken, 'Content-Type': 'application/json' } }) if (sellersResponse.ok) { const sellersData: any = await sellersResponse.json() marketplaces = sellersData.payload?.map((p: any) => ({ marketplace: p.marketplace?.name || p.marketplace?.id, marketplaceId: p.marketplace?.id, country: p.marketplace?.countryCode, isParticipating: p.participation?.isParticipating, })) || [] } } catch (err: any) { // Non-critical, continue testing } // Step 3: Test each endpoint for access const accessResults: any[] = [] for (const endpoint of testEndpoints) { try { const response = await fetch(`${SP_API_BASE_URL}${endpoint.path}`, { method: 'GET', headers: { 'x-amz-access-token': accessToken, 'Content-Type': 'application/json' } }) accessResults.push({ name: endpoint.name, description: endpoint.description, status: response.status, accessible: response.ok, error: response.ok ? null : `${response.status} ${response.statusText}` }) } catch (err: any) { accessResults.push({ name: endpoint.name, description: endpoint.description, status: 0, accessible: false, error: err.message }) } } // Step 4: Test feed type access by creating a dummy feed document const feedAccessResults: any[] = [] for (const feedTest of testFeedTypes) { try { // Try to create a feed document (this tests basic feeds access) const docResponse = await fetch(`${SP_API_BASE_URL}/feeds/2021-06-30/documents`, { method: 'POST', headers: { 'x-amz-access-token': accessToken, 'Content-Type': 'application/json' }, body: JSON.stringify({ contentType: 'application/pdf' }) }) if (!docResponse.ok) { feedAccessResults.push({ name: feedTest.name, description: feedTest.description, accessible: false, error: `Cannot create feed document: ${docResponse.status}`, step: 'createFeedDocument' }) continue } const docData: any = await docResponse.json() // Now try to create the actual feed (this tests feed type permission) const feedResponse = await fetch(`${SP_API_BASE_URL}/feeds/2021-06-30/feeds`, { method: 'POST', headers: { 'x-amz-access-token': accessToken, 'Content-Type': 'application/json' }, body: JSON.stringify({ feedType: feedTest.feedType, marketplaceIds: ['A1PA6795UKMFR9'], inputFeedDocumentId: docData.feedDocumentId, feedOptions: { 'metadata:orderid': '000-0000000-0000000', 'metadata:invoicenumber': 'TEST-000', 'metadata:documenttype': 'Invoice' } }) }) const feedResponseText = await feedResponse.text() let feedResponseData: any = {} try { feedResponseData = JSON.parse(feedResponseText) } catch (e) {} feedAccessResults.push({ name: feedTest.name, description: feedTest.description, accessible: feedResponse.ok, status: feedResponse.status, error: feedResponse.ok ? null : (feedResponseData.errors?.[0]?.message || feedResponseText), feedId: feedResponseData.feedId || null, step: 'createFeed' }) } catch (err: any) { feedAccessResults.push({ name: feedTest.name, description: feedTest.description, accessible: false, error: err.message, step: 'error' }) } } const allEndpointsAccessible = accessResults.every(r => r.accessible) const feedsAccessible = feedAccessResults.every(r => r.accessible) const hasInvoiceUploadAccess = feedAccessResults.find(r => r.name === 'UPLOAD_VAT_INVOICE')?.accessible || false let statusMessage = 'Amazon SP-API connection successful.' if (!hasInvoiceUploadAccess) { const feedError = feedAccessResults.find(r => r.name === 'UPLOAD_VAT_INVOICE') statusMessage = `Connection works but UPLOAD_VAT_INVOICE feed access is DENIED (${feedError?.status || 'unknown'}). Your app needs the "Tax Invoicing (Restricted)" role. Go to Amazon Seller Central > Developer Profile > Edit App > add the "Tax Invoicing" restricted role.` } return { success: true, message: statusMessage, authentication: 'OK', marketplaces, apiAccess: accessResults, feedAccess: feedAccessResults, hasInvoiceUploadAccess, summary: { authenticated: true, marketplaceCount: marketplaces.length, accessibleApis: accessResults.filter(r => r.accessible).map(r => r.name), deniedApis: accessResults.filter(r => !r.accessible).map(r => `${r.name} (${r.error})`), invoiceUploadReady: hasInvoiceUploadAccess, } } } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch (err: any) { try { let authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch (error) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) } } return data })