Rubyで Amazon SP-API の初歩レベルの認証を突破するためのメモ

Amazonが SP-APIを発表しましたが、連日以下のようなエラーに悩まされていませんか？　自分も同じような壁に悩まされたのでメモします。

```json
{
    "errors": [
        {
            "message": "Access to requested resource is denied.",
            "code": "Unauthorized",
            "details": ""
        }
    ]
}
```

最初は以下のライブラリを試したのですが、どれを使っても認証エラーなので自分で書くことにしました。

* PYTHON-AMAZON-SP-API - https://sp-api-docs.saleweaver.com/
* Ruby Muffin - 発展途上
* Ruby amz_sp_api - 内部でlibcurlを呼ぶため、windowsでは不向き
  

# アカウントを作成し、postmanで動作確認

神のようなドキュメントがあるので、以下の手順で環境を作ります。

* https://zats-firm.com/2021/09/07/amazon-mws-%e3%81%8b%e3%82%89-sp-api-%e3%81%b8%e3%81%ae%e7%a7%bb%e8%a1%8c-%e6%ba%96%e5%82%99%e7%b7%a8/
* https://zats-firm.com/2021/09/08/amazon-mws-%e3%81%8b%e3%82%89-sp-api-%e3%81%b8%e3%81%ae%e7%a7%bb%e8%a1%8c-postman%e3%81%ab%e3%82%88%e3%82%8b%e3%83%86%e3%82%b9%e3%83%88%e6%8e%a5%e7%b6%9a%e7%b7%a8/


postmanを使った、スクラッチパッドでの動作確認は必須です。時間をドブに捨てないためにも、必ずpostmanで動作確認しましょう。

## postman でアクセストークンを取得しているところ

https://web.postman.co/

「アクセストークンを取得」がダメなら何をやってもダメです。
時間をドブに捨てないために、postmanでアクセストークンが取得できることを検証しましょう。

![gettoken.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666068/15788d1d-5f2f-5ca4-3d4a-7bdd28cf94c0.png)

## トークンを元に、ASINから商品名を得るところ

なおAmazonデベロッパーセントラルの「アプリの認証」設定は、即時反映ではありません。"The LWA secret token you provided has expired."エラーが出る場合は、アプリを認証してから一時間ほど待ったほうがよいでしょう。

![authz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666068/84188aab-d99b-6b86-af34-59c4540e4479.png)

![kojien.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666068/7b4a2138-c33b-a801-a00c-5a4bf8bea811.png)


## HTTPの何処に、何を入れるのか？
エラーになった場合、入れる場所が３か所あるので、そこを間違っておりませんか？

| この場所に | これを入れる |
|:-:|:-:|
| params <br>（別名 QUERY_STRING）  | GETなAPIでつかうやつ   |
|  HTTP request header  |    x-amz-access-token: Atzr●●●●●● <br> Content-Type : application/json
| body （別名 POST DATA）| これはJSON textになっている必要がある|


# あのパラメータはどこにあるの？
refresh_token は、Amazonデベロッパーセントラルにあります。
![Clipboard01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666068/074ebba2-75f0-6fad-4aa5-9d33ec60e1e2.jpeg)

client_id  / client_secret は、デベロッパーセントラルで、各アプリの「LWA認証情報を表示」ボタンから見れます。
![cb2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666068/fc932aee-756e-2116-9cf1-baecd3f20f32.jpeg)

aws_access_key_id /  aws_secret_access_key は、AWS IAMから参照します。
![iam.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666068/189868e6-4154-8a74-0be6-ef95d5350507.jpeg)


# Rubyでsp-apiを叩いてみる

ASINを指定すると、商品のタイトルを取得する初歩のAPIで実験してみます。

ヘッダに認証サインする箇所がものすごく面倒なので、公式のAWS SDKを使っています。

> gem install aws-sigv4 

```ruby
require "aws-sigv4"
require "json"

require "faraday"
require "faraday_json"

#認証データ
CREDENTIALS = {
    # 以下の情報はデベロッパーセントラルから取得します
    refresh_token: "Atzr|IwE●●●●●●●●●●●●●●●●●●●●●●●●●●●●●Wo",
    client_id: "amzn1●●●●●●●●●●●●●●●●●●●●●●●●●●●●●c1",
    client_secret:  "amzn1.●●●●●●●●●●●●●●●●●●●●●●●●●●●●●3b",

    # 以下の情報はIAM画面から取得します
    aws_access_key_id:  "AKI●●●●●●●●●●●●●●●●●●●●●●●●●●●●●73",
    aws_secret_access_key: "0EOE●●●●●●●●●●●●●●●●●●●●●●●●●●●●●Ii",
}


##---------------
# 認証トークンを設定する
def login_with_amazon(cred)
	data = {
		:grant_type => "refresh_token", 
		:refresh_token => cred[ :refresh_token ],
		:client_id => cred[ :client_id ], 
		:client_secret => cred[ :client_secret ] }


	conn = Faraday.new("https://api.amazon.com")
	conn.response :json
	res =  conn.post("/auth/o2/token", data).body

	return res["access_token"]
end

#
# ASINを指定すると商品のタイトルを得る Catalog Items API v2020-12-01 
#  @see https://developer-docs.amazon.com/sp-api/docs/catalog-items-api-v2020-12-01-reference
#
def catalogitem(cred, token, asin)
	signer = Aws::Sigv4::Signer.new(
		service: 'execute-api',
		region: 'us-west-2',
		access_key_id: cred[:aws_access_key_id],
		secret_access_key: cred[:aws_secret_access_key]
	)
	url = "https://sellingpartnerapi-fe.amazon.com/catalog/2020-12-01/items/#{asin}?marketplaceIds=A1VC38T7YXB528"


	signature = signer.sign_request(
		http_method: 'GET',
		url: url,
		headers: {
			"host" => "sellingpartnerapi-fe.amazon.com",
			'User-Agent' => 'ruby/sp-api',
			"x-amz-access-token" => token,
			"x-amz-date" => Time.now.getutc.xmlschema.tr('-:',"")
		},
	  )

	pp sig_headers = signature.headers
	sig_headers["x-amz-access-token"] = token

	conn = Faraday.new()
	conn.headers = sig_headers
	res = conn.get(url).body

	return JSON.parse(res)

end

token = login_with_amazon(CREDENTIALS)

pp catalogitem(CREDENTIALS, token, "4000801317")		# 4000801317 は広辞苑のASIN
```

この例では広辞苑のASINを指定していて、以下のような結果が取得できます

```json
{"asin"=>"4000801317", "summaries"=>[{"marketplaceId"=>"A1VC38T7YXB528", "browseNode"=>"500272", "itemName"=>"広辞苑 第七版(普通版)", "manufacturer"=>"岩波書店"}]}
```

おしまい。