FileMakerで SP-API の Feeds API を用いてAmazon在庫更新(後編)
はじめに
前編からの続きになります。
今回の後編では、Feeds API を実行できる実際のスクリプトステップを元に、その詳細を解説していきたいと思います。一つ注意点としては、
Feeds API を確実に実行できることを主としていますので、ハードコードしている箇所があります。SP-API の他のAPIでも使えるような応用を効かせたり、今後のAWS署名のバージョンに対応するには、本来は考慮すべき点が色々あることを予め断っておきます。
本題
前編でも記載したように Feeds API では、
- POST:在庫更新データ(XML)アップロード用のURLと識別IDの取得
- PUT:1.のURLに在庫更新データ(XML)をアップロード
- POST:1.の識別IDを用いて2.のアップロードしたデータに対して処理を指定
- GET:3.の処理状態を確認し、処理完了時の識別IDを取得
- GET:4.の識別IDを用いて、結果レポートIDを取得
- GET:5.の結果レポートIDを用いて、結果レポートを取得
これらが一つのセットとなる手順になりますが、その中で2.6.以外の1.3.4.5.で署名付きリクエストが必要になります。が、ぶっちゃけ、署名さえ作成できればそれぞれのリクエスト自体は単純な仕組みです。
POSTの際の、cURLオプションの例をあげてみます。
--request "POST"
--header "Authorization: AWS4-HMAC-SHA256 Credential=××××××××××××/20230402/us-west-2/execute-api/aws4_request, SignedHeaders=content-type;host;x-amz-access-token;x-amz-date, Signature=×××××××××××××××××××××"
--header "content-type:application/json"
--header "host:sellingpartnerapi-fe.amazon.com"
--header "x-amz-access-token:×××××××××××××××××××××××"
--header "x-amz-date:20230402T145138Z"
--data "@$requestBody"
この中の、長い Authorization
ヘッダーの最後、Signature=×××××××××××××××××××××
が署名の箇所になりますが、スクリプトの構造としては cURLオプション
を作成するサブスクリプトを作り、Feeds API の各メインスクリプトからそのサブスクリプトを呼び出すという形が、全体としてスッキリすると思います。
サブスクリプトで必要な要素
それぞれの署名を作成する上で、どのリクエストでも変わらず必要になるのが、
- AccessKey(IAMユーザーに発行するアクセスキー)
- SecretKey(同上、シークレットキー)
- AccessToken(リフレッシュトークンを用いて取得)
※各取得方法の詳細は今回は省きます。
この3つです。
そして、それぞれの署名を作成する上でリクエスト毎に変わり、且つ必要になるのが、
- HTTPMethod(GET、POST、などの種類)
- CanonicalUri(リクエストURLの一部)
- SignedHeaders(content-typeやhostなどのヘッダーリスト)
- requestBody(リクエストする際の実態データ)
この4つです。
ですので、メインスクリプトからは変動するこの4つをサブスクリプトに引き渡し、cURLオプションを作成して貰うことになります。
では、
実際の Feeds API 各メインスクリプト1.~6.を順に見ていきます(サブスクリプトは最後)。
1. POST:在庫更新データ(XML)アップロード用のURLと識別IDの取得
これは、このようになります。
3~6行目:サブスクリプトへ渡す4つの要素の設定
content-type;host;x-amz-access-token;x-amz-date
{"contentType":"text/xml;charset=UTF-8"}
$requestBody
の記述に注意です。
content-Type
ではありません。ハイフン無しの contentType
です。
9行目:サブスクリプトへ渡す引数の準備
JSONSetElement ( "" ;
[ "HTTPMethod" ; $HTTPMethod ; 1 ];
[ "CanonicalUri" ; $CanonicalUri ; 1 ];
[ "SignedHeaders" ; $SignedHeaders ; 1 ];
[ "requestBody" ; $requestBody ; 1 ]
)
12~13行目:サブスクリプトへ引数 $arguments
を渡し $cURLoption
を作成させます。
16行目:作成された $cURLoption
を元にリクエストします。
"https://sellingpartnerapi-fe.amazon.com" & $CanonicalUri
$cURLoption
成功すると、このように feedDocumentId
(3.で必要) と url
(2.で必要) が返却されます。
{
"feedDocumentId" : "amzn××××××××××××××××××××××××××××××××",
"url" : "https://tortuga-prod-fe.s3-us-west×××××××××××××××××××××××××××××"
}
2. PUT:1.のURLに在庫更新データ(XML)をアップロード
これは、単純に1.で返却されたURLにxmlを $requestBody
としてPUTするだけですが、xmlデータの作り方はMWSの時と全く同じです。ですので、こちらのAmazonのxmlデータの箇所を参考にして下さい。
PUT時の URLから挿入
のリクエストオプションは、このようになります。
List (
"--request " & Quote ( "PUT" );
"--dump-header " & Quote ( "$dumpHeader" );
"--header " & Quote ( "content-type:text/xml;charset=UTF-8" );
"--data " & Quote ( "@$requestBody" )
)
$dumpHeader
として返却ヘッダーを取得格納していますが、PUTの場合、エラーでなければレスポンスボディは空です。ですので、一応ヘッダーを取得しHTTPコードを確認するようにしています。
HTTP/1.1 200 OK
x-amz-id-2: ×××××××××××××××××××××=
x-amz-request-id: ×××××××××
Date: Mon, 10 Apr 2023 05:57:23 GMT
x-amz-version-id: ××××××××××××××××××
x-amz-expiration: expiry-date="Mon, 10 Jul 2023 00:00:00 GMT", rule-id="Rule for: NinetyDays"
x-amz-server-side-encryption: aws:kms
x-amz-server-side-encryption-aws-kms-key-id: arn:aws:kms:us-west-2:××××××××××××××××××
x-amz-server-side-encryption-bucket-key-enabled: true
ETag: "××××××××××××××××××"
Server: AmazonS3
Content-Length: 0
成功すれば、このように HTTP/1.1 200 OK
となります。
3. POST:1.の識別IDを用いて2.のアップロードしたデータに対して処理を指定
これは、このようになります。
4行目:$CanonicalUri
6行目:$requestBody
この2行が1.の時と変わりますので、注意です。
JSONSetElement ( "" ;
[ "feedType" ; "POST_INVENTORY_AVAILABILITY_DATA" ; 1 ];
[ "marketplaceIds[0]" ; "A1VC38T7YXB528" ; 1 ];
[ "inputFeedDocumentId" ; "amzn××××××××××××××××××××××××××××××××" ; 1 ]
)
marketplaceIds
キーの値は、複数の国に出店しているのであれば、各国のIDを配列に順に入れる必要があります。日本でしか展開していなければ上記の記述で問題なしです。
inputFeedDocumentId
キーの値には、1.で取得した feedDocumentId
を設定します。
9~13行目:サブスクリプトへ引数を渡して返却させる、というどれも共通の処理です。
16行目:1.と同じです(URLから挿入
のレスポンス格納先ターゲット変数は変えてます)。
リクエストに成功すると、
{
"feedId" : "469999457"
}
こんなレスポンスになり、feedId
(4.で必要)が返却されます。
閑話休題
取り合えず、ここまでどうでしょうか。
肝心の署名作成を含むサブスクリプトの紹介がまだですので、アレかもしれませんが、1.~3.を順に行うことで一先ずは、在庫更新データをAmazonにぶん投げた状態になります。
そのぶん投げたデータが正常に処理されたかどうかは、4.~6.を行って確認する必要があります。
実際に、xmlデータを作成する際にSKUの記述が間違っていて更新されていなかったというのがありました。1.~3.が問題なく行えてても、正常に処理されるとは限りません。
4. GET:3.の処理状態を確認し、処理完了時の識別IDを取得
Feeds API は非同期処理ですので、更新処理の確認が必要になります。更新負荷が高い場合は、処理にやや時間がかかるようです。ですので、この4.の処理確認を行うのは3.の後、少し置いた方が良いです。
3~6行目:今までとガラっと変わりますので注意です。
4行目:$CanonicalUri
は、3.で返却された feedId
を結合したものになります。
5行目:GET ですので content-type
ヘッダーは不要です。
6行目:当然、$requestBody
は空です(が、この空が必要です)。
9~13行目:サブスクリプトへ引数を渡して返却させる、というどれも共通の処理です。
16行目:ここも特に何も変わりません(ターゲット変数は変えてます)。
"https://sellingpartnerapi-fe.amazon.com" & $CanonicalUri
$cURLoption
リクエストが成功した場合、次のようなレスポンスになります。
{
"createdTime" : "2023-04-10T05:57:25+00:00",
"feedId" : "469999457",
"feedType" : "POST_INVENTORY_AVAILABILITY_DATA",
"marketplaceIds" : [ "A1VC38T7YXB528" ],
"processingEndTime" : "2023-04-10T05:58:10+00:00",
"processingStartTime" : "2023-04-10T05:57:31+00:00",
"processingStatus" : "DONE",
"resultFeedDocumentId" : "amzn1.tortuga.××××××××××××××"
}
resultFeedDocumentId
に注目して下さい。
このキーがレスポンスに含まれていない場合は、まだ3.の処理が完了していないことになります。その場合のレスポンスは次のようになります。
{
"createdTime" : "2023-04-10T05:57:25+00:00",
"feedId" : "469999457",
"feedType" : "POST_INVENTORY_AVAILABILITY_DATA",
"marketplaceIds" : [ "A1VC38T7YXB528" ],
"processingStatus" : "IN_QUEUE"
}
resultFeedDocumentId
キーの値が、次の5.で必要になります。
5. GET:4.の識別IDを用いて、結果レポートIDを取得
4行目:4.で返却された resultFeedDocumentId
を結合したものになります。
5行目:GET なので content-type
ヘッダーはふy...
6行目:当然、$requestBody
は空です(が、この空がひつy...)。
9~13行目:サブスクリプトへ引数を渡して返却させる、というどれも共通の処理です。
16行目:特に何も変わりません(ターゲット変数は変えてます)。
リクエストに成功すると、
{
"feedDocumentId" : "amzn1.tortuga.××××××××××××××",
"url" : "https://tortuga-prod-fe.s××××××××××××××××××××××××"
}
このようなレスポンスになります。
feedDocumentId
は4.のIDです、つまりこのIDの結果レポートは url
で取得して下さい。ということになります。ちなみに、このURLには有効期限があります。もたもたしていると、叩いてもエラーが返却されます(
6. GET:5.の結果レポートIDを用いて、結果レポートを取得
もう後は、5.の url
を叩くだけです。
一つ注意点があるとすれば、URLから挿入
は、デフォルトでは POST になります。きちんと GET を指定してあげましょう。
"--request " & Quote ( "GET" )
成功すれば、結果がxmlで返却されます。
<?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.02</DocumentVersion>
<MerchantIdentifier>××××××××</MerchantIdentifier>
</Header>
<MessageType>ProcessingReport</MessageType>
<Message>
<MessageID>1</MessageID>
<ProcessingReport>
<DocumentTransactionID>469999457</DocumentTransactionID>
<StatusCode>Complete</StatusCode>
<ProcessingSummary>
<MessagesProcessed>1</MessagesProcessed>
<MessagesSuccessful>1</MessagesSuccessful>
<MessagesWithError>0</MessagesWithError>
<MessagesWithWarning>0</MessagesWithWarning>
</ProcessingSummary>
</ProcessingReport>
</Message>
</AmazonEnvelope>
DocumentTransactionID
は、3.で返却された feedId
ですね。
署名というラスボスを含むcURLoption作成
さて、やってまいりました。
1.3.4.5.と何度も呼び出されていた $cURLoption
を作成するサブスクリプト。
むしろ、こっちが本体と言っても過言ではありません。
でも、
このサブスクリプトをしっかり作っておくことで、1.3.4.5.のスクリプトがスッキリします。と言うか...カスタム関数化するのが一番いいかもしれません(
※カスタム関数化したものは、またいずれ掲載します。
※しました(
ではスクリプトを見ていきます。
こちらの公式のリファレンスと変数名称や手順などを合わせていますので、見比べながら進めてみて下さい。
3~9行目:メインスクリプトから受け取った引数を、それぞれセットします。
12~14行目:どんな時も変わらず必要になる要素です。
手順1:正規リクエストを作成する
19行目:テキストを挿入
を使って正規リクエストのフォーマット $CanonicalRequestFormat
を設定します。後に、これに対して全置換をかけて正規リクエスト $CanonicalRequest
を作成します。
~HTTPMethod
~CanonicalUri
~CanonicalHeader
~SignedHeaders
~HashedPayload
21行目:$x_amz_date、日付(ISO8601)になります。ゼロ埋め処理もされている、前編で紹介させて頂いた株式会社フルーデンス 小巻さんの計算式を利用させて頂きます。
Let (
[
~timestamp_local = Get ( タイムスタンプ ) ;
~timestamp = GetAsTimestamp ( Int ( Get ( 現在の時刻 UTC ミリ秒 ) / 1000 ) ) ;
~date = GetAsDate ( ~timestamp ) ;
~dateY = Year ( ~date ) ;
~dateM = Right ( "0" & Month ( ~date ) ; 2 ) ;
~dateD = Right ( "0" & Day ( ~date ) ; 2 ) ;
~time = GetAsTime ( ~timestamp ) ;
~timeH = Right ( "0" & Hour ( ~time ) ; 2 ) ;
~timeM = Right ( "0" & Minute ( ~time ) ; 2 ) ;
~timeS = Right ( "0" & Seconds ( ~time ) ; 2 )
] ;
~dateY & ~dateM & ~dateD & "T" & ~timeH & ~timeM & ~timeS & "Z"
)
23行目:$CanonicalHeader
を作成します。引数として受け取る $SignedHeaders
が content-type;host;x-amz-access-token;x-amz-date
といった形ですので、これを改行区切りに変更し、それぞれ値を全置換で設定します。
Substitute ( $SignedHeaders ;
[ ";" ; ¶ ];
[ "content-type" ; "content-type:application/json" ];
[ "host" ; "host:sellingpartnerapi-fe.amazon.com" ];
[ "x-amz-access-token" ; "x-amz-access-token:" & $AccessToken ];
[ "x-amz-date" ; "x-amz-date:" & $x_amz_date ]
)
例えば、POST の場合 $CanonicalHeader
の中はこのようになります。
content-type:application/json
host:sellingpartnerapi-fe.amazon.com
x-amz-access-token:××××××××××××××××××
x-amz-date:20230410T132507Z
GET の場合、$SignedHeaders
の中に content-type
がないのでこうなります。
host:sellingpartnerapi-fe.amazon.com
x-amz-access-token:××××××××××××××××××
x-amz-date:20230410T132507Z
25行目:$requestBody
のハッシュ $HashedPayload
(小文字の 16 進数文字)を求めます。GET の時は当然 $requestBody
は空ですが、その「空」でのハッシュが必要になる為、空で問題ありません。
28行目:$CanonicalRequestFormat
に全置換をかけて $CanonicalRequest
を設定します。
Substitute ( $CanonicalRequestFormat ;
[ "~HTTPMethod" ; $HTTPMethod ];
[ "~CanonicalUri" ; $CanonicalUri ];
[ "~CanonicalHeader" ; $CanonicalHeader ];
[ "~SignedHeaders" ; $SignedHeaders ];
[ "~HashedPayload" ; $HashedPayload ];
[ ¶ ; Char ( 10 ) ]
)
改行コードが違う為、Char ( 10 )
への置換を忘れないで下さい。
手順2:正規リクエストのハッシュを作成する
33行目:正規リクエスト $CanonicalRequest
のハッシュ $HashedCanonicalRequest
(小文字の 16 進数文字)を求めます。
手順3:署名文字列を作成する
38行目:$StringSignFormat
全置換用フォーマット作成
~Algorithm
~RequestDateTime
~CredentialScope
~HashedCanonicalRequest
40行目:$StringSignFormat
に全置換をかけて $StringSign
を作成
Substitute ( $StringSignFormat ;
[ "~Algorithm" ; "AWS4-HMAC-SHA256" ] ;
[ "~RequestDateTime" ; $x_amz_date ] ;
[ "~CredentialScope" ; Left ( $x_amz_date ; 8 ) & "/us-west-2/execute-api/aws4_request" ];
[ "~HashedCanonicalRequest" ; $HashedCanonicalRequest ] ;
[ ¶ ; Char ( 10 ) ]
)
こちらも、改行コードが違う為 Char ( 10 )
への置換を忘れないで下さい。
手順4:署名を計算する
45行目:署名 $CalculateTheSignature
の計算をします。
Let (
[
~hmacDate = CryptAuthCode ( Left ( $x_amz_date ; 8 ) ; "SHA256" ; "AWS4" & $SecretKey );
~hmacRegion = CryptAuthCode ( "us-west-2" ; "SHA256" ; ~hmacDate );
~hmacService = CryptAuthCode ( "execute-api" ; "SHA256" ; ~hmacRegion );
~hmacSigning = CryptAuthCode ( "aws4_request" ; "SHA256" ; ~hmacService )
];
Lower ( HexEncode ( CryptAuthCode ( $StringSign ; "SHA256" ; ~hmacSigning ) ) )
)
もうファルシのルシがコクーンでパージですが、頑張りましょう。
auth
50行目:ようやく Authorization ヘッダーの値が完成です。
"AWS4-HMAC-SHA256 " &
"Credential=" & $AccessKey & "/" & Left ( $x_amz_date ; 8 ) & "/us-west-2/execute-api/aws4_request," &
"SignedHeaders=" & $SignedHeaders & "," &
"Signature=" & $CalculateTheSignature
一つ罠がありまして、リファレンスには「アルゴリズム(AWS4-HMAC-SHA256)と Credential の間にカンマはありません。」と記載がありますが、カンマはなくとも半角開けずに詰め詰めにするとエラーになります ガッデム!
そして伝説へ・・・
56行目:メインスクリプトへ返却する $cURLoption
の設定
List (
"--request " & Quote ( $HTTPMethod );
"--header " & Quote ( "Authorization: " & $auth );
"--header " & Substitute ( $CanonicalHeader ; ¶ ; ¶ & "--header " );
Case ( not IsEmpty ( $requestBody ) ; "--data " & Quote ( "@$requestBody" ) )
)
Substitute ( $CanonicalHeader ; ¶ ; ¶ & "--header " )
としているのは、$CanonicalHeader
を利用して、header を作る為です。
content-type:application/json
host:sellingpartnerapi-fe.amazon.com
x-amz-access-token:××××××××××××××××××
x-amz-date:20230410T132507Z
↓
--header content-type:application/json
--header host:sellingpartnerapi-fe.amazon.com
--header x-amz-access-token:××××××××××××××××××
--header x-amz-date:20230410T132507Z
まとめ
Feeds API は、一括でSKUの在庫更新できる利点があります。その代わり、手順が多いです。Feeds API を組み上げるのはちょっと辛いな...という方は、PATCH 処理を検討してみてはいかがでしょうか。レート制限に注意しループで回すだけですので、まだお手軽です。
ちなみに、Feeds API も当然レート制限がありますので注意です(
AccessKey・SecretKey・AccessToken の取得の仕方、それからPATCH 処理などは、また機会があれば、記事化したいと思います。
おわりに
いかがだったでしょうか。
AWS のラスボスの署名に比べたら、他の auth なんてもう何ともないですね、過程が天と地ほどの差、何が来ても驚かない。
でも、何が辛かったかって SP-API のリファレンスが全編英語...これが一番辛かったですね。
高校で赤点を取ってたくらい英語が本当に苦手ですから(
でも、悲しいけどEC通販土方なのよね、乗り越えるしかない。
もし不明点があれば、TwitterでコソーリDM下さい!
それでは
Let's enjoy FileMaker!
Discussion