こんにちは。
ディレクターの田辺です。
今回は、こちらの記事で予告した、
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になります。
- トリガー: Order created
- アクション: Run code
- 条件: Run codeのisHoldがtrueであれば
- アクション: 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
それでは。