Shopify POS でクーポンを付与する機能を実装してみた

「オンラインとオフラインの両方を Shopify で完結させたい」というニーズがあったときに、最初に連想する機能は Shopify POS です。

参考リンク

Shopify POS そのものの情報は少しづつ出てきていますが、POS を活用した事例や、POS で使えるアプリにかんする記事は少なく、特に開発にいたっては日本語での情報がほとんど見当たりません。(僕が知らないだけでここにあるよ〜という情報をお持ちの方はぜひ教えてください!)

見当たらないのであればやってみるしかない、ということで、POS を使ったアプリ開発を試してみました。この記事では Shopify POS を活用した開発のトライアルとして、POS上でクーポンを付与する機能を実装してみた際のメモをまとめています。


Cart app extension を利用する

オンラインでは実装している「クーポンを通じたディスカウント」を、オフラインでも同じように実現するために、Cart app extension を使用して、ユーザーに対してクーポンを付与していきます。

Cart app extension のエンドポイントを設定

まず、パートナーダッシュボードから拡張機能として追加したいアプリを選択し、エンドポイントを設定します。ここではPOSカートを選択します。

赤枠のところです
変更ごとにペイロードを受信する設定

ここでエンドポイントを設定します。上記スクリーンショットの右側「ペイロードの例」と記載があるように、カート内で変更があるたびにペイロードを受け取れるようになります。

Shopify POS アプリを埋め込む

上記で作成した拡張機能を使うには Shopify POS アプリの埋め込み設定が必要なので、さっそく設定していきます。

まず、パートナーダッシュボードのアプリ管理からアプリを選択して、アプリ設定の中の埋め込み式アプリを選択します。

こんな感じの表示です

ここで、 Shopify POS のアプリに埋め込む を有効にすると、拡張機能が Shopify POS で利用できるようになります。

Shopify POS に拡張機能を設定する

上記で設定した拡張機能を使用するには、Shopify POS に「タイル」を追加します。

ポイント

タイルを追加するには、Point of Sale のスタッフ権限が設定されている必要がありますので注意です!

タイルを追加する

赤枠の部分をタップします

アプリ を選択

「アプリ」を選択してください

タイルにアプリを設定

設定したいアプリを選択します

拡張機能を選択

スマートグリッドに追加するをタップします

タイルの追加が完了

追加されました

これでタイルが追加されましたが、この段階ではまだプロモーションは使えませんので、次のステップで有効化します。

エンドポイントを使って、タイルを利用可能にする

ポイント

ここでは、顧客に対して「500円割引のサマークーポン」を Shopify POS で付与できる設定をします。ちなみに Shopify POS の公式ドキュメントでもポイント管理の例があるので、そちらも併せてご覧ください!

設定したエンドポイント/promotionsからユーザー情報を受けとり、指定の値を返す

/promotionsで受け取れるペイロード

{
		shop_id: ********,
		shopify_domain: 'rewiredteststore.myshopify.com',
		supported_templates: [ 'simple_action_list' ],
		supported_actions: [ 'flat_discount', 'percent_discount', 'add_variant' ],
		locale: 'ja',
		currency_code: 'JPY',
		customer_id: ********,
		customer_email: 'rewiredteststore@rewired.jp'
	}

/server/index.js

app.post("/pos-discount/promotions", async (req, res) => {
    const body = {
      type: 'simple_action_list',
      points_label: '500円割引',
      points_balance: 'サマークーポン',
      actions: [
        {
          type: 'flat_discount',
          title: 'クーポンを利用する',
          description: '期間限定クーポン',
          action_id: '123ABC',
          value: '500',
        },
      ],
    };
    res.status(200).send(body)
  });

上記は、顧客情報を Shopiy POS で選択した時に、/promotions のエンドポイントに設定値を返すための記述です。このコードの設定値だと Shopify POS では以下のようにレンダリングされます。

ポイント

今回のテストアプリではおこないませんが、ここで顧客IDが初めて取得できるので、使えるクーポンの出し分けなども実装できそうです!

実際にこのクーポンを利用するときのエンドポイントが/perform_actionです。

/perform_action で受け取れるペイロード

{
		shop_id: ********,
		shopify_domain: 'rewiredteststore.myshopify.com',
		supported_templates: [ 'simple_action_list' ],
		supported_actions: [ 'flat_discount', 'percent_discount', 'add_variant' ],
		locale: 'ja',
		currency_code: 'JPY',
		customer_id: ********,
		customer_email: 'rewiredteststore@rewired.jp',
		action_id: '123ABC'
	}

顧客情報を Shopiy POS で選択した時に、/perform_actionのエンドポイントに設定値を返すための記述が以下です。

/server/index.js

app.post("/pos-discount/perform_action", async (req, res) => {
    console.log(req.body)
    const body = {
      type: 'simple_action_list',
      points_label: '500円割引',
      points_balance: 'サマークーポン',
      actions: [
        {
          type: 'flat_discount',
          title: 'サマークーポン',
          description: '期間限定クーポン',
          action_id: '123ABC',
          value: '500',
        },
      ],
    };
    res.status(200).send(body)
  });

こちらの設定値ですと Shopify POS ではこのようにレンダリングされます。

クーポンが適用されています

続いて、割引情報を削除するときの処理です。エンドポイントは /revert_action になります。

/revert_action で受け取れるペイロード

{
		shop_id: ********,
		shopify_domain: 'rewiredteststore.myshopify.com',
		supported_templates: [ 'simple_action_list' ],
		supported_actions: [ 'flat_discount', 'percent_discount', 'add_variant' ],
		locale: 'ja',
		currency_code: 'JPY',
		customer_id: null,
		customer_email: null
	}

顧客情報を Shopiy POS で選択した時に、/revert_actionのエンドポイントに設定値を返すための記述が以下です。

/server/index.js

app.post("/pos-discount/revert_action", async (req, res) => {
    console.log(req.body)
    const body = {
      type: 'simple_action_list',
      points_label: '500円割引',
      points_balance: 'サマークーポン',
      actions: [
        {
          type: 'flat_discount',
          title: 'サマークーポン',
          description: '期間限定クーポン',
          action_id: '123ABC',
          value: '500',
        },
      ],
    };
    res.status(200).send(body)
  });

削除すると、元の設定値どおりの表示になります。

戻ります

カート内でディスカウントを適用させる

続いて、POS links を使い、カート内に指定のディスカウントを適用させていきます。

まず、パートナーダッシュボードのアプリ管理からアプリを選択して、アプリ設定の中の「埋め込み式アプリ」を選択します。

作成するボタンをクリックすると、POS links の作成画面が出るので、入力していきます。

今回のリンクは /POS 内に設置しようと思います

あとは作成ボタンを押せば、保存されて POS links が設定されます。

Shopify POS アプリにタイルを追加する

タイルの追加方法は Cart app extension のときと同じです。 POS links はアクション内に追加されます。

追加すると、このように設定通りにタイルが表示されます。

この状態ではまだアプリ内に /POS の機能が実装されていないので、以下のステップで作成していきます。

POS links用画面の作成

管理画面の完成図は以下のように、クーポン使用すると Shopify POS のカートにクーポンを適用できる機能が実装されている状態です。

ポイント

POS links はアプリ内に機能を設置していきます。ちなみに今回の例はすべて Polaris を使用しています。

画面用のコード

import {
  Card,
  Page,
  Layout,
  Toast,
  Frame,
} from "@shopify/polaris";
import { useAppBridge } from "@shopify/app-bridge-react";
import { Cart } from '@shopify/app-bridge/actions';
import React, { useState, useCallback } from 'react';

export function POSLink () {
  
  const [active, setActive] = useState(false);

  const toggleActive = useCallback(() => setActive((active) => !active), []);

  const toastMarkup = active ? (
    <Toast content="クーポンを適応しました" onDismiss={toggleActive} />
  ) : null;

  const app = useAppBridge();
  const Discount = (value) => {
    // setActive(true)
    const discountPayload = {
      discountDescription: value,
      discountCode: value,
    }
    const cart = Cart.create(app);
    const unsubscriber = cart.subscribe(
      Cart.Action.UPDATE,
      function (payload) {
        console.log('[Client] setDiscount', payload);
        unsubscriber();
      },
    );
    cart.dispatch(Cart.Action.SET_DISCOUNT, {
      data: discountPayload
    });
    
  }
  
  return (
    <Frame>
    <Page fullWidth>
      <Layout>
        <Layout.Section oneThird>
      <Card
        title="サマークーポン"
        footerActionAlignment='left'
        primaryFooterAction={{content: 'クーポンを使用する',onAction:()=>{
          Discount('SUMMERSALE');
          toggleActive();
        }}}
      >
         <Card.Section title="クーポン詳細">
        <p>500円引き サマークーポン</p>
        </Card.Section>
      </Card>
      </Layout.Section>
      <Layout.Section oneThird>
      <Card
        title="お誕生日クーポン"
        footerActionAlignment='left'
        primaryFooterAction={{content: 'クーポンを使用する',onAction:()=>{
          Discount('BIRTHDAY');
          toggleActive();
        }}}
      >
      <Card.Section title="クーポン詳細">
        <p>1,000円引き お誕生日クーポン</p>
        </Card.Section>
      </Card>
      </Layout.Section>
      <Layout.Section oneThird>
      <Card
        title="サマークーポン"
        footerActionAlignment='left'
        primaryFooterAction={{content: 'クーポンを使用する',onAction:()=>{
          Discount('SUMMERSALE2');
          toggleActive();
        }}}
      >
      <Card.Section title="クーポン詳細">
        <p>1,500円引き サマークーポン</p>
        </Card.Section>
      </Card>
      </Layout.Section>
      </Layout>
      {toastMarkup}
    </Page>
    </Frame>
  );
}

カートページのデータの取得や商品、顧客、ディスカウントの追加は AppBridge の Cart を使用することで実現しています。

参考リンク

上記のディスカウントコードをカートにセットするコードは以下になります。

カートにディスカウントを設定

  const app = useAppBridge();
  const Discount = (value) => {
    // setActive(true)
    const discountPayload = {
      discountDescription: value,
      discountCode: value,
    }
    const cart = Cart.create(app);
    const unsubscriber = cart.subscribe(
      Cart.Action.UPDATE,
      function (payload) {
        console.log('[Client] setDiscount', payload);
        unsubscriber();
      },
    );
    cart.dispatch(Cart.Action.SET_DISCOUNT, {
      data: discountPayload
    });

こんな感じになりました!

まとめ

今回は Shopify POS を使用することで、オンラインでの体験をかんたんにオフラインでも展開ができるアプリを作ってみました。

POS が使えると、たとえばポップアップストアといったテンポラリな場面でもオンラインの顧客へのオフライン誘導がスムーズに行えますし、管理も Shopify で一元化できるのでオペレーション的にもシンプルです。今回の記事で作成した簡易アプリのように、拡張機能を使うことでオフライン独自の特典を作ったりすることもできるでしょう。

App Store には Shopify POS 対応のアプリもたくさんでていますし、Omni Hub のような Shopify と他社POS との連携のアプリもあります。ご自身のストアの状況に合わせてどんな活用が可能なのか、試してみるのもいいかもしれません!