[Shopify] X Amazon連携、商品情報　(1/2)

Shopify x Amazon連携に関してまとめていきます。　商品データ連携、注文データ連携、在庫データ連携、配送データ連携をカバーしていきます。

本日はまず、
Shopifyの商品情報をAmazonにプッシュする方法をカバーします。

長くなるので2部に分けました。まずは、前半を公開します。前半ではShopifyで商品情報が更新された最にWebhookで受け取るところから始まります。 下記がイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/ba30bf84-85dc-ac31-ba2b-3d01ca2dceea.png)


# 事前に準備するもの
- Shopifyアカウント
- Amazon MWS アカウント
- Azure アカウント
- Visual Studio

# ShopifyのWebhookを受け取る

まずはShopify上で商品情報に変更がある際に`Webhook`で更新された内容を探知します。

そのために`Azure Functions`を用意します。`Azure Functions`の用意に関しては下記を参照ください。
https://qiita.com/syantien/items/ab62db3b5ccec230db4e

`Webhook`に関してはShopifyにあるこちらのドキュメントをベースに進めていきます
https://help.shopify.com/en/api/getting-started/webhooks

## Shopifyで`Webhook`の購読を設定

メニュー右下の「設定」をクリック、設定メニューの「通知」をクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/4f7a2803-ed5f-2f70-f105-35489b797ed9.png)

通知設定画面の一番下部分に「Webhookを作成」ボタンがあるのでクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/a6058f2d-5ef0-e09b-2677-8b3035bb90ad.png)

次に`Webhook`の内容を選択し、それを受信する`URL`を入力します。
今回は商品の更新がある度にAmazonでも該当する商品の更新を行うので「商品更新」を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/a8fb4b38-95d8-096b-3328-6949b44112b3.png)

`Webhook`が作成されると、下記のように表示されます。ここ(①)に表記される`URL`が正しいことを確認し、下部分にある文字列(②)を使い、正しい`Webhook`であることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/29ba0bd4-540c-2455-d4fa-863e96154041.png)

こちらの[ドキュメント](https://help.shopify.com/en/api/reference/events/webhook)によるとヘッダーで下記の値が渡されるとのことです。


```
X-Shopify-Topic: orders/create
X-Shopify-Hmac-Sha256: XWmrwMey6OsLMeiZKwP4FppHH3cmAiiJJAweH5Jo4bM=
X-Shopify-Shop-Domain: johns-apparel.myshopify.com
X-Shopify-API-Version: 2019-04
```

`X-Shopify-Topic`で正しいトピックか確認、`X-Shopify-Hmac-Sha256`で②にあった文字列を使いSha256で暗号化された文字列が正しいか確認、`X-Shopify-Shop-Domain`で対象のストアーであることを確認、`X-Shopify-API-Version`でAPIのバージョンを確認してください。

## `Webhook`レスポンスクラスの定義

`products/create`または`products/update`の際のレスポンス型は下記です。

```json

{
  "id": 788032119674292922,
  "title": "Example T-Shirt",
  "body_html": null,
  "vendor": "Acme",
  "product_type": "Shirts",
  "created_at": null,
  "handle": "example-t-shirt",
  "updated_at": null,
  "published_at": "2019-12-18T12:06:06-05:00",
  "template_suffix": null,
  "tags": "example, mens, t-shirt",
  "published_scope": "web",
  "variants": [
    {
      "id": 642667041472713922,
      "product_id": 788032119674292922,
      "title": "",
      "price": "19.99",
      "sku": "example-shirt-s",
      "position": 0,
      "inventory_policy": "deny",
      "compare_at_price": "24.99",
      "fulfillment_service": "manual",
      "inventory_management": "shopify",
      "option1": "Small",
      "option2": null,
      "option3": null,
      "created_at": null,
      "updated_at": null,
      "taxable": true,
      "barcode": null,
      "grams": 200,
      "image_id": null,
      "weight": 200.0,
      "weight_unit": "g",
      "inventory_item_id": null,
      "inventory_quantity": 75,
      "old_inventory_quantity": 75,
      "requires_shipping": true
    },
    {
      "id": 757650484644203962,
      "product_id": 788032119674292922,
      "title": "",
      "price": "19.99",
      "sku": "example-shirt-m",
      "position": 0,
      "inventory_policy": "deny",
      "compare_at_price": "24.99",
      "fulfillment_service": "manual",
      "inventory_management": "shopify",
      "option1": "Medium",
      "option2": null,
      "option3": null,
      "created_at": null,
      "updated_at": null,
      "taxable": true,
      "barcode": null,
      "grams": 200,
      "image_id": null,
      "weight": 200.0,
      "weight_unit": "g",
      "inventory_item_id": null,
      "inventory_quantity": 50,
      "old_inventory_quantity": 50,
      "requires_shipping": true
    }
  ],
  "options": [
    {
      "id": 527050010214937811,
      "product_id": 788032119674292922,
      "name": "Title",
      "position": 1,
      "values": [
        "Small",
        "Medium"
      ]
    }
  ],
  "images": [
    {
      "id": 539438707724640965,
      "product_id": 788032119674292922,
      "position": 0,
      "created_at": null,
      "updated_at": null,
      "alt": null,
      "width": 323,
      "height": 434,
      "src": "\/\/cdn.shopify.com\/s\/assets\/shopify_shirt-39bb555874ecaeed0a1170417d58bbcf792f7ceb56acfe758384f788710ba635.png",
      "variant_ids": [
      ]
    }
  ],
  "image": null
}
```

ここで一つ、Visual Studioでのやばいテクニックを紹介。上記のサンプルJsonをコピーします。このJsonファイルのモデルを定義したいクラスファイルを開き、Visual Studioの「編集」→「形式を選択して貼り付け」→JSONクラスとして貼り付けるをクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/7de44b6a-2b8d-7ec6-59bc-508308861be2.png)

クリック後、下記のようなモデルが自動で生成されます。

```csharp
public class Rootobject
{
  public long id { get; set; }
  public string title { get; set; }
  public object body_html { get; set; }
  public string vendor { get; set; }
  public string product_type { get; set; }
  public object created_at { get; set; }
  public string handle { get; set; }
  public object updated_at { get; set; }
  public DateTime published_at { get; set; }
  public object template_suffix { get; set; }
  public string tags { get; set; }
  public string published_scope { get; set; }
  public Variant[] variants { get; set; }
  public Option[] options { get; set; }
  public Image[] images { get; set; }
  public object image { get; set; }
}

public class Variant
{
  public long id { get; set; }
  public long product_id { get; set; }
  public string title { get; set; }
  public string price { get; set; }
  public string sku { get; set; }
  public int position { get; set; }
  public string inventory_policy { get; set; }
  public string compare_at_price { get; set; }
  public string fulfillment_service { get; set; }
  public string inventory_management { get; set; }
  public string option1 { get; set; }
  public object option2 { get; set; }
  public object option3 { get; set; }
  public object created_at { get; set; }
  public object updated_at { get; set; }
  public bool taxable { get; set; }
  public object barcode { get; set; }
  public int grams { get; set; }
  public object image_id { get; set; }
  public float weight { get; set; }
  public string weight_unit { get; set; }
  public object inventory_item_id { get; set; }
  public int inventory_quantity { get; set; }
  public int old_inventory_quantity { get; set; }
  public bool requires_shipping { get; set; }
}

public class Option
{
  public long id { get; set; }
  public long product_id { get; set; }
  public string name { get; set; }
  public int position { get; set; }
  public string[] values { get; set; }
}

public class Image
{
  public long id { get; set; }
  public long product_id { get; set; }
  public int position { get; set; }
  public object created_at { get; set; }
  public object updated_at { get; set; }
  public object alt { get; set; }
  public int width { get; set; }
  public int height { get; set; }
  public string src { get; set; }
  public object[] variant_ids { get; set; }
}
```

ということで、Shopify `Webhook` (Product/Create Product/Update)のJsonを受けるC#モデルは上記です。

## `Webhook`を受ける`Function`

では、さっそくFunctionもこのデータを受け取るようにします。

```csharp

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using ShopifyWebhookHandler.Models;
using System.Text;
using System.Security.Cryptography;

namespace ShopifyWebhookHandler
{
    public class ShopifyWebHookHandlerProductUpdateHandler
    {
        private string _sharedSecret = "Your Key Here";

        [FunctionName("ShopifyWebHookHandlerProductUpdateHandler")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("Hello? Did you call me?");

            // Read all
            req.Headers.TryGetValue("X-Shopify-Topic", out var topic);
            req.Headers.TryGetValue("X-Shopify-Hmac-Sha256", out var hMacSha256);
            req.Headers.TryGetValue("X-Shopify-Shop-Domain", out var domain);
            req.Headers.TryGetValue("X-Shopify-API-Version", out var apiVersion);
            string requestBody = await new StreamReader(req.Body,Encoding.UTF8).ReadToEndAsync();

            if (topic != "")
            {
                // do something if it's not the tipc you are looking for
                log.LogInformation("Topic was: " + topic );
            }

            var keyBytes = Encoding.UTF8.GetBytes(_sharedSecret);
            var dataBytes = Encoding.UTF8.GetBytes(requestBody);

            var hmac = new HMACSHA256(keyBytes);
            var hmacBytes = hmac.ComputeHash(dataBytes);
            var createSignature = Convert.ToBase64String(hmacBytes);

            if (hMacSha256 == createSignature)
            {
                // do something if key does not match
                
            }

            log.LogInformation("I compare " + hMacSha256 + " vs " + createSignature);

            if (domain != "")
            {
                // do something if it's not the domain you are looking for
                log.LogInformation("Domain was: " + domain);
            }
            
            // Map data into ProductModel
            var data = JsonConvert.DeserializeObject<ProductModel>(requestBody);

            log.LogInformation("The product " + data.title + " was updated. Api version is " + apiVersion + ".");

            return data != null
                ? (ActionResult)new OkObjectResult("Good news, all good.")
                : new BadRequestObjectResult("Bad news, I got NULL.");
        }

        private string sha256(string randomString)
        {
            var crypt = new System.Security.Cryptography.SHA256Managed();
            var hash = new System.Text.StringBuilder();
            byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
            foreach (byte theByte in crypto)
            {
                hash.Append(theByte.ToString("x2"));
            }
            return hash.ToString();
        }
    }
}

```

ではさっそくテストをしてみます。テストは先ほどの管理画面右側から送ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/2f5934ce-123b-725a-0e80-00bd00181b27.png)

そして下記がFunctionsのLogになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/4c59eac9-5092-af81-0e83-de8381c4a826.png)

`X-Shopify-Hmac-Sha256`もちゃんと照合することができました。

# 次

次はAmzazon API を使って商品情報をプッシュします。