2023.02.09[Thu]
[シリーズ連載] Shopify Post-Purchase Checkout Extension

Shopify Post-Purchase Extensionでレジ前販売を実装する 機能拡張編① 〜ランダム商品〜

  • Shopify

目次

この機能は既に実用可能ではありますが、執筆時点の2023年2月現在も「ベータ版」という位置付けであり、今後仕様が変更になる可能性があります。また、公式ドキュメントも随時更新されており、一部の表現やコードの内容が一致しない可能性があります。

Webエンジニアの茄子です。
前回はレイアウト系のコンポーネントを紹介しました。( https://techlab.q-co.jp/articles/79/ )
今回からは今までの内容を活かして、チュートリアルのアプリの機能を拡張します。
チュートリアルの追加販売画面をベースにして、更にこれらの3つの機能を付けます。

1. 特定の商品をPost-Purchaseに固定表示するのではなく、条件を設定し条件に合う商品を表示する
2. 表示する商品を1つではなく複数にする
3. Post-Purchaseにおいて購入ボタンをクリックすると即時購入になってしまうので、購入前に確認メッセージを出す

本記事では 1. を、以下の順に解説していきます。

・候補を登録するためのコレクションを設定する
・APIのレスポンスを調整する
・フロント側にロジックを準備し、条件に合う商品をランダムに表示する

コレクション作成

商品を選ぶ方法は、独自のデータベース・APIを作ったり、固定のリストをJSONで持つなども可能ですが、Shopifyの備え付けで手軽にできる方法がコレクションを使う方法です。
コレクションは、主にグルーピング(フィルタリング)済みの商品リストを掲載するページのための機能ですが、ここではリンクの設置をしない単なる管理上のグループとして利用します。

(コレクション作成の前に、商品が1、2個しか登録されてない場合はいくらか増やしておいてください。)

コレクションは「商品管理」→「コレクション」から作成できます。今回は「Post-Purchase Upsell」という名前にします。カテゴリーやタグなどのフィルターを元に自動で構成することもできますが、今回は直接選んだもののみになる「手動」を選択します。ここにあるものがレジ前販売の候補となります。

APIの調整

APIのクエリを変更します。

ShopifyではREST API、GraphQL API どちらも用意されていますが、引き続きチュートリアルで使ったGraphQLを使います。
クエリの作成の際には、以下の2つのツールを使います。本来Shopify GraphiQL Appだけでいけたらいいのですが、補完としてExplorerが必要です。

Shopify GraphiQL App :
 自身のストア管理画面にインストールできるアプリで、ストア内の商品情報が返ってきます。履歴機能もあって便利ですが、なぜかスキーマのタブが無く直接入力のみ。
インストールリンク:https://shopify-graphiql-app.shopifycloud.com/login

Shopify Admin API GraphQL Explorer :
 クエリを実行すると、Shopifyが用意したダミーデータがレスポンスされます。こちらはスキーマのタブがあるので、クエリを作りやすいです。ログイン下のページではないので履歴機能はなく、単品へのクエリをするのにリストを一旦しないとIDがわからないなど面倒です。 https://shopify.dev/apps/tools/graphiql-admin-api

Admin APIのリファレンスはこちら : https://shopify.dev/api/admin-graphql

今回は先程のコレクションに登録されているProductを取ってくるクエリを作成します。取得するフィールドはチュートリアルの時と同じです。

web/post-purchase/server.ts の /offer 部分をコレクションのクエリに書き換えます。
先に変更後のコードを示します。

type CollectionResponse = {
  collection: {
    id;
    title: string;
    products: {
      nodes: Product[];
    };
  };
};

app.get("/offer", async (req: Request, res: Response) => {
  console.log(`/offer`, new Date());

  const query = gql`
    query ($collectionId: ID!) {
      collection(id: $collectionId) {
        title
        products(first: 10) {
          nodes {
            id
            title
            featuredImage {
              url
            }
            descriptionHtml
            variants(first: 3) {
              edges {
                node {
                  id
                  price
                  compareAtPrice
                }
              }
            }
          }
        }
      }
    }
  `;

  const result = await graphQLClient.request<CollectionResponse>(query, {
    collectionId: `gid://shopify/Collection/274800705621`,
  });

  const collection = result.collection;
  const products = collection.products;

  const initialData = products.nodes.map((product, i) => {
    const variant = product.variants.edges[0].node;

    return {
      variantId: variant.id.split("gid://shopify/ProductVariant/")[1],
      productTitle: product.title,
      productImageURL: product.featuredImage.url,
      productDescription: product.descriptionHtml.split(/<br.*?>/),
      originalPrice: variant.compareAtPrice,
      discountedPrice: variant.price,
    };
  });

  res.send(initialData);
});

クエリはチュートリアルの時の

    query ($productId: ID!) {
      product(id: $productId) {
        ...

が、collectionで囲われたものです。

また、initialDataとして返していた

{
  variantId: variant.id.split("gid://shopify/ProductVariant/")[1],
  productTitle: product.title,
  productImageURL: product.featuredImage.url,
  productDescription: product.descriptionHtml.split(/<br.*?>/),
  originalPrice: variant.compareAtPrice,
  discountedPrice: variant.price,
};

が、コレクションの中身の配列で返るようにしてあります。

ここで一旦APIのレスポンスを確認します

curl http://localhost:8077/offer

↓このようなレスポンスが返ってくれば問題ありません。

クライアント側の調整

クライアント側では、固定で渡していたpostPurchaseOfferを複数個の中から選ぶように処理を変更します。
今回は例として、
・先程作ったコレクションに登録されている商品の内、元の購入金額の半分以下の商品をランダムで1つ出す。
・元の購入金額が少額で、半分以下の商品が無い場合はPost-Purchase画面をスキップする。
という動作にしてみます。

Checkout::PostPurchase::ShouldRender フックを以下のように書き換えます。

extend("Checkout::PostPurchase::ShouldRender", async ({ storage, inputData }): Promise<{ render: boolean }> => {
  console.log('Shouldrender hook');
  // 購入金額
  const initialPurchasePrice = parseInt(inputData.initialPurchase.totalPriceSet.presentmentMoney.amount, 10);
  // コレクション取得
  const postPurchaseOffer: UpsellVariant[] = await fetch(
    'http://localhost:8077/offer', {}
  ).then((res) => res.json());

  // 初期購入金額の半分以下の商品を候補にする
  const candidateItems = postPurchaseOffer.filter((item) => {
    const originalPrice = parseInt(item.originalPrice, 10);
    return initialPurchasePrice / 2 > originalPrice;
  })

  //候補が無い場合はPost-Purchase画面を表示せずにThankyou画面へ
  const render = candidateItems.length > 0;

  if (render) {
    // 候補からランダムに
    const rand = Math.floor(Math.random() * 100) % candidateItems.length;
    const pickedItem = candidateItems[rand];

    // Saves initial state, provided to `Render` via `storage.initialData`
    await storage.update(pickedItem);
  }

  return {
    render,
  };
});

初期購入の情報を取得するため、フックの引数に inputData を追加しています。
そして、APIから受け取ったコレクションの中身と比較して残ったcandidateProductsによって

  const render = candidateItems.length > 0;

でPost-Purchaseページが表示されるかどうか判定されます。

終わりに

今回の拡張は以上です。何回かテスト購入でPost-Purchase画面を出し、商品が替わることを確認してみてください。
次回は、複数の商品を表示できる様にします。

Share

  1. 01 Shopify Post-Purchase Extensionでレジ前販売を実装する 概要編 >
  2. 02 Shopify Post-Purchase Extensionでレジ前販売を実装する チュートリアル編 ① >
  3. 03 Shopify Post-Purchase Extensionでレジ前販売を実装する チュートリアル編 ② >
  4. 04 Shopify Post-Purchase Extensionでレジ前販売を実装する コンポーネント紹介編 ① 〜見た目・入力〜 >
  5. 05 Shopify Post-Purchase Extensionでレジ前販売を実装する コンポーネント紹介編 ② 〜レイアウト〜 >
  6. 06 Shopify Post-Purchase Extensionでレジ前販売を実装する 機能拡張編① 〜ランダム商品〜
  7. 07 Shopify Post-Purchase Extensionでレジ前販売を実装する 機能拡張編② 〜複数商品〜 >
  8. 08 Shopify Post-Purchase Extensionでレジ前販売を実装する 機能拡張編③ 〜ディスカウント・確認〜 >
  9. 09 Shopify Post-Purchase Extensionでレジ前販売を実装する 仕上げ編 〜モバイル〜 >
Shopify Post-Purchase Extensionでレジ前販売を実装する 機能拡張編② 〜複数商品〜Webフロントエンドのパフォーマンス基礎 まとめ