# Shopify Fuctions APIを使用した自動割引Extension開発方法（廃止された割引APIの移行）

> ShopifyのFunctions APIを使用した自動割引の拡張機能（Extension）について開発方法や廃止された割引APIから新しい割引APIへの移行手順、実装例を紹介します。必要な開発ツールや構成、ディスカウントのロジック更新の手順も詳しく解説しています。

- 公開日: 2025-06-04
- 著者: Matsu
- タグ: Shopify
- URL: https://tech.anycloud.co.jp/articles/shopify-fuctions-api-extension

---

今回は、ShopifyのFuctions APIを使用した割引の拡張機能（Discount Function Extension）の作成について紹介します！

また、最近API自体が大幅に変わったこともあり、廃止された割引API から新しい割引API に移行する方法も併せてのご紹介となります。

## 前提

-   Shopifyアプリが構築されていること
    -   Functions拡張機能は`shopify app create`で作成したアプリのディレクトリ構成内に組み込まれるため
    -   作成したFunction ExtensionをShopに適用するには、アプリを通じてインストールおよび設定する必要があるため

ディレクトリ構成的には以下のようになります。

```dart
my-shopify-app/
├── extensions/
│   └── my-discount-function/
│       ├── shopify.extension.toml
│       └── src/
│           └── function.ts
├── shopify.app.toml
```

## 必要な開発ツールと構成

<table><tbody><tr><th colspan="1" rowspan="1"><p>構成要素</p></th><th colspan="1" rowspan="1"><p>説明</p></th></tr><tr><td colspan="1" rowspan="1"><p>Shopifyアプリ</p></td><td colspan="1" rowspan="1"><p>Functions Extensionを含むメインコンテナをCLIで作成</p></td></tr><tr><td colspan="1" rowspan="1"><p>Functions Extension</p></td><td colspan="1" rowspan="1"><p><code>discounts</code> タイプのExtensionを定義</p></td></tr><tr><td colspan="1" rowspan="1"><p>Shopify CLI</p></td><td colspan="1" rowspan="1"><p>開発・デプロイ・登録すべてに必須</p></td></tr><tr><td colspan="1" rowspan="1"><p>Partnerアカウント</p></td><td colspan="1" rowspan="1"><p>アプリの作成・開発に必要</p></td></tr><tr><td colspan="1" rowspan="1"><p>開発用Shop</p></td><td colspan="1" rowspan="1"><p>開発したアプリをテストするための環境</p></td></tr></tbody></table>

## **廃止された割引APIから新しい割引APIに移行する**

[https://shopify.dev/docs/apps/build/discounts/migrate-discount-api?extension=javascript&appType=graphql](https://shopify.dev/docs/apps/build/discounts/migrate-discount-api?extension=javascript&appType=graphql)

結論から言うと、ドキュメントには移行と書かれていますが、ちょっと書き換えるような移行ではなく、Extension自体を作り直すと言うのが正しい解釈です。

私も、APIバージョンだけ上げて既存コードの型などを修正しようと試みましたが、大幅にレスポンスなども変わってしまっており、作り直すのが早いと言う回答に至りました。

## Discount APIで割引のExtension作成〜デプロイ

ここからは実践です。

### Extension Appを作成

`my-shopify-app/`の階層で以下コマンドを実行。

```plaintext
% shopify app generate extension --template discount --name my-discount-function
```

ストアの選択と言語選択が出てくるので、自分に合うものを選択してください。

これでExtensionが作られます。

```plaintext
my-shopify-app/
├── extensions/
│   └── my-discount-function/
├── shopify.app.toml
```

<figure><img src="./image-001.webp" alt="Extension Appのディレクトリ構成" width="574" height="760"></figure>

### デフォルト生成された割引のロジック見てみる

デフォルトで生成されたファイルの中に、

-   cart\_lines\_discounts\_generate\_run.ts
-   cart\_delivery\_options\_discounts\_generate\_run.ts

すでに簡易的な割引機能が実装されています。

`cart_lines_discounts_generate_run.ts`では、以下のような割引が実装されています。

-   `DiscountClass.Order` で注文全体から10%割引
-   `DiscountClass.Product` でカート内で最も高額な商品に20%割引

`cart_delivery_options_discounts_generate_run.ts`では、以下のような割引が実装されています。

-   `DiscountClass.Shipping`で配送グループに対して 100% オフ（送料無料）割引を返す

### 割引ロジックを更新する（初回購入かつ5,000円以上の購入に限り500円OFFする機能を作ってみる）

では、せっかくなので別の割引機能を実装してみましょう。

仕様としては以下を想定します。

-   カートの合計が税込5,000円以上の購入で500円OFF
-   初回購入のみに適応（shopifyのattribute属性で判定）

まずは、attribute属性などを取得できるようにGraphQLを更新する必要があります。

`cart_lines_discounts_generate_run.graphql`

attributeとcostが取得できるようにQueryを追加しました。

```graphql
query CartInput {
  cart {
    attribute(key: "isFirstOrder") {
      key
      value
    }
    cost {
      totalAmount {
        amount
        currencyCode
      }
    }
    lines {
      id
      cost {
        subtotalAmount {
          amount
        }
      }
    }
  }
  discount {
    discountClasses
  }
}

```

GraphQLを更新したらスキーマの更新をする必要があります。

`my-shopify-app/extension/my-discount-function/`の階層で以下コマンドを実行してください。

```plaintext
% shopify app function schema
```

これで、attributeやcostが使えるようになります。

ロジックを以下のように修正します。

要所にコメントしていますので、わかりやすいかと。

`cart_lines_discounts_generate_run.ts`

```typescript
import {
  CartLinesDiscountsGenerateRunResult,
  CurrencyCode,
  DiscountClass,
  OrderDiscountSelectionStrategy,
} from "../generated/api";

export function cartLinesDiscountsGenerateRun(
  input
): CartLinesDiscountsGenerateRunResult {
  // カート内の商品行が0件なら処理を中断
  // Shopifyの仕様上、空のカートでは割引処理をしないため
  if (!input.cart.lines.length) {
    throw new Error("No cart lines found");
  }

  // Discount Functionが許可されている割引タイプが入っているか確認
  const hasOrderDiscountClass = input.discount.discountClasses.includes(
    DiscountClass.Order
  );

  // 対象の割引クラスがなければreturn
  if (!hasOrderDiscountClass) {
    return { operations: [] };
  }

  // 初回割引対象かの属性確認
  const attribute = input.cart.attribute;
  const isFirstOrder =
    attribute?.key === "isFirstOrder" && attribute?.value === "true";

  if (!isFirstOrder) {
    return { operations: [] };
  }

  // 初回割引対象の5,000円以上（税込）の購入か確認
  // hr商品では商品価格に税が含まれている運用なのでsubtotalAmountを使用する
  const totalAmount = input.cart.cost.totalAmount;
  if (
    totalAmount.currencyCode !== CurrencyCode.Jpy ||
    totalAmount.amount < 5000
  ) {
    return { operations: [] };
  }

  // 割引操作を格納する配列を初期化
  // Shopify Functionsでは、割引の定義（operations）を配列で返す必要があるので、複数の割引を順にpushできるように
  const operations: CartLinesDiscountsGenerateRunResult["operations"] = [];

  operations.push({
    // 注文全体に対する割引
    orderDiscountsAdd: {
      // 割引候補
      candidates: [
        {
          message: "初回5,000円以上の購入で500円OFF", // 管理画面などで表示される説明文
          targets: [
            {
              // 注文のどの部分に割引を適用するか？ここでは「全体（orderSubtotal）」
              orderSubtotal: {
                excludedCartLineIds: [],
              },
            },
          ],
          value: {
            // 割引種類
            fixedAmount: {
              amount: "500",
            },
          },
        },
      ],
      selectionStrategy: OrderDiscountSelectionStrategy.First, // 割引候補のうちどれを採用するかの戦略（First = 最初に適用可能なもの）
    },
  });

  return {
    operations,
  };
}
```

### ディスカウントExtensionをデプロイする

実装が完了したら、ExtensionをShopifyにデプロイします。

`my-shopify-app/extension/my-discount-function/`の階層で以下コマンドを実行してください。

```plaintext
% shopify app deploy
```

デプロイ完了すると、ターミナルのログにShopify CLIから提供されたリンクURLが出る（successの緑枠の中）ので、クリックして開きます。

<figure><img src="./image-002.webp" alt="Shopify CLIのデプロイ" width="1486" height="614"></figure>

### **Shopify GraphiQL Appのインストール**（初回デプロイ時のみ）

アプリをデプロイしただけでは、実際にまだディスカウントが作成されていません。

GraphiQLインターフェースを使用して、前のステップでデプロイした関数を基に、商品、注文、配送の割引を作成します。

具体的には、関数のIDをクエリし、それに基づいて適切なGraphQLミューテーションで割引を作成します。

Shopify GraphiQL Appを使うので、インストールしておきましょう。

[https://shopify-graphiql-app.shopifycloud.com/login](https://shopify-graphiql-app.shopifycloud.com/login)

また、インストール時に`write_discounts`権限が必要なので付与してください。

その他、作成した割引ロジックで必要な権限があれば付与しましょう。

### **Shopify GraphiQL Appから割引機能のIDを見つける**（初回デプロイ時のみ）

まずは、クエリでアプリを探して、IDをコピーします。

```graphql
query {
  shopifyFunctions(first: 100) {
    nodes {
      app {
        title
      }
      apiType
      title
      id
    }
  }
}
```

<figure><img src="./image-003.webp" alt="Shopify GraphiQL App" width="700" height="453"></figure>

```plaintext
{
  "app": {
		"title": "my-shopify-app"
  },
	"apiType": "order_discounts",
	"title": "my-discount-function",
	"id": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxx"
},
```

### **Shopify GraphiQL Appから割引を作成する**（初回デプロイ時のみ）

次に、先ほど取得したIDを`functionId`に代入して使って割引を作成します。

```graphql
mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "初回5,000円以上の購入で500円OFF"
      functionId: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxx"
      discountClasses: [ORDER]
      startsAt: "2025-01-01T00:00:00"
    }
  ) {
    automaticAppDiscount {
      discountId
    }
    userErrors {
      field
      message
    }
  }
}
```

### 実際にディスカウントを動作確認する

実行が完了したら、Shopify管理画面メニューのディスカウントから割引が有効になっていることを確認します。

<figure><img src="./image-004.webp" alt="Shopify ディスカウント" width="1185" height="221"></figure>

これで実際にストアでチェックアウトを見ると割引が適応されるようになります。

<figure><img src="./image-005.webp" alt="チェックアウトで割引確認" width="594" height="402"></figure>

## まとめ

本記事では、Shopify Functions API を活用した自動割引機能（Discount Function Extension） の開発方法、廃止された割引APIからの移行を含めて詳しく解説しました。

今後のShopify開発に参考になれば幸いです。
