2019年10月15日

Amazon MWSを使ってみた


はじめに

こちらで Amazon MWS を使った案件について紹介しました。
しかし、このページだけだと実際に何をやったのか、技術的なことまでは載せていませんでした。 そこで、せっかく技術ブログがあるのだから、場所を借りて、実際にどういうことをやったのかを紹介していこうかと思います。 4つバッチがありますが、今回は受注作成バッチについてです。

概要

Amazon で未出荷状態の注文を取得して、データベースに反映するバッチです。
Amazon MWS の「注文API」を使用して、 Amazon から未出荷の注文を取得します。
そこで取得した「注文番号」から、その注文の商品情報を、「注文API」で取得します。
取得した情報を、データベースに沿った形に変更、データベースに注文として登録します。

事例紹介ページでの説明です。
これを具体的に、コード例などを交えて説明していきます。
なお、使用言語は PHP です。

具体的な流れ

Amazon から未出荷状態の注文情報を取得

Amazon MWS を通して、未出荷状態の注文リストを取得します。
ざっくりとした流れはこんなかんじ。

Amazon MWS にリクエストを投げて、レスポンスを受け取ります。

リクエストを送る

MWSから提供されている「注文API」を使用して、 Amazon から注文リストをとってきます。
注文リストは、以下の2種類をそれぞれ対応した API を使用して取得します。

注文リスト 取得する内容 使用するAPI
注文情報一覧 注文番号、購入者情報、お届け先住所、Emailなど ListOrders 、 ListOrdersByNextToken
注文商品一覧 指定された注文で購入された商品情報。
注文番号、商品番号、商品名、価格など
ListOrderItems 、 ListOrderItemsByNextToken

レスポンスで取得できるものについては、公式ドキュメントに一覧が掲載されているので、そちらを見てください。

リクエスト処理

ここでは、 ListOrders を例にあげていきます。
商品一覧を取得する際の ListOrderItems も、基本的には同じ流れです。 公式ドキュメントのレスポンスサンプルは、ながーいURLみたいな形式になっています。 が、今回は Amazon から提供されているクライアントライブラリを使用したため、以下のようなにリクエストをつくりました。
クライアントライブラリ内で署名のリクエストなどを処理してくれます。便利。

$serviceUrl = "https://mws.amazonservices.jp/Orders/2013-09-01";

$config = [
    'ServiceURL' => $serviceUrl,
    'ProxyHost' => null,
    'ProxyPort' => -1,
    'ProxyUsername' => null,
    'ProxyPassword' => null,
    'MaxErrorRetry' => 3,
];

$service = new MarketplaceWebServiceOrders_Client(
    'AWSAccessKeyId',
    'secret_key',
    'application_name',
    'application_version',
    $config,
);

$request = new MarketplaceWebServiceOrders_Model_ListOrdersRequest();

// パラメーターの設定
$request->setMarketplaceId('market_place_id');
$request->setOrderStatus(['Unshipped', 'PartiallyShipped']);
$request->setLastUpdatedAfter('2016-01-01T00:00:00');
$request->setFulfillmentChannel('MFN');
$request->setSellerId('merchant_id');

// リクエストの送信
$response = $service->ListOrders($request);

公式クライアントライブラリで提供されているサンプルを参考にしています。
むしろそっくりそのまま。ちょっと違うのが、パラメーターの部分です。
サンプルでは、 setSellerId しかないですが、未出荷のものをとってきたいので、ステータスなどのパラメーターも設定しています。 なにをつかって設定すればいいかは、 Model の下にある PHP ファイルを見てください。
ListOrders の場合は、 ListOrdersRequest.php です。
リクエストで実行している内容が書かれています。そこに、 setほにゃらら といった、パラメーターを設定する関数があります。

レスポンス処理

$response = $service->ListOrders($request);

リクエストを送る処理です。
クライアントライブラリでリクエストを送ってくれます。 $response に、返ってきましたレスポンスが入りますが、これだけだと表示させようとしてもできません。
ので、XML形式に変換してから、配列として読み込みます。

// レスポンス内容をXMLに変換
$dom = new DOMDocument();
$dom->loadXML($response->toXML());
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$response = $dom->saveXML();

// XMLを配列に変換
$xml_data = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA);
$data = json_decode(json_encode($xml_data), true);

$dataを見ると、配列で格納されています。
どういったふうに入るかは、公式のレスポンスXMLサンプルを見てください。

次ページのリストを追加する

注文が多い場合は、一度に取得できません。ページごとに区切られます。
返ってきたレスポンスに NextToken があるときは、残っているページがあるという意味になります。 次ページを取得するには、「ListOrdersByNextToken」「ListOrderItemsByNextToken」を使用します。
さらにページがあったときは、また追加で取得することになります。 ListOrdersByNextToken では、 NextToken を送ることで、次のページの注文リストを取得することができます。
3ページ目があった場合は、 ListOrdersByNextToken のレスポンスに、さらに NextToken が返ってきます。
ざっくりいえば、 NextToken は次のページへのリンクみたいなかんじです。
次のページがなかったら、 NextToken は返ってきません。

$request->setNextToken('next_token');

「ListOrdersByNextToken」「ListOrderItemsByNextToken」を使用するときは、「ListOrders」で設定したパラメーターは不要です。引き継がれているので。
設定するパラメーターは NextToken のみです。( config とアクセスキーとかの設定は必要)

リストを整形する

最初に取得したリストと、追加で取得したリストは、この時点ではバラバラの配列に入っています。
このままだと、注文を一括で処理しようとしたとき、ループ処理ができない…などの問題が発生してしまいます。
そのため、配列をマージする処理をはさんで、ひとつにまとめておきます。

$order_list = array_merge($order_list, $next_list);

商品情報を取得する

リクエスト処理とレスポンス処理のやり方は、ほぼほぼ変わりません。
注文情報とおなじく、APIを使用して情報を取得します。

$serviceUrl = "https://mws.amazonservices.jp/Orders/2013-09-01";

$config = [
    'ServiceURL' => $serviceUrl,
    'ProxyHost' => null,
    'ProxyPort' => -1,
    'ProxyUsername' => null,
    'ProxyPassword' => null,
    'MaxErrorRetry' => 3,
];

$service = new MarketplaceWebServiceOrders_Client(
    'AWSAccessKeyId',
    'secret_key',
    'application_name',
    'application_version',
    $config,
);

$request = new MarketplaceWebServiceOrders_Model_ListOrderItemsRequest();

// パラメーターの設定
$request->setAmazonOrderId('order_id');

// リクエストの送信
$response = $service->ListOrderItems($request);

ほぼ一緒。
ListOrderItems で取得できるのは、注文番号に紐づいた注文商品です。
注文番号が1の場合、注文番号1で注文された商品しか取得できません。
注文番号2の商品を取得したいときは、またこの処理をする必要があります。 もし、 ListOrders で取得した注文全部の商品をとってきたい!というときは、 ListOrders で取得した注文分回す必要があります。

次のページを取得する

ListOrders と同じように、 NexToken が返ってきたときは、 NextToken で次ページを取得します。
やり方は ListOrders と同じになるため、カット。

データベース登録処理

ここまでがAWSを使用した処理です。
ここからは AWS から取得したデータを、各データベースに振り分けて登録する処理になります。
MWS でとってきたデータを見て、これだったらこういうふうに登録、これだったらこの値で登録、とかしているだけなので、かんたんな説明程度にさせていただきます。

注文ごとにまわす

ListOrders でとってきた注文分登録するので、 foreach でまわします。

$order_list = 'ListOrders でとってきた注文一覧';
// XMLをそのまま配列に変えただけだと、 NextToken とかも含まれてるのでそのままループに回せません。
// 配列から Orders の中だけ取り出すなどしてください。

$order_info = 'ListOrderItems でとってきた注文商品一覧';
// 前もって全商品を取得、キーが注文番号の配列に変えています。

foreach ($order_list as $order) {
    foreach ($order_info as $info) {
        $items = [];
        // 注文番号が同じ商品を取得
        if ($info['AmazonOrderId'] == $order['AmazonOrderId']) {
            $items = array_merge($items, $info['OrderItems']['OrderItem']);
        }
    }
    // ここでデータベースにインサート処理
}

今回の案件の仕様が、ループ内で商品を取得するのではなく、あらかじめ商品を全部取得してから…とのことだったため、注文番号をキーとした配列に変更している…などちょっとまわりくどいやり方をしています。
「注文番号が同じ商品を取得」のところで、 ListOrderItems を使って商品一覧を取得する方法でもいいと思います。

データベースにインサート

正直インサート自体は記載することはないです。
なので、インサート時に注意する必要のありそうなデータについて説明しようかと思います。

名前

購入者や送り先の名前です。
ListOrders の ShippingAddress.Name に入っていますが、苗字と名前が一体型です。
もし、インサート先のデータベースが苗字と名前に分けていたら、どちらかを空で登録する必要があります。

商品金額

ListOrderItems の ItemPrice に入っています。
が、公式ドキュメントをよく読むと、以下のように記載されています。

注文商品は商品と数量です。 つまり、ItemPriceの値は、商品の販売価格を注文数量で乗算した値です。 ItemPriceにはShippingPriceとGiftWrapPriceが含まれません。

Amazonマーケットプレイス Web サービス (Amazon MWS) ドキュメント

単価ではないことに注意してください。
単価を出す場合は、 ItemPrice を商品の数量 QuantityOrdered で割ってください。

時間

時間全般です。
MWS で扱われている時間は、以下のような ISO 8601日付フォーマット で返ってきます。

2014-10-10T13:50:40.000+09:00

おわり

以上が、受注作成バッチでやっていたことになります。 仕組み自体は思い返せば、そこまで大変なものではなかったように思えるのですが、Amazon MWS に触れるのが初めてだったため、難しく感じました。
また、 MWS に関する解説などが、検索をかけてもあまりヒットしないところも、難易度の要因のひとつでした。 仕様で注文をとってきた後に、すぐに商品もとってこなければならなかったため、注文と商品を紐づけて呼び出せるような配列の形にするのも、思いつくまでに少し時間がかかってしまいました…。

ここまで読んでいただいてありがとうございました!

この案件の事例については、こちらをご覧ください。


[technical blog]