2025.02.27[Thu]

Shopify FlowのRun codeを使った「購入制限」の実装

  • Shopify

目次

  • 準備
  • 実装
  • Run codeの内容
  • - 入力
  • - コード
  • - 出力
  • まとめ

こんにちは。
ディレクターの田辺です。

今回は、こちらの記事で予告した、

Shopify FlowのRun codeを利用して、1人の顧客が、特定の期間内に、特定の商品を、何個購入できるかを規制する

を実装してみたので、そのShopify Flowをご紹介します。

なお、本記事で紹介するShopify Flowは十分に検証しておりません。
実際にご利用になったり、参考にされたりする場合は、十分に動作検証することをおすすめします。

準備

1人の顧客が、特定の期間内に、特定の商品を、何個購入できるか、を指定するため、商品にメタフィールドを設定します。今回は下記のように設定しました。

メタフィールド
商品
ネームスペース
custom
キー
purchase_condition
meta object※詳細は後述

meta objectの利用

Run codeにおけるmeta object(メタオブジェクト)の利用の仕方について、あまりドキュメントがないようだったのでトライアルも兼ねて、今回はmeta objectを利用します。

meta objectの内容

タイプ
purchase_condition
field1 ネーム
期間
field1 キー
period
field1 型
整数
field2 ネーム
個数
field2 キー
quantity
field2 型
整数

期間 (period) に、過去何日間分のデータを集計するかの情報を、
個数 (quantity) に、「期間」中に何個購入できるかの情報を、
登録します。

例えば、過去30日間に3個まで購入可能な商品であれば、
期間 (period) が、30
個数 (quantity) が、3
となります。

メタフィールドとメタオブジェクトの準備ができたら、適当な商品を見繕って、購入制限情報を設定します。

実装

早速ですが、下図のようなShopify Flowになります。

  1. トリガー: Order created
  2. アクション: Run code
  3. 条件: Run codeisHoldtrue であれば
  4. アクション: Hold fulfillment order

Run codeの内容

入力

query{
  order{
    createdAt
    lineItems {
      quantity
      product {
        id
        metafields {
          namespace
          key
          reference {
            ...on Metaobject {
              fields {
                key
                value
              }
            }
          }
        }
      }
    }
    customer {
      orders {
        createdAt
        lineItems {
          quantity
          product {
            id
          }
        }
      }
    }
  }
}

Run codeにて、meta objectを取り出すには、下記のようにします。

        metafields {
          namespace
          key
          reference {
            ...on Metaobject {
              fields {
                key
                value
              }
            }
          }
        }

referenceを利用するのが肝です。
なお、meta objectの「リスト」をメタフィールドに設定している場合は、おそらく、referenceではなくreferencesを利用します。(検証はしておりません。)

コード

export default function main(input) {
  const lineItems = input.order.lineItems;
  const customerOrders = input.order.customer.orders;
  const lastDate = new Date(input.order.createdAt);

  const META_FIELD_NAMESPACE = "custom";
  const META_FIELD_KEY = "purchase_condition";

  const productsWithConditios = lineItems.map(lineItem => {
    const metafields = lineItem.product.metafields;
    const metaObject = metafields.reduce((obj, metafield) => {
      if (metafield.namespace === META_FIELD_NAMESPACE && metafield.key === META_FIELD_KEY && metafield.reference) {
        obj = metafield.reference.Metaobject.fields.reduce((acc, cur) => {
          acc[cur.key] = cur.value;
          return acc;
        },{});
        return obj;
      }
    },{});
    if (metaObject) {
      return {
        id: lineItem.product.id,
        quantity: lineItem.quantity,
        period: metaObject.period,
        limitQuantity: metaObject.quantity
      }
    }
  }).filter(Boolean);

  const unavailableProducts = productsWithConditios.map(product => {
    const firstDate = lastDate;
    firstDate.setDate(lastDate.getDate() - product.period);
    const totalQuantity = customerOrders.reduce((init, order) => {
      const orderCreatedAt = new Date(order.createdAt);
      if ((orderCreatedAt.getTime() - firstDate.getTime()) >= 0) {
        const targetProducts = order.lineItems.filter(lineItem => lineItem.product.id === product.id);
        if (targetProducts.length >= 1) {
          init = init + targetProducts.reduce((q, targetProduct) => {
            q = q + targetProduct.quantity;
            return q;
          },0);
        }
      }
      return init;
    },0);
    if (totalQuantity > product.limitQuantity) {
      product.totalQuantity = totalQuantity;
      return product;
    }
  }).filter(Boolean)
  
  return {
    isHold: unavailableProducts.length > 0 ? true : false,
    unavailableProducts: unavailableProducts
  }
}

処理のポイントは下記です。

  • 最新の注文の作成日時を基準日時(lastDate)として考えます。
  • その基準日時から、meta objectに指定した「期間」を引いた日時(firstDate)までが、集計期間になります。
  • 最新の注文に、meta objectが指定してある商品があれば処理を行います。
  • 最新の注文に含まれるmeta objectが指定してある商品ごとに、顧客の注文履歴をチェックします。
  • チェックする顧客の注文履歴は「集計期間内」のものだけです。
  • 集計期間中にmeta objectが設定されている商品を何個購入したか、を集計します。
  • もし、集計結果がmeta objectに指定した「個数」を超える場合は、その商品情報を配列に格納します。
  • 上記の配列の長さが0より大きい場合、isHoldフラグをtrueにし、そうでなければ、falseにします。

出力

type Output {
  isHold: Boolean!
  unavailableProducts: [Product!]
}

type Product {
  id: ID!
  quantity: Int!
  period: Int!
  limitQuantity: Int!
  totalQuantity: Int!
}

Run codeの返り値のサンプルと説明は下記です。

runCode = {
  isHold: true, // 発送を保留するかどうかのフラグ
  unavailableProducts: [ // 購入制限に引っかかった商品群
    {
      id: "gid://shopify/Product/xxxxxxxxxxxx", // 商品ID
      quantity: 2, // 今回購入された個数
      period: 30, // 集計期間
      limitQuantity: 3, // 制限個数
      totalQuantity: 4 // 集計期間中の購入個数
    }
  ]
}

今回は、 runCode.isHold しか、Shopify Flowでの制御に利用していませんが、ストアオーナーにアラート通知を送るようにした場合に、 runCode.unavailableProducts を利用することで、どの商品が、どれくらいの個数オーバーして発送保留になったかを記載することもできます。

まとめ

こちらの記事で予告してから、本記事を執筆するまで1年もかかってしまいました。
時が経つのは早いものです。

さて、Run Codeには、

  • 現在時刻を参照できない ( new Date() の結果が現在時刻にならない )
  • 外部APIをコールできない

などの制限があります。
1年前の時点では、2024のQ3には外部APIコールができるようになるロードマップだったと思うのですが、今も実装されていないということはロードマップから削除されたのかもしれません。

なお、Run codeを利用する上で参考になる「利用例(テンプレート)」が公開されているので、そちらも参考にすると、よりRun codeを有効活用できるかと思います。

利用例(テンプレート): https://help.shopify.com/ja/manual/shopify-flow/reference/actions/run-code#templates

それでは。

Share

@googlemaps/react-wrapperでGoogle Map上に五芒星を描画する