提问人:Mica 提问时间:11/1/2023 最后编辑:duckoakMica 更新时间:11/10/2023 访问量:65
使用 AWS SP-API 将源上传到 Amazon
Upload Feed to Amazon with AWS SP-API
问:
我的问题是在创建提要后应上传的 url 中上传 xml 文件,但每次我都收到错误 SignatureDoesNotMatch
我正在尝试在我从请求 https://developer-docs.amazon.com/sp-api/docs/feeds-api-v2021-06-30-use-case-guide 获得的 url 上上传 xml 文件createFeedDocument
public function createFeedDocument($distributionChannelTeamId){
$response = Http::withHeaders([
'x-amz-access-token'=> $this->getAccessToken($distributionChannelTeamId),
])->post(
'https://sellingpartnerapi-eu.amazon.com/feeds/2021-06-30/documents',
[
"contentType"=> "text/tab-separated-values; charset=UTF-8"
]
);
Log::channel('amazon')->info('Amazon create feed document response: ', ['response' => $response->json()]);
if($response->successful()){
$body = $response->json();
return $body;
}else{
Log::channel('amazon')->info('Amazon create feed document failed for distribution channel team id: ' . $distributionChannelTeamId);
Log::channel('amazon')->info('Amazon create feed document failed: ', ['response' => $response->json()]);
}
}`
然后我在我的真实销售账户上得到了这样的回应(这是例子)
{
"feedDocumentId": "3d4e42b5-1d6e-44e8-a89c-2abfca0625bb",
"url": "https://d34o8swod1owfl.cloudfront.net/Feed_101__POST_PRODUCT_DATA_.xml"
}
然后我想创建Feed并上传它,因为docs说:
public function createFeed($distributionChannelTeamId)
{
$res=$this->createFeedDocument($distributionChannelTeamId);
$url=$res['url'];
$feedDocumentId=$res['feedDocumentId'];
$prefixAmazon = Storage::disk('amazon')->getDriver()->getAdapter()->getPathPrefix();
$requestXml = file_get_contents($prefixAmazon . 'createFeed.xml');
$response = Http::withHeaders(['Content-Type' => 'text/xml'
])->post($url,[
'body' => utf8_encode($requestXml)
]);
Log::channel('amazon')->info('Amazon create feed response: ', ['response' => $response]);`
我在日志中得到这个,因为请求是,亚马逊创建提要响应:403
{“response”:{“Illuminate\Http\Client\Response”:“ 我们计算的请求签名与您提供的签名不匹配。检查密钥和签名方法。AKIAX2ZVOZFBB4YFXXHUAWS4-HMAC-SHA256 20231031T161031Z 20231031/eu-west-1/s3/aws4_request 93dfa574b9431ebb05c7c80589f14946f14741d8ca907ea25415a1891c25965aa30675d84223aea473a1772fc2907ce7a9c7b3a4575aad2ee2549665992d866841 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 0a 34 2 30 32 33 31 30 33 31 54 31 36 31 30 33 31 5a 0a 32 30 32 33 31 30 33 31 2f 65 75 2d 77 65 73 74 2d 31 2f 73 33 2f 61 77 73 34 5f 72 65 71 75 65 73 74 0a 39 33 64 66 61 3537 34 62 39 34 33 31 65 62 62 30 35 63 37 63 38 30 35 38 39 66 31 34 39 34 36 66 31 34 37 34 31 64 38 63 61 39 30 37 65 61 32 35 34 31 35 61 31 38 39 31 63 32 35 39 36 35 61邮政 /3e46e671-9500-49c1-8920-18a4732287c9.amzn1.tortuga.4.eu.T1OBJR31A06AXO x-amz-algorithm=AWS4-HMAC-SHA256&X-amz-credential=AKIAX2ZVOZFBB4YFXXHU%2F20231031%2Feu-west-1%2Fs3%2Faws4_request&X-amz-date=20231031T161031Z&X-amz-expires=300&X-amz-signedHeaders=content-type%3Bhost 内容类型:text/xml 主机:Tortuga-Prod-EU.S3-EU-West-1.AmazonAWS.com 内容类型;主机 无符号PAYLOAD50 4f 53 54 0a 2f 33 65 34 36 65 36 37 31 2d 39 35 30 30 2d 34 39 63 31 2d 38 39 32 30 2d 31 38 61 34 37 33 32 32 38 37 63 39 2e 61 6d 7a 6e 31 2e 74 6f 72 74 75 67 61 2e 34 2e 65 75 2e 54 31 4f 42 4a 52 33 31 41 30 36 41 584f 0a 58 2d 41 6d 7a 2d 41 6c 67 6f 72 69 74 68 6d 3d 41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 26 58 2d 41 6d 7a 2d 43 72 65 64 65 6e 74 69 61 6c 3d 41 4b 49 41 58 32 5a 56 4f 5a 46 42 42 34 59 46 58 58 48 55 25 32 4632 30 32 33 31 30 33 31 25 32 46 65 75 2d 77 65 73 74 2d 31 25 32 46 73 33 25 32 46 61 77 73 34 5f 72 65 71 75 65 73 74 26 58 2d 41 6d 7a 2d 44 61 74 65 3d 32 30 32 33 31 30 33 31 54 31 36 31 30 33 31 5a 26 58 2d 41 6d 7a 2d45 78 70 69 72 65 73 3d 33 30 30 26 58 2d 41 6d 7a 2d 53 69 67 6e 65 64 48 65 61 64 65 72 73 3d 63 6f 6e 74 65 6e 74 2d 74 79 70 65 25 33 42 68 6f 73 74 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3a 74 65 78 74 2f 78 6d 6c 0a 686f 73 74 3a 74 6f 72 74 75 67 61 2d 70 72 6f 64 2d 65 75 2e 73 33 2d 65 75 2d 77 65 73 74 2d 31 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f 6d 0a 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3b 68 6f 73 74 0a 55 4e 53 49 47 4e 45 44 2d50 41 59 4c 4f 41 44FEWNEPN090HEGZVTvmwWxEA8m500fDCGb9NXTXeYVrE+GRFih0e/3RppPUc243qA5QEA80aCCQsC0MM/h9gNQ4cUYGT0BLYboiz+mw==“}}
SignatureDoesNotMatch
答:
我找到了使用 Amazon SP-API (https://github.com/amazon-php/sp-api-sdk 在 Laravel 中创建产品提要的解决方案:
composer require amazon-php/sp-api-sdk
composer require nyholm/psr7 -W
use AmazonPHP\SellingPartner\AccessToken;
use AmazonPHP\SellingPartner\Model\Feeds\CreateFeedDocumentSpecification;
use AmazonPHP\SellingPartner\Model\Feeds\CreateFeedSpecification;
use AmazonPHP\SellingPartner\Regions;
use AmazonPHP\SellingPartner\SellingPartnerSDK;
use App\Models\Amazon\AmazonAuth;
use Exception;
use GuzzleHttp\Handler\CurlFactory;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\App;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
use GuzzleHttp\Client;
use AmazonPHP\SellingPartner\Api\FeedsApi\FeedsSDK;
use AmazonPHP\SellingPartner\Configuration;
use AmazonPHP\SellingPartner\Exception\ApiException;
use AmazonPHP\SellingPartner\Exception\InvalidArgumentException;
use AmazonPHP\SellingPartner\HttpFactory;
use AmazonPHP\SellingPartner\HttpSignatureHeaders;
use AmazonPHP\SellingPartner\ObjectSerializer;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;
use AmazonPHP\SellingPartner\OAuth;
use Buzz\Client\Curl;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Log\NullLogger;
public function getAccessToken($channelId)
{
$amazonAuth = AmazonAuth::where('channel_id', $channelId)->first(); //my local database channel
$isExpired = Carbon::parse($amazonAuth->expires_at)->isPast();
$token = null;
if ($isExpired) {
$data = [
'grant_type' => 'refresh_token',
'refresh_token' => $amazonAuth->refresh_token,
'client_id' => TenantService::getLwaClientId(),
'client_secret' => TenantService::getLwaSecret()
];
$response = Http::asForm()->post(
'https://api.amazon.com/auth/o2/token',
$data
);
if ($response->successful()) {
$body = $response->json();
$amazonAuth->access_token = $body['access_token'];
$amazonAuth->expires_at = now()->addSeconds($body['expires_in'])->toDateTimeString();
$amazonAuth->save();
$token = $body['access_token'];
} else {
Log::channel('amazon')->info('Amazon access token refresh failed for amazon auth id: ' . $amazonAuth->id);
}
} else {
$token = $amazonAuth->access_token;
}
if ($token) {
$accessToken = new AccessToken(
$token,
$amazonAuth->refresh_token,
'refresh_token',
(int) $amazonAuth->expires_in,
'refresh_token'
);
return $accessToken;
}
}
public function createFeedTest()
{
$client = new Client();
$factory = new Psr17Factory();
// $httpFactory = new HttpFactory($factory, $factory);
$region = 'eu';
$accessToken = $this->getAccessToken(150);
$logger = new NullLogger();
$configuration = Configuration::forIAMUser(
$getLwaClientId,
$getLwaSecret,
$getAwsAccessKey,
$getAwsSecretKey,
);
$sdk = SellingPartnerSDK::create($client, $factory, $factory, $configuration, $logger);
$region = Regions::EUROPE;
$specification = new CreateFeedDocumentSpecification;
$feedDoc = $sdk->feeds()->createFeedDocument(
$accessToken,
$region,
$specification->setContentType('text/xml; charset=utf-8')
);
$feedDocID = $feedDoc['feed_document_id'];
$urlFeedUpload = $feedDoc['url'];
$fileContent = '<?xml version="1.0" encoding="utf-8" ?>
<AmazonEnvelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="amzn-envelope.xsd">
<Header>
<DocumentVersion>1.01</DocumentVersion>
<MerchantIdentifier>MYMERCHANTTOKEN</MerchantIdentifier>
</Header>
<MessageType>Product</MessageType>
<PurgeAndReplace>false</PurgeAndReplace>
<Message>
<MessageID>1</MessageID>
<OperationType>Update</OperationType>
<Product>
<SKU>56789</SKU>
<StandardProductID>
<Type>ASIN</Type>
<Value>B0EXAMPLEG</Value>
</StandardProductID>
<ProductTaxCode>A_GEN_NOTAX</ProductTaxCode>
<DescriptionData>
<Title>Example Product Title</Title>
<Brand>Example Product Brand</Brand>
<Description>This is an example product description.</Description>
<BulletPoint>Example Bullet Point 1</BulletPoint>
<BulletPoint>Example Bullet Point 2</BulletPoint>
<MSRP currency="USD">25.19</MSRP>
<Manufacturer>Example Product Manufacturer</Manufacturer>
<ItemType>example-item-type</ItemType>
<CountryOfOrigin>DE</CountryOfOrigin>
<UnitCount>1</UnitCount>
<PPUCountType>stück</PPUCountType>
<IsExpirationDatedProduct>false</IsExpirationDatedProduct>
</DescriptionData>
<ProductData>
<Health>
<ProductType>
<HealthMisc>
<Ingredients>Example Ingredients</Ingredients>
<Directions>Example Directions</Directions>
</HealthMisc>
</ProductType>
</Health>
</ProductData>
<IsHeatSensitive>false</IsHeatSensitive>
</Product>
</Message>
</AmazonEnvelope>';
dump($feedDoc);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $urlFeedUpload);
curl_setopt($curl, CURLOPT_UPLOAD, true);
curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/xml; charset=utf-8'));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_PUT, 1);
curl_setopt($curl, CURLOPT_INFILE, fopen('data://text/plain,' . $fileContent, 'r'));
curl_setopt($curl, CURLOPT_INFILESIZE, strlen($fileContent));
#Only use below option on TEST environment if you have a self-signed certificate!!! On production this can cause security issues
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($curl);
dump($response);
curl_close($curl);
$specificationNewFeed = new CreateFeedSpecification([
'feed_type' => 'POST_PRODUCT_DATA',
'marketplace_ids' => ['A1PA6795UKMFR9'],
'input_feed_document_id' => $feedDocID
]);
$responseFeed = $sdk->feeds()->createFeed(
$accessToken,
$region,
$specificationNewFeed
);
dd($responseFeed);
}
评论