CRM PLUS on LINE の Web Pixels 実装時にフロントエンドでやったこと

ソーシャルPLUS社にてアプリ開発支援をしている、endlessnです。先日、Shopifyアプリ CRM PLUS on LINE に閲覧商品のリマインド配信機能を追加するにあたって、Web Pixels を使った顧客の商品閲覧情報の保存機能を実装しました。そのときの体験を踏まえて、本記事では Web Pixels とは何かと、フロントエンドエンジニア視点で行ったことなどをまとめています

【参考】閲覧商品リマインド配信機能の詳細はこちらの記事をどうぞ


Web Pixels とは

【公式ドキュメントはこちら】

Web Pixelsとはオンラインストア上で動作する JavaScript のスニペットのことで、Customer events と呼ばれる顧客の行動情報(商品閲覧など)が取得可能です。具体的には下記のような JavaScript のコードを実装して公開することで、オンラインストアを訪問したユーザーのブラウザ上で動作します。

```
import { register } from '@shopify/web-pixels-extension';

register(async ({analytics, browser, settings}) => {
  // get/set your tracking cookies
  const uid = await browser.cookie.get('your_visitor_cookie');
  const pixelEndpoint = `https://example.com/pixel?id=${settings.accountID}&uid=${uid}`;

  // イベント名を指定して subscribe することで、商品閲覧など各イベントの情報を取得できる
  analytics.subscribe('all_events', (event) => {
    // transform the event payload to fit your schema (optional)

    // push customer event to your server for processing
    browser.sendBeacon(pixelEndpoint, event);
  });
});
```

Web Pixels にはアプリピクセルとカスタムピクセルの2種類があり、今回はアプリピクセルで実装しました。

  • アプリピクセル: アプリの一部としてインストールするもので、拡張機能として追加されるもの
  • カスタムピクセル: ストアごとに追加するもので、ストア独自の要望に合わせて使うもの
    →ストア設定の 「お客様のイベント」 から追加できます

  

フロントエンド側の作業

Web Pixels を作るための要件として「Shopify CLI の v3 を使ってアプリを作成した」あるいは「アプリが v3 と互換性がある」というものがあり、 Shopify CLI v3 からしか Web Pixels の extension を作成できませんでした。

Web Pixels の要件はこちら→https://shopify.dev/docs/apps/marketing/pixels/getting-started#requirements

弊社の場合、埋め込みアプリ (Embedded App)  はそもそも CLI で作成したものではないため v3 と互換性がなく、マイグレートするか、extension を管理するためだけの空のアプリを作成する(v3 からは extension だけデプロイすることはできず、アプリが必要になる)という2つの選択肢がありました。

マイグレートの大きな影響はディレクトリ構成の変更くらいでしたが、今後モノレポ化の予定などもあり変更点を最小限に留めておきたかったので、今回は空のアプリを作成することにしました。

以降の作業は、基本的に公式ドキュメントに沿って進めました。

【Web Pixels 公式ドキュメント】

1. Shopify CLI からアプリを作成

`$ yarn create @shopify/app` を実行し、アプリを作成します。

その際に `shopify.app.toml` というアプリ用の設定ファイルが作成され、ここに scope などを定義するようでしたが、CRM PLUS on LINE はバックエンド側で scope を管理しているため、中身は特に記述しませんでした。中身が空の場合でも、設定ファイルがないと CLI を叩くときにエラーになってしまうので、設定ファイル自体は必要です。

2. Shopify CLI から extension を作成

`$ yarn shopify app generate extension` を実行し、extension を作成します。

こちらも `shopify.ui.extension.toml` という extension 用の設定ファイルが作成されるので、この中で種類を Web Pixels に指定したりします。

```
type = "web_pixel_extension"
name = "customer-browsing-history-extension"

runtime_context = "strict"

[settings]
type = "object"

[settings.fields.enabled]
name = "Enabled"
description = "Customer browsing history extension is enabled"
type = "single_line_text_field"
validations =  [
  { name = "choices", value = "[\"true\", \"false\"]" }
]
```

`settings.fields` は必須になっていて、これを定義すると Web Pixels の API から設定したデータを使えるようになります。特に使いたいデータがない場合でも定義しておかないと CLI がエラーを吐くので、何かしら定義しておく必要があります。

CRM PLUS on LINEの特定のプランのみで閲覧履歴を保存したかったので、バックエンド側でストアの現在のプランを判定し、Web Pixels 側には単に閲覧履歴を保存するかどうかのフラグだけを渡すようにフィールドを定義しました。

3. スニペットの実装

自動生成された `src/index.ts` 内にユーザーのブラウザ上で動作するスニペットのコードを実装します。

Web Pixels の API を使うために `@shopify/web-pixels-extension` を import します。最新の Shopify CLI 3.40.1 で extension を作成したにも関わらず、なぜかパッケージは `0.1.1` という古いバージョンでインストールされてしまい、インターフェースもドキュメントと全く異なっていたので、手動で最新の `0.3.0` (2023年2月時点)をインストールしなおしました。

コードは下記の通りで、商品閲覧時に履歴保存用の API に商品情報と `clientId` を付与してリクエストする実装になっていますね。

```ts
  analytics.subscribe('product_viewed', (event) => {
    const endpoint = `${APP_PROXY_PATH}/web_pixels/product_viewed?client_id=${event.clientId}&product_id=${event.data.productVariant.product.id}&variant_id=${event.data.productVariant.id}`;
    browser.sendBeacon(endpoint);
  });
```

4. CRM PLUS on LINE にデプロイ

スニペットのコードを実装したら CLI からビルドし、デプロイします。

今回は空のアプリを作成したので、CLI から`shopify app env pull`を実行し、CRM PLUS on LINE の API キーなどを取得しました。

上記コマンド実行で `.env` が作成されるので、その状態で `shopify app deploy` を実行すれば、アプリに extension をデプロイできます。

App Proxy やデプロイ先の CRM PLUS on LINE のステージング・本番環境を切り替える都合で、この辺りはシェルスクリプト内で行っています。

5. extension を公開

デプロイ後、Shopify のパートナーダッシュボードからバージョンを作成し、公開します。

フロントエンド側で対応したのはここまでですが、これだけだとまだオンラインストア上では使えません。Web Pixels を使うにあたってバックエンド側で追加したスコープを許可する認可フローを各ストアで行う必要があるのと、Web Pixels をアクティベートする Admin API の Mutation の実行も必要です。Mutation の実行はバックエンド側で認可フロー後にやってもらっています。

ちなみに、Mutation(`webPixelCreate`) 実行時に返されるWeb Pixels の ID は、Web Pixels の `settings.fields` の値を更新したり、削除したりするときに使います。Web Pixels や extension の一覧を返す API などは見当たらず、バックエンドなどから ID を知る術がなさそうでした。一応、フロントエンドから Web Pixels の情報を取得できるので、そこから知ることはできます。

Admin API の Mutation→https://shopify.dev/docs/apps/marketing/pixels/getting-started#step-6-create-a-web-pixel-merchant-setting-for-a-shop

ここまでやると、オンラインストア上で Web Pixels が動作するようになります。

 

Shopify Web Pixels で取得できるイベントデータ

Shopify Web Pixels で取得できるイベント一覧は、下記のリファレンスを参照してください。

実際に取得できたイベントデータの例

実際に今回CRM PLUS on LINEのステージング環境で取得したイベントデータを例として掲載します。商品や個人情報はテストデータですが、JSON の形式などは実際に取得しているものと同じ形です。

## イベントデータ

### [共通部分](https://shopify.dev/api/pixels/customer-events#event-structure)

```json
{
  "id": "485b1a7a-e9bd-4f7e-b815-2eadc23f904b",
  "clientId": "c990dae1-cbed-4de4-9034-5189af20109d",
  "timestamp": "2023-02-07T07:55:48.659Z",
  "name": "product_viewed",
  "context": {
    "document": {
      "location": {
        "href": "https://********.myshopify.com/products/%E7%84%A1%E6%96%99%E3%81%AE%E3%82%A2%E3%83%AD%E3%83%8F-t-%E3%82%B7%E3%83%A3%E3%83%84",
        "hash": "",
        "host": "********.myshopify.com",
        "hostname": "********.myshopify.com",
        "origin": "https://********.myshopify.com",
        "pathname": "/products/%E7%84%A1%E6%96%99%E3%81%AE%E3%82%A2%E3%83%AD%E3%83%8F-t-%E3%82%B7%E3%83%A3%E3%83%84",
        "port": "",
        "protocol": "https:",
        "search": ""
      },
      "referrer": "https://********.myshopify.com/",
      "characterSet": "UTF-8",
      "title": "お得なアロハ T シャツ – CRM PLUS on LINE Staging 動作確認用ストア"
    },
    "navigator": {
      "language": "en",
      "cookieEnabled": true,
      "languages": [
        "en"
      ],
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/110.0"
    },
    "window": {
      "innerHeight": 803,
      "innerWidth": 489,
      "outerHeight": 900,
      "outerWidth": 1440,
      "pageXOffset": 0,
      "pageYOffset": 0,
      "location": {
        "href": "https://********.myshopify.com/products/%E7%84%A1%E6%96%99%E3%81%AE%E3%82%A2%E3%83%AD%E3%83%8F-t-%E3%82%B7%E3%83%A3%E3%83%84",
        "hash": "",
        "host": "********.myshopify.com",
        "hostname": "********.myshopify.com",
        "origin": "https://********.myshopify.com",
        "pathname": "/products/%E7%84%A1%E6%96%99%E3%81%AE%E3%82%A2%E3%83%AD%E3%83%8F-t-%E3%82%B7%E3%83%A3%E3%83%84",
        "port": "",
        "protocol": "https:",
        "search": ""
      },
      "origin": "https://********.myshopify.com",
      "screenX": 0,
      "screenY": 0,
      "scrollX": 0,
      "scrollY": 0
    }
  },
  "data": {
    ...
  }
}
```


### [checkout_completed](https://shopify.dev/api/pixels/customer-events#checkout_completed)

```json
{
  "data": {
    "checkout": {
      "currencyCode": "JPY",
      "email": "test@socialplus.jp",
      "lineItems": [
        {
          "id": "1284036ce82d5ebab0a44838fb89e387",
          "quantity": 1,
          "title": "カゴ落ちのアロハ T シャツ",
          "variant": {
            "id": "39464400683251",
            "image": {
              "src": "https://cdn.shopify.com/s/files/1/0550/4145/8419/products/alohashirt_red_3173a143-8b55-4ea9-a707-d3a03bad4c8d.png?v=1632987516"
            },
            "price": {
              "amount": 100,
              "currencyCode": "JPY"
            },
            "product": {
              "id": "6636596101363",
              "title": "カゴ落ちのアロハ T シャツ",
              "vendor": "いらすとや"
            },
            "sku": null,
            "title": "Red"
          }
        }
      ],
      "order": {
        "id": "5061017764083"
      },
      "phone": null,
      "shippingAddress": {
        "city": "文京区",
        "country": "Japan",
        "countryCode": "JP",
        "phone": null,
        "province": "Tōkyō",
        "provinceCode": "JP-13"
      },
      "shippingLine": {
        "price": {
          "amount": 1000,
          "currencyCode": "JPY"
        }
      },
      "subtotalPrice": {
        "amount": 100,
        "currencyCode": "JPY"
      },
      “token”: “99e166c2831*************”,
      "totalPrice": {
        "amount": 1110,
        "currencyCode": "JPY"
      },
      "totalTax": {
        "amount": 10,
        "currencyCode": "JPY"
      }
    }
  }
}
```

### [checkout_started](https://shopify.dev/api/pixels/customer-events#checkout_started)
ログイン状態のデータ。
未ログインの場合、`shippingAddress` が空オブジェクトだったり、`email` がキーごと無かったりする。
```json
{  
  "data": {
    "checkout": {
      “token”: “99e166c2831*************”,
      "currencyCode": "JPY",
      "email": "test@socialplus.jp",
      "lineItems": [
        {
          "id": "39464400683251",
          "quantity": 1,
          "title": "カゴ落ちのアロハ T シャツ",
          "variant": {
            "id": "39464400683251",
            "image": {
              "src": "https://cdn.shopify.com/s/files/1/0550/4145/8419/products/alohashirt_red_3173a143-8b55-4ea9-a707-d3a03bad4c8d_64x64.png?v=1632987516"
            },
            "price": {
              "amount": 100,
              "currencyCode": "JPY"
            },
            "product": {
              "id": "6636596101363",
              "title": "カゴ落ちのアロハ T シャツ",
              "vendor": "いらすとや"
            },
            "title": "Red"
          }
        }
      ],
      "order": {},
      "shippingAddress": {
        "city": "文京区",
        "country": "JP",
        "countryCode": "JP",
        "province": "JP-13",
        "provinceCode": "JP-13"
      },
      "subtotalPrice": {
        "amount": 100,
        "currencyCode": "JPY"
      },
      "shippingLine": {
        "price": {}
      },
      "totalTax": {
        "amount": 10,
        "currencyCode": "JPY"
      },
      "totalPrice": {
        "amount": 110,
        "currencyCode": "JPY"
      }
    }
  }
}
```

### [page_viewed](https://shopify.dev/api/pixels/customer-events#page_viewed)

```json
{
  "data": {}
}
```

### [product_added_to_cart](https://shopify.dev/api/pixels/customer-events#product_added_to_cart)

```json
{
  "data": {
    "cartLine": {
      "cost": {
        "totalAmount": {
          "amount": 500,
          "currencyCode": "JPY"
        }
      },
      "merchandise": {
        "id": "39464400584947",
        "image": {
          "src": "https://cdn.shopify.com/s/files/1/0550/4145/8419/products/alohashirt_red.png?v=1632987511"
        },
        "price": {
          "amount": 500,
          "currencyCode": "JPY"
        },
        "product": {
          "id": "6636596068595",
          "title": "お得なアロハ T シャツ - Red",
          "vendor": "いらすとや"
        },
        "sku": "",
        "title": "Red"
      },
      "quantity": "1"
    }
  }
}
```

### [product_viewed](https://shopify.dev/api/pixels/customer-events#product_viewed)

```json
{
  "data": {
    "productVariant": {
      "id": "39464400584947",
      "image": {
        "src": "https://cdn.shopify.com/s/files/1/0550/4145/8419/products/alohashirt_red.png?v=1632987511"
      },
      "price": {
        "amount": 500,
        "currencyCode": "JPY"
      },
      "product": {
        "id": "6636596068595",
        "title": "お得なアロハ T シャツ",
        "vendor": "いらすとや"
      },
      "sku": "",
      "title": "Red"
    }
  }
}
```

### [search_submitted](https://shopify.dev/api/pixels/customer-events#search_submitted)
`query` に入力した検索ワードが入っている
```json
{
  "data": {
    "searchResult": {
      "query": "Tシャツ"
    }
  }
}
```

 

まとめ

Shopifyアプリ CRM PLUS on LINE に Web Pixels を使った顧客の商品閲覧情報の保存機能を実装するにあたって、フロントエンド視点でやったことなどをまとめました。

Shopify CLI についての知見などは必要でしたが、Web Pixels の実装自体はとても簡単で、Liquid 上で DOM や URL から閲覧情報を取得する JavaScript を動かすなど手間のかかる方法も必要ないので、開発者目線でとても役立つ機能でした。Web Pixels を使うことで、閲覧情報などを基にマーチャント側としても嬉しい機能を今後提供できるのが楽しみです。

Web Pixelsを使った「閲覧商品のリマインドLINE配信」機能については、下記ページで詳しく紹介していますので、ぜひ併せてご覧ください。