Amazon マーケットプレイスWebサービス (MWS) APIから注文情報を取得する方法

(コメント)

当社では、Amazon のマーケットプレイスに出店していたり、FBA (フルフィルメントByアマゾン: Amazon社の倉庫に商品を納品し、販売を代行してもらう販売方法) を行っています。

マーケットプレイスにはAPIが用意されており、リクエストすることで受注情報など多くの情報を取得できるのですが、署名の計算に少し躓いたので書いておきます。

Amazon MWS 公式ガイド

アクセスに必要な情報を集める

認証情報 (クレデンシャル)

アクセスに必要な認証情報は、

  1. 出品者ID (マーチャントID, セラーIDと呼ばれることもある)
  2. AWSアクセスキーID
  3. 秘密キー(シークレットキー)

の3つです。もし、アクセスするアカウントがセラーセントラルのアカウントではなく、派生して作られた子アカウントである場合、別途「MWS認証トークン」が必要になります。

これらの情報は、すべてセラーセントラルの「設定」→「ユーザー権限」のページで取得できます。AWSアクセスキーID,秘密キー は、ページ下部の「Amazon MWS 開発者権限」の、「認証情報を表示」をクリックすると表示されます。

マーケットプレイスID

別途、マーケットプレイスID という文字列が必要になる場合があります。

マーケットプレイスIDの一覧はページから確認できます。

Amazon マーケットプレイス Web サービスエンドポイント

例えば、日本なら A1VC38T7YXB528 です。

APIアクセスのテストをする

Amazon社がAPIのテストツール「Scratchpad」を公開しているので、それを使います。後述する HMAC の計算が正しいか確認する意味でも、このツールは必ず使ってみたほうが良いです。

  1. Amazon MWS Scratchpad を開く
  2. 左上「API Selection」を適当に選択。今回は、API セクション: 注文、Operation: ListOrders
  3. Authentication 欄に認証情報を入力、SellerId: には 出品者ID, MWSAuthToken は元アカウントなら空、払い出された子アカウントならトークン文字列を入れる。AWSAccessKeyId、Secret Kye は先ほど取得した文字列を取得。
  4. API必須パラメータの「MarketplaceId.Id.1」には、A1VC38T7YXB528 を入力
  5. API任意パラメータの「LastUpdatedAfter」のみ、2017-05-05 のように入力
  6. 「送信」をクリックすると、結果が表示されます。

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())

下品にベターっと書いてますが、これで動きます。

実際にはこれをライブラリ化して肉付けしていくと良いでしょう。

現在の評価: 2

コメント

検索

最近のツイート

  • ytyng

    ytyng @ytyng

    class SerialCode って書こうとして、class Serialcode (非キャメルの1単語) の方がいいのではと思ってしまう。Datasturcture, Javascript みたいに非キャメル1単語で書きそうになるのは症状名があるのだろうか
    2 日, 16 時間 前

  • おてふ

    おてふ @otef

    ytyng

    ほんとうに頭がおかしいコマしかないのに読後感は非常に爽やかという奇跡のマンガ、男日本海が今なら無料なのでみなさん読んでおいたほうがいいですよほんとに https://t.co/AsIZsNuwiP https://t.co/kKl77lJIlH
    1 週間, 5 日 前

  • ytyng

    ytyng @ytyng

    昼間なのに影が多方向に出るのってちょっと面白い https://t.co/x1scFI2Uk2
    2 週間, 3 日 前

  • ytyng

    ytyng @ytyng

    https://t.co/FUChcVIVpm になって、パッケージの登録ができないんですけど、Python有識者の方にどうしたらいいか教えてほしい。 https://t.co/AEkCfbml6Z このガイドの通り twine 使っても HTTPError 410 っていう
    2 週間, 5 日 前

  • ytyng

    ytyng @ytyng

    PyPIあがんね
    2 週間, 5 日 前