当社では、Amazon のマーケットプレイスに出店していたり、FBA (フルフィルメントByアマゾン: Amazon社の倉庫に商品を納品し、販売を代行してもらう販売方法) を行っています。
マーケットプレイスにはAPIが用意されており、リクエストすることで受注情報など多くの情報を取得できるのですが、署名の計算に少し躓いたので書いておきます。
アクセスに必要な情報を集める
認証情報 (クレデンシャル)
アクセスに必要な認証情報は、
- 出品者ID (マーチャントID, セラーIDと呼ばれることもある)
- AWSアクセスキーID
- 秘密キー(シークレットキー)
の3つです。もし、アクセスするアカウントがセラーセントラルのアカウントではなく、派生して作られた子アカウントである場合、別途「MWS認証トークン」が必要になります。
これらの情報は、すべてセラーセントラルの「設定
」→「ユーザー権限」のページで取得できます。AWSアクセスキーID,秘密キー は、ページ下部の「Amazon MWS 開発者権限」の、「認証情報を表示」をクリックすると表示されます。
マーケットプレイスID
別途、マーケットプレイスID という文字列が必要になる場合があります。
マーケットプレイスIDの一覧はページから確認できます。
Amazon マーケットプレイス Web サービスエンドポイント
例えば、日本なら A1VC38T7YXB528
です。
APIアクセスのテストをする
Amazon社がAPIのテストツール「Scratchpad」を公開しているので、それを使います。後述する HMAC の計算が正しいか確認する意味でも、このツールは必ず使ってみたほうが良いです。
- Amazon MWS Scratchpad を開く
- 左上「API Selection」を適当に選択。今回は、API セクション:
注文
、Operation:ListOrders
- Authentication 欄に認証情報を入力、SellerId: には 出品者ID, MWSAuthToken は元アカウントなら空、払い出された子アカウントならトークン文字列を入れる。AWSAccessKeyId、Secret Kye は先ほど取得した文字列を取得。
- API必須パラメータの「MarketplaceId.Id.1」には、
A1VC38T7YXB528
を入力 - API任意パラメータの「LastUpdatedAfter」のみ、
2017-05-05
のように入力 - 「送信」をクリックすると、結果が表示されます。
HMAC の値を確認しておく
結果が正常に表示された場合、「リクエスト」タブをクリックして開いてみると「署名対象の文字列」というセクションと、その下に計算した HMAC署名 が表示されています。APIを開発する時は、この情報を元に開発するとやりやすいです。「証明対象の文字列」に対して SHA 256 HMAC で計算を行い、その下に書かれている文字列が結果で得られるよう開発をしていきます。
APIライブラリを開発する
Pythonで作ります。
署名方法は、Amazonで「署名バージョン2」と言われる方法です。
公式ドキュメントの署名のロジック説明 (Java のサンプルコードあり)
HMACの計算ロジックの作成
Python には hmac ライブラリがあるので、それを使えばすぐにできます。
Python3での例
import hmac
import hashlib
import base64
secret_key = b"取得した秘密キー"
canonical = b"""
POST
… 「証明対象の文字列」をここにコピペ …
"""
h = hmac.new(secret_key, canonical.strip(), hashlib.sha256)
print(h.hexdigest())
print(base64.b64encode(h.digest()))
これを実行すると、Scratchpad に表示されている「SHA 256 HMAC」「Base64 HMAC」と同じ値が取得できるはずです。
署名対象の文字列の作成
署名対象の文字列は、HTTPメソッド(POST)、ドメイン名(mws.amazonservices.jp)、パス(/Orders/2013-09-01)、それとクエリ文字列を、改行(\n
)で連結して作ります。
クエリ文字列の作成
クエリ文字列は、検索パラメータ名をソートさせ、&
と =
で連結して作ります。値は URL エンコードします。
ディクショナリで値を用意してたとすると、
import datetime
import urllib.parse
AMAZON_CREDENTIAL = {
'SELLER_ID': 'セラーID',
'ACCESS_KEY_ID': 'AWSアクセスキーID',
'ACCESS_SECRET': 'アクセスシークレット',
}
data = {
'AWSAccessKeyId': AMAZON_CREDENTIAL['ACCESS_KEY_ID'],
'Action': 'ListOrders',
'MarketplaceId.Id.1': 'A1VC38T7YXB528',
'SellerId': AMAZON_CREDENTIAL['SELLER_ID'],
'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2',
'Timestamp': datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
'Version': '2013-09-01',
}
query_string = '&'.join('{}={}'.format(
n, urllib.parse.quote(v, safe='')) for n, v in sorted(data.items()))
print(query_string)
このようなロジックで作成できます。
sorted
メソッドでキーで並び替えを行い、値は urllib.parse.quote
で URLエンコードします。safe=''
を入れないと /
がエンコードされないので、入れます。
後は、改行で連結すれば署名対象文字列になります。
canonical = "{}\n{}\n{}\n{}".format(
'POST', 'mws.amazonservices.jp', '/Orders/2013-09-01', query_string
)
print(canonical)
署名をつけてリクエストする方法
リクエストメソッドは POST です。ですが、POSTのデータは空で、パラメータはURLのクエリストリングに入れます。
署名は、クエリストリングの末尾に &Signature=署名
という形で付与します。
requests でリクエストしてみる
実際にリクエストするコードを書いてみます。
import base64
import datetime
import hashlib
import hmac
import urllib.parse
import requests
import six
AMAZON_CREDENTIAL = {
'SELLER_ID': 'セラーID',
'ACCESS_KEY_ID': 'AWSアクセスキーID',
'ACCESS_SECRET': 'アクセスシークレット',
}
DOMAIN = 'mws.amazonservices.jp'
ENDPOINT = '/Orders/2013-09-01'
def datetime_encode(dt):
return dt.strftime('%Y-%m-%dT%H:%M:%SZ')
timestamp = datetime_encode(datetime.datetime.utcnow())
last_update_after = datetime_encode(
datetime.datetime.utcnow() - datetime.timedelta(days=1))
data = {
'AWSAccessKeyId': AMAZON_CREDENTIAL['ACCESS_KEY_ID'],
'Action': 'ListOrders',
'MarketplaceId.Id.1': 'A1VC38T7YXB528',
'SellerId': AMAZON_CREDENTIAL['SELLER_ID'],
'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2',
'Timestamp': timestamp,
'Version': '2013-09-01',
'LastUpdatedAfter': last_update_after,
}
query_string = '&'.join('{}={}'.format(
n, urllib.parse.quote(v, safe='')) for n, v in sorted(data.items()))
canonical = "{}\n{}\n{}\n{}".format(
'POST', DOMAIN, ENDPOINT, query_string
)
h = hmac.new(
six.b(AMAZON_CREDENTIAL['ACCESS_SECRET']),
six.b(canonical), hashlib.sha256)
signature = urllib.parse.quote(base64.b64encode(h.digest()), safe='')
url = 'https://{}{}?{}&Signature={}'.format(
DOMAIN, ENDPOINT, query_string, signature)
response = requests.post(url)
print(response.content.decode())
下品にベターっと書いてますが、これで動きます。
実際にはこれをライブラリ化して肉付けしていくと良いでしょう。