Help us understand the problem. What is going on with this article?

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

今回はShopifyアプリをGoogle Cloud PlatformのGAE(Google App Engine)を使ってGoで書いてみました。

Shopifyとは?

Shopify 日本語サイト
Shopifyとは、カナダ産のEC(e-Commerce)のSaaSです。ひと昔前風に言うと、GMOメイクショップや、フューチャーショップ、Baseの様なEC向けのASPです。
現在、カナダだけではなく、北米はもとより世界中で利用されており、昨年くらいからアジアオセアニア地域でのマーケティングにお力を入れ始めた、ECプラットフォーム界の巨人です。
グローバルに展開しているサービスなので、世界中のローカルの決済等が利用でき、越境EC向けのECプラットフォームです。

標準機能も素晴らしいのですが、Shopifyアプリというサードパーティーのアプリでその機能を拡張できる点も魅力的な点です。
すでに世界中で様々なShopifyアプリが開発されており、日本国内でも増えてきています。
スクリーンショット 2019-10-19 20.48.24.png

Shopifyアプリとは?

じゃあ、具体的にShopifyアプリってどうなってんの?というと、基本的にShopifyのWebAPIを叩くsomethingです。
APIには伝統的なRESTのAPIと最近流行りのGraphQLがあります。
shopify_app_arc@2x.png

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

Shopifyアプリには、大きく分けて公開されてて誰でも利用することができるPublic Appsと、その店舗専用みたいなPrivate Appsがあります。

Private Apps

ShopifyのAppストアに出さないそのショップ専用のアプリ。Public Appと違って、認証周りが楽。トークンとか取得しなくて良い。https://{API_key}:{password}@{shop}.myshopify.com/admin/api/2019-10/shop.jsonみたいにBasic認証でリクエストして処理できる。今回使ったのはこれ

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
    • 管理画面に埋め込む系の何かを作る時用のSDK
  • POS App SDK
    • iPadとかのタブレットなどで使えるShopify POSアプリがあり、それ用のSDK。今は非推奨。iOS9以降は対応してないっぽい。
  • Shopify 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で書くのを止めた段階で候補はGoPHPしかなかったんだけど、ざっくり調べて見た。

世界中で使われてるSaaSなだけあって、とにかくいっぱいある。ただ、日本語対応とか日本向けはまだまだ少ないので、日本人的には辛い。日本人頑張らねば
スクリーンショット 2019-10-19 18.36.26.pngスクリーンショット 2019-10-19 20.48.05.png
PHPが公式のリポジトリにあるにも関わらずメンテされていなかったので、何となく避けた。Laravelのライブラリは開発が盛んで盛り上がっているみたいだったので、少し興味を持ったけど今回はLaravel使うほどのものでもなかったのでGoでベタ書きすることにした。結局100行ちょいのmain.go1ファイルで終わった。

go-shopifyを使ってgolangでShopify Appを書く

goでShopify Appを書く場合、go-shopifyを使うと色々と便利。大体のShopifyのオブジェクトの構造体が準備されてるし、.Create()で追加したり色々できる。
とりあえずgo getしてインポートする。

$ go get github.com/bold-commerce/go-shopify
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 AppPublic Appで少し違う

Public AppのShopify APIの認証

API KeyAPI Secretをセットしてトークンを取得する

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

API KeyAPI SecretShopifyのパートナーページのアプリ管理で取得できる。
スクリーンショット 2019-10-21 11.51.47.png

次にトークン取得

// 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)//アプリ認証画面にリダイレクトさせる
}
// 認証画面からの戻りの画面
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.
}

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

// 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はショップのアプリ管理から取得する。
Shopify Admin Menu
グループ 1.png
プライベートアプリ管理画面
取得した** API Key*パスワード*で初期化。トークンは空文字ですぐクライアント作れる。
このプライベートアプリがどのAPIにアクアセスできるか?とかはストアのプライベートアプリの管理画面で設定する。

// 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の設定は、ストアの管理画面の通知メニューから設定する。
Shopify Admin Settingメニュー
ここでWebhookの送信先とかリクエストの検証用キーとかが設定できる。
Shopify Admin Setting 通知メニュー

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

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

Webhookを起点にShopify APIを叩く

Webhookのパース

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

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を追加する。

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

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

環境はまたGAE使いました

Goの簡単な環境作るならこれがGoogle Cloud PlatformApp Engineが一番楽ですね。運用も楽だし、速い、安い。

[PR]株式会社クロスキューブではShopifyアプリの開発も請け負っております!

最後に、弊社株式会社クロスキューブでは、Shopifyアプリ開発やShopifyを使ったECサイト構築のお仕事も大募集中です!
何かShopifyアプリでお困りの方がいらっしゃいましたらぜひお問合せ願います

tao_s
オープンソースなweb屋です。 Webベースの業務システムからECサイト、ブログまで何でもござれ EC-CUBE、concrete5のコミッターもやってます コンクリートファイブ ジャパン株式会社 ファウンダー
http://www.xross-cube.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした