8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Shopify開発を盛り上げる(Liquid, React, Node.js, Graph QL)Advent Calendar 2020

Day 21

Shopifyアプリ開発 ~ スクリプトAPIでストアフロントに会員情報と連携するプラグインを設置する方法 ~

Last updated at Posted at 2020-12-20

#簡単自己紹介
はじめまして、熱狂的ファンを作る接客チャットチャネルトークShopifyアプリをリリース中のチャネルトークCCOあやーそんと申します。

かなり特殊な経歴ですが、アクセンチュアでデジタル技術を使ったPoC専門チームに新卒で入り、エンジニア(ソフトウェア・ハードウェア)とUXデザインなど開発とビズを兼業していました。

また、ソフトウェアエンジニアとして再ジョインしたチャネルトークでもCRE(Customer Reliablity Engineer)とCSやPMMを兼務するなど常に開発とビズを両方やるおかしなキャリアを歩み、極めていきたいと思っています。

#チャネルトークとは?
熱狂的ファンを作るCSチャットのチャネルトークは、「答えは顧客にある」をモットーに、気軽に声かけができるチャット窓口から顧客の声をきける機能と顧客中心文化を提供しています。

顧客の声を社内に共有してサービスや商品に反映させたり、素晴らしいサポートや接客を実施することでファンを作りや売上やLTVの向上など、Shopifyマーチャントをはじめ、SMBの持続的な成長を支援するのが私たちのミッションです。

##Shopifyの導入事例が急増中
コロナの影響もあり、チャット接客をはじめたり、利便性向上のためにサポート窓口をメール・電話からチャネルトークに移行するマーチャントさんが増えています。
▶︎ Shopify導入事例まとめ

▶︎KURAND「チャット導入で購入前の相談件数、約600%UPを実現!コロナ禍でオンライン接客に注力!」
▶︎ジョンマスターオーガニック「ジョンマスターオーガニックがチャット接客で、オンラインストアの購入に繋げる」

#チャネルトークのShopifyアプリのリリース
顧客に商品を直接販売できる自社ECを提供するShopifyに共感をするとともに、EC・D2Cに顧客中心文化に提供することで持続的な成長を支援したく、Shopifyアプリの開発に臨みました。すでに提供しているスクリプトの手動追加によるShopifyへの導入が多かったことや、マーチャントさんや支援事業社さんからの期待の声もあり優先度をあげて開発をしてきました!

##Shopify設置後のイメージと機能
▶︎ Shopifyのデモサイトで動作確認も可能です
▶︎ Shopifyへの設置方法(theme.liquidのbody一番下に手動でコード設置)
※Shopifyアプリストアへの掲載が決まったら更新します!

###5分でサイト設置して顧客と会話が始められる「接客チャット」
接客チャット
###FAQチャットボットや問い合わせ統計で効率化や改善ができる「サポート強化機能」
サポート強化機能
###サイト内ポップアップやオフラインの顧客にもSMSやメールで一斉・自動で話しかけてスケールさせられる「マーケティング機能」
マーケティング機能

##Shopifyの会員情報やカート内情報との連携
Shopify会員情報連携

  • Shopifyの会員情報やカート内情報との連携
  • クリックひとつでShopifyの顧客ページに飛んで注文履歴を確認できるCRMリンク
  • イベント欄にて閲覧ページなどからお客様の興味を把握した上での接客・サポートが可能

#チャネルトーク アプリの開発方法
さて、長くなりましたが開発方法はざっくりと以下の通りです。

  1. OAuth2.0による認証処理の実装(https://shopify.dev/tutorials/authenticate-with-oauth#step-3-confirm-installation)
  2. ScriptTag APIを使ってアプリのインストール時にチャネルトークの連携スクリプト挿入
  3. Liquid objectsにて取得できる会員情報(customerオブジェクト)やカート情報(cartオブジェクト)を取得するためにApp proxyを実装
  4. Shopのlocaleに合わせたアプリのローカライズ
  5. billing APIによる有料アプリの課金連携
  6. GDPR準拠のための必須Webhook

引っかかった点

###1. App Proxyの実装!?
③の手順にあるApp Proxyの実装しなくてはならない点で引っかかりました。ScriptTag APIが挿入される時点ではすでにjsコードになっており、liquid objectを直接呼び出すことができません。

App Proxyでliquid objectを取得しておいて、jsの変数に保存、Script APIで挿入されるコードに受け渡す必要があります!

###2. Billing API
チャネルトークの課金システムが前払いかつ従量課金制であるため、billing APIの実装にもっとも時間がかかりました。他社アプリなどの事例なども参考にして、どうにかbilling APIなしでもまずはリリースできないか考えました。。。が、billing APIを連携しない有料アプリは過去は審査通過が可能だったけど今は無理で、今後停止になる可能性もあるとのこと。アプリストアの掲載には必須だという結論になりました。有料アプリは素直に実装するのがオススメです!

###3. OAuth時のローカライズ実装
チャネルトークは完全日本語対応のアプリでありながら、グローバルに展開するため(自動翻訳機能や韓国語・英語にも対応しているため越境ECにもオススメです^^)、OAuth処理のレスポンスで送られてくるShopのlocale情報に従ってアプリのUIや設定をローカライズしました。

ただし、OAuthのレスポンスでlocale情報をくれるのは、"online access mode"でOAuthを実装したときのみ。チャネルトークでは"online access mode"のように、Webセッションに紐付く処理は不必要かつ開発コストが高いため、"Offline access"を採用していました。

代わりに、OAuth直後の処理でShop情報をAPIで取得してローカライズしました。

App ProxyでScript Tagsに顧客情報を受け渡す方法

OAuth2.0やScriptTag APIなどは基本的な使い方だったのでサンプルコードとして、App ProxyからScript TagsにLiquid Objectで提供する顧客情報を受け渡す部分を共有します。
※あくまでもサンプルコードで実際のアプリ開発したコードはより詳細な処理があるため異なります!

  • App Proxyの設定
    App Proxyの設定
    App Proxyの設定

  • Script Tagsで登録されたurlによりアクセスされるスクリプト

app.js
/* plugin */
router.get('/plugin/:pluginKey',  async (ctx, next) => {
  let pluginKey = ctx.params.pluginKey;
  ctx.body = `var myAppJavaScript = function(a) {
    a.get("/apps/integrate-liquid", function(e) {
      var t = function(e) {
        var t = document.createElement("div");
        t.innerHTML = e;
        var a = document.createDocumentFragment();
        return a.appendChild(t), a.querySelector("#channel-widget")
      }(e);
          a("body").append(t);
          ChannelIO('boot', {
              "pluginKey": "${pluginKey}",
              "memberId": window.channelMemberId,
              "profile": window.channelParameters
          });
    })
  };
  var loadScript = function(e, t) {
    var a = document.createElement("script");
    a.type = "text/javascript", a.readyState ? a.onreadystatechange = function() {
      "loaded" != a.readyState && "complete" != a.readyState || (a.onreadystatechange =
        null, t())
    } : a.onload = function() {
      t()
    }, a.src = e, document.getElementsByTagName("head")[0].appendChild(a)
  };
  "undefined" == typeof jQuery || parseFloat(jQuery.fn.jquery) < 1.7 ? loadScript(
    "//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js",
    function() {
      jQuery191 = jQuery.noConflict(!0), myAppJavaScript(jQuery191)
    }) : myAppJavaScript(jQuery);
  !function() {
    var t = window;
    if (t.ChannelIO) return (window.console.error || window.console.log ||
      function() {})("ChannelIO script included twice.");
    window.document;
    var e = function() {
      e.c(arguments)
    };
  
    function n() {
      if (!t.ChannelIOInitialized) {
        t.ChannelIOInitialized = !0;
        var n = document.createElement("script");
        n.type = "text/javascript", n.async = !0, n.src =
          "https://cdn.channel.io/plugin/ch-plugin-web.js", n.charset = "UTF-8";
        var e = document.getElementsByTagName("script")[0];
        e.parentNode.insertBefore(n, e)
      }
    }
    e.q = [], e.c = function(n) {
        e.q.push(n)
      }, t.ChannelIO = e, "complete" === document.readyState ? n() : window.attachEvent ?
      window.attachEvent("onload", n) : (window.addEventListener("DOMContentLoaded",
        n, !1), window.addEventListener("load", n, !1))
  }()`
});
  • 上記のScript Tagsに会員情報を受け渡せるように、
    App Proxyにアクセスされた際に、Liquid Objectを取得してjs変数に入れる処理
app.js
/* integrate-liquid */
router.get('/integrate-liquid',  async (ctx, next) => {
  ctx.set('Content-Type', 'application/liquid');
  ctx.body = `<script id="channel-widget">
      window.channelMemberId = "{{customer.id}}"
      window.channelParameters = {
        "name": "{{ customer.name }}",
        "email": "{{ customer.email }}",
        "defaultAddressCity": "{{ customer.default_address.city}}",
        "cartTotalPrice": "{{ cart.total_price | money }}",
        "totalSpent": "{{ customer.total_spent | money }}",
        "ordersCount": "{{customer.orders_count}}",
        "lastOrderCreatedAt": "{{ customer.last_order.created_at | date: %s*1000}}",
        "lastOrderNumber": "{{ customer.last_order.order_number }}",
        "fulfillmentUrl": "{{ fulfillment.tracking_url }}"
      }
    </script>`
});

最後まで読んでいただきありがとうございました!

チャネルトークのShopifyアプリは、リリース後もさらにアップデートをして強化していく予定です!まずはリリースを楽しみにしていてください!

▶︎ 疑問点はチャネルトークホームページからお問い合わせください!
▶︎ アプリのリリース前にすぐに導入したい場合は、「Shopifyにチャネルトークを10分で設置する方法」から可能です!😁

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?