GoとGAEでWebhookを使ったプライベートなShopifyアプリを作る

今回は[Shopify](https://www.shopify.jp/)アプリを[Google Cloud Platform](https://console.cloud.google.com/?hl=ja)のGAE(Google App Engine)を使って[Go](https://qiita.com/tags/go)で書いてみました。

## [Shopifyとは？](https://www.shopify.jp/about)
<img width="1603" alt="Shopify 日本語サイト" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/0a3812b4-f9e5-b2f4-fbb5-3eadfde400c5.png">
**Shopify**とは、カナダ産のEC（e-Commerce）のSaaSです。ひと昔前風に言うと、GMOメイクショップや、フューチャーショップ、Baseの様なEC向けのASPです。
現在、カナダだけではなく、北米はもとより世界中で利用されており、昨年くらいからアジアオセアニア地域でのマーケティングにお力を入れ始めた、**ECプラットフォーム界の巨人**です。
グローバルに展開しているサービスなので、世界中のローカルの決済等が利用でき、越境EC向けのECプラットフォームです。

標準機能も素晴らしいのですが、***Shopifyアプリ***というサードパーティーのアプリでその機能を拡張できる点も魅力的な点です。
すでに世界中で様々な**Shopifyアプリ**が開発されており、[日本国内でも増えてきています。](https://apps.shopify.com/collections/apps-for-japan?locale=ja)
<a href="https://apps.shopify.com/collections/apps-for-japan?locale=ja" target="_blank"><img width="1602" alt="スクリーンショット 2019-10-19 20.48.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/68c47bca-9238-9bfc-83c9-de29f0f02564.png"></a>

## Shopifyアプリとは？
じゃあ、具体的にShopifyアプリってどうなってんの？というと、基本的に**ShopifyのWebAPI**を叩くsomethingです。
APIには伝統的な**REST**のAPIと最近流行りの**GraphQL**があります。
![shopify_app_arc@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/c5f7ae7e-1cf7-441c-99b3-010a3a612d4e.png)


Shopify内の何かしらのイベントに対応した**Event**か**Webhook**でキックされる感じです。
APIとアプリにはいくつか種類があって、~~全体的なAPIと、管理画面上の要素をごにょごにょするAPIとかフロント用のAPIとか実店舗や倉庫向けのPOSアプリ用のAPIとかもあります~~**よくわかりません**。とりあえず今回の要件に必要な分だけ調べました。

Shopifyアプリには、大きく分けて公開されてて誰でも利用することができる**Public Apps**と、その店舗専用みたいな**Private Apps**があります。 
[^1]: https://help.shopify.com/en/api/getting-started#build-an-app

### Private Apps
ShopifyのAppストアに出さないそのショップ専用のアプリ。Public Appと違って、認証周りが楽。トークンとか取得しなくて良い。``https://{API_key}:{password}@{shop}.myshopify.com/admin/api/2019-10/shop.json``みたいにBasic認証でリクエストして処理できる。今回使ったのはこれ
[^2]: FQDNにIDとパスワード出るからちょっと危ない。このIDとパスワードが漏れると個人情報とか取り放題になる。

### Public Apps
ShopifyのAPPストアからインストールできる。APIの接続には認証とトークンが必要。一般的なAppsといったらこっち。
App Storeに公開しなくても使える。その場合はPrivate Appsっぽい扱いができる。Private Appじゃないメリットは後述のEmbedded SDKとかが使えること。管理画面系のアプリはPublicにしないとできない。
#### 自分が作ったアプリを広く売りたいなら通常のShopify App
**Public Apps**はShopifyの利用者に向け販売できます。だいたいのアプリがサブスクリプション方式なので、定期的な収入が欲しい方は作ってみてはいかがでしょうか？
英語など多言語に対応すれば市場はものすごく大きいので、ポテンシャルは高いです。ただ、現状だと日本語と日本にしか対応していないアプリは厳しそうです。実際、現在日本語で提供されているアプリも、決済系かロジ系が多く、アプリじゃなくて自社サービスと連携させるアプリを出して、自社サービスで利益を出すタイプが多い様です。

#### Embedded apps
なぜこれがPublic AppsとPrivate Appsと同じレベルで説明されているのかわからないけど、上記のPublic Appsで使える管理画面系のアプリのためのSDKがあり、これらを使う様なアプリはEmbedded appsと呼ばれる。

- [Embedded App SDK](https://help.shopify.com/en/api/embedded-apps/embedded-app-sdk)
    - 管理画面に埋め込む系の何かを作る時用のSDK
- ~~[POS App SDK](https://help.shopify.com/en/api/embedded-apps/shopify-pos-app-sdk)~~
    - iPadとかのタブレットなどで使える[Shopify POSアプリ]()があり、それ用のSDK。今は非推奨。iOS9以降は対応してないっぽい。
- [Shopify App Bridge](https://help.shopify.com/en/api/embedded-apps/app-bridge)
    - Embedded App SDK、POS SDKに代わる新しいしくみ。JavaScriptのライブラリで、管理画面やShopify POS Appのフロントエンド向け。


## どの言語でShopify Appを作るか？

ぶっちゃけどれでも良い。APIとのやり取りがメインなので、何だったらCでもアセンブラでも良い。
RESTかGraphQL叩ければ良い。ただ、いろんな言語でShopify API用のライブラリが出てるのでそれらを使うと楽。

### 公式に推してるのはRuby on Rails
https://github.com/Shopify/shopify_app
最初、Railsで書いてたけど、Railsが動くApp Serverを立てるのとそのメンテが面倒だったので途中でやめた。たぶんHerokuとか使えば良いんだろうけど、嫌いなのでテンション下がってやめた。今回はクライアントにとってもHerokuとRailsを使うメリットはあまり無かった。

#### Shopify自体がRubyで書かれていて、UAがRubyでAPPにアクセスしてくる。

Shopifyって中は何で書いてるんだろう？って思ってたんですが、Wenhookに関してはアクセスしてくるUAがRubyとなってるので、中でも結構Rubyが使われてるのかも。 Matzすごい

### Ruby以外の言語のライブラリ
Ruby on Railsで書くのを止めた段階で候補は**Go**か**PHP**しかなかったんだけど、ざっくり調べて見た。

- Go
    - **[go-shopify](https://github.com/bold-commerce/go-shopify)**（今回使ったやつ）
        - [Transaction追加できる様にした僕のリポジトリ](https://github.com/tao-s/go-shopify)
- PHP
    - [Shopify公式](https://github.com/Shopify/shopify-php) （メンテされてない）
    - [Laravel Shopify App](https://github.com/ohmybrew/laravel-shopify)
    - [Symfony Shopify Bundle](https://github.com/Richtermeister/symfony-shopify-bundle) （最近コミットされてなくて怪しい）
- node
    - [Shopify API Node.js (Official module)](https://github.com/MONEI/Shopify-api-node)
- Java
    - [Shopify SDK](https://github.com/channelape/shopify-sdk)ちゃんとメンテされてるっぽい
    - [Shopify4J - Caffeinated Shopify Bindings](https://github.com/csaunders/Shopify4J)Android用（メンテされてない）
- Elixir
    - [Shopify API](https://github.com/nsweeting/shopify)

世界中で使われてるSaaSなだけあって、とにかくいっぱいある。ただ、日本語対応とか日本向けはまだまだ少ないので、日本人的には辛い。日本人頑張らねば
<img width="420" alt="スクリーンショット 2019-10-19 18.36.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/9bd07e1f-4667-b971-7254-526818f448a6.png"><img width="420" alt="スクリーンショット 2019-10-19 20.48.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/f2b3fd5b-8f0a-a245-21ba-f70ea5285aa3.png">
PHPが公式のリポジトリにあるにも関わらずメンテされていなかったので、何となく避けた。Laravelのライブラリは開発が盛んで盛り上がっているみたいだったので、少し興味を持ったけど今回はLaravel使うほどのものでもなかったのでGoでベタ書きすることにした。結局100行ちょいの``main.go``1ファイルで終わった。
## go-shopifyを使ってgolangでShopify Appを書く
goで**Shopify App**を書く場合、go-shopifyを使うと色々と便利。大体のShopifyのオブジェクトの構造体が準備されてるし、``.Create()``で追加したり色々できる。
とりあえず``go get``してインポートする。

```console
$ go get github.com/bold-commerce/go-shopify
```

```golang
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	goshopify "github.com/tao-s/go-shopify"//これ
)
```

### go-shoipfyの基本的な使い方
まずはAPIの認証。 **Private App**と**Public App**で少し違う

#### Public AppのShopify APIの認証
``API Key``と``API Secret``をセットしてトークンを取得する

```golang
// Create an app somewhere.
app := goshopify.App{
    ApiKey: "abcd",
    ApiSecret: "efgh",
    RedirectUrl: "https://example.com/shopify/callback",
    Scope: "read_products,read_orders",
}
```
``API Key``と``API Secret``は[Shopifyのパートナーページ](https://partners.shopify.com)のアプリ管理で取得できる。
<img width="461" alt="スクリーンショット 2019-10-21 11.51.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/c63aab86-1f17-99d3-ae02-faf78054b253.png">

次にトークン取得

```golang
// Create an oauth-authorize url for the app and redirect to it.
// In some request handler, you probably want something like this:
func MyHandler(w http.ResponseWriter, r *http.Request) {
    shopName := r.URL.Query().Get("shop")
    authUrl := app.AuthorizeURL(shopName)//shopNameはストアの管理画面のドメイン。
    http.Redirect(w, r, authUrl, http.StatusFound)//アプリ認証画面にリダイレクトさせる
}
[^3]: go-shopifyのreadmeから引用

// 認証画面からの戻りの画面
func MyCallbackHandler(w http.ResponseWriter, r *http.Request) {
    // Check that the callback signature is valid
    if ok, _ := app.VerifyAuthorizationURL(r.URL); !ok {//戻りの検証
        http.Error(w, "Invalid Signature", http.StatusUnauthorized)
        return
    }

    query := r.URL.Query()
    shopName := query.Get("shop")
    code := query.Get("code")
    token, err := app.GetAccessToken(shopName, code)//トークンを取得。ここで取得したトークンはDBとかに保存しとく

    // Do something with the token, like store it in a DB.
}
```
[^4]: go-shopifyのreadmeから引用

トークンが取得できたらAPI叩ける。

```golang
// Create an app somewhere.
app := goshopify.App{
    ApiKey: "abcd",
    ApiSecret: "efgh",
    RedirectUrl: "https://example.com/shopify/callback",
    Scope: "read_products",
}

// Create a new API client
client := goshopify.NewClient(app, "shopname", "token")//取得したshopnameとトークンを渡してクライアントを初期化

// Fetch the number of products.
numProducts, err := client.Product.Count(nil)//APIをCallする
```

#### Private AppのShopify APIの認証
Private Appの場合は、APIキー取得するとこが違う。Public Appはパートナーページだったけど、Pricvate Appはショップのアプリ管理から取得する。
<img width="245" alt="Shopify Admin Menu" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/833c5e96-8082-35c5-9cd6-53ce80489acf.png">
![グループ 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/1b89d5c8-62f3-1b73-f2ec-bc7056778da9.png)
<img width="986" alt="プライベートアプリ管理画面" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/23a9a048-188b-1d15-0d54-f0a3ba1882cd.png">
取得した** API Key**と**パスワード**で初期化。トークンは空文字ですぐクライアント作れる。
このプライベートアプリがどのAPIにアクアセスできるか？とかはストアのプライベートアプリの管理画面で設定する。

```golang
// Create an app somewhere.
app := goshopify.App{
	ApiKey: "apikey",
	Password: "apipassword",//API Secretじゃなくてpassword
}
// Create a new API client (notice the token parameter is the empty string)
client := goshopify.NewClient(app, "shopname", "")
```

### アプリの起動はWebhookで
アプリの起動方法は2種類あって、あらかじめshopifyのEventに登録しておく方法と、Webhookを使う方法。プライベートアプリならWebhookが楽。
Webhookの設定は、ストアの管理画面の**通知**メニューから設定する。
<img width="970" alt="Shopify Admin Settingメニュー" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/b459f687-6b9c-c9de-7275-81f2d119290c.png">
ここでWebhookの送信先とかリクエストの検証用キーとかが設定できる。
<img width="996" alt="Shopify Admin Setting 通知メニュー" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5743/524892f0-fd30-5aea-80b9-3ce87c151b91.png">


Private Appはトークンいらない代わりにWebhookリクエストの検証が必要。じゃないとショップ以外の悪意ある第三者からのリクエストも処理しちゃう。

```golang
func ValidateWebhook(httpRequest *http.Request) (bool) {
    shopifyApp := goshopify.App{ApiSecret: "ratz"}
    return shopifyApp.VerifyWebhookRequest(httpRequest)
}
```

### Webhookを起点にShopify APIを叩く

#### Webhookのパース
今回は受注のタイミングで支払いステータスを変更したいって要件だったので、注文作成のWebhookを設定した。その際、作成された注文の情報がWebhookのリクエストに入ってくるので、必要な情報だけを取得する。
go-shopifyでもできるんだけど、諸事情によりここだけ手作業でやった。

```golang
b, err := ioutil.ReadAll(r.Body)//リクエストのbodyをパース
defer r.Body.Close()
if err != nil {
	panic(err)
}
var jsonData Webhook//jsonの形式に合わせたstructを初期化
if err := json.Unmarshal(b, &jsonData); err != nil {//jsonをパースして構造体に値を入れる。
	panic(err)
}
//Webgook Webhookの構造体
type Webhook = struct {
	ID              json.Number `json:id`//これがID。受注番号とは違う。
	Gateway         string      `json:gateway`
	FinancialStatus string      `json:financial_status`//支払いステータス 読み取り専用
}
```
#### Shopify Admin APIを叩いてTransactionを追加する
Shopifyの受注データの支払いステータスは読み取り専用で直接変更できない。変更するためにはTransactionという支払いデータを追加して、そのステータスを``success``に設定する必要がある。具体的にはOrderのIDを使ってtransactionを追加する。

```golang
ntr := goshopify.Transaction{
	ParentID:   &tr[0].ID,//親のトランサクションID、受注時に正成される
	Status:     "success",
	Kind:       "sale",//必須項目
	Amount:     tr[0].Amount,
	Gateway:    tr[0].Gateway,
	Currency:   tr[0].Currency,
	OrderID:    orderID,
	SourceName: "external",
	Source:     "external",//ここが今回のハマりポイント
}
rtr, err := client.Transaction.Create(orderID, ntr) //API叩いて追加
```
ここで今回最大にハマったのが、この**``Source``**ってパラメータ。なんと**公式ドキュメントに書いてないw**
**なのに必須要素**で、**'external'**に指定しないとTransactionが追加できない。公式のドキュメントにも載ってないのでgo-shopifyにも定義されてないw
仕方が無いのでgo-shopifyをいじって、``Source``を設定できる様にして解決。[そのPR](https://github.com/bold-commerce/go-shopify/pull/70)

これで無事、受注ステータスを**Shopify App**から変更できる様になりました。いやー、最後の最後でハマった...

### 環境はまたGAE使いました
**Go**の簡単な環境作るならこれが**Google Cloud Platform**の**App Engine**が一番楽ですね。運用も楽だし、速い、安い。

# [PR][株式会社クロスキューブではShopifyアプリの開発](https://www.xross-cube.com/contact/)も請け負っております！
最後に、弊社株式会社クロスキューブでは、**Shopifyアプリ開発**やShopifyを使った**ECサイト構築**のお仕事も大募集中です！
何か**Shopifyアプリ**でお困りの方がいらっしゃいましたら**[ぜひお問合せ願います](https://www.xross-cube.com/contact?resource=qiita)**。