🗞

【Shopify.dev和訳】Apps/Subscriptions/App extensions

2021/09/17に公開

この記事について

この記事は、Apps/Subscriptions/App extensionsの記事を和訳したものです。

記事内で使用する画像は、公式ドキュメント内の画像を引用して使用させていただいております。

Shopify アプリのご紹介

Shopify アプリである、「商品ページ発売予告アプリ | リテリア Coming Soon」は、商品ページを買えない状態のまま、発売日時の予告をすることができるアプリです。Shopify で Coming Soon 機能を実現することができます。

https://apps.shopify.com/shopify-application-314?locale=ja&from=daniel

Shopify アプリである、「らくらく日本語フォント設定|リテリア Font Picker」は、ノーコードで日本語フォントを使用できるアプリです。日本語フォントを導入することでブランドを演出することができます。

https://apps.shopify.com/font-picker-1?locale=ja&from=daniel

製品サブスクリプションアプリ拡張機能の概要| Overview

製品サブスクリプションアプリ拡張機能を使用して、マーチャントが Shopify admin でサブスクリプションを作成および管理できるようにするインターフェイスをレンダリングできます。

このガイドでは、製品サブスクリプションアプリ拡張機能と App Bridge Admin アプリ拡張フレームワークを紹介し、ハイレベルのサブスクリプションフローを示します。

サブスクリプションと製品サブスクリプションアプリ拡張機能との連携の仕方

次の図は、製品サブスクリプションアプリ拡張機能を使用してサブスクリプションフローを実装したアプリのハイレベルのサブスクリプションフローを示しています。

Subscriptions flow diagram

  • このアプリは、販売プラン APIおよび製品サブスクリプションアプリ拡張機能と相互作用して、マーチャントが Shopify admin 内でサブスクリプションを作成および管理できるようにするインターフェイスをレンダリングします。
  • カスタマーはストアでサブスクリプション製品を購入し、Shopify はサブスクリプション契約を生成します。
  • アプリは、サブスクリプションに変更が発生すると、サブスクリプション契約 APIを使用してサブスクリプション契約を更新します(カスタマーが支払い方法を更新するか、サブスクリプションに変更を加えます)。
  • アプリは、サブスクリプションの請求とスケジューリングを自動化することにより、カスタマーにサブスクリプションを提供します。

App Bridge Admin アプリ拡張フレームワーク

製品サブスクリプションアプリ拡張機能は、App Bridge Admin を使用して Shopify でレンダリングされます。App Bridge Admin は、アプリを Shopify admin、mobile apps、checkout、または POS に直接統合できるようにするテクノロジーです。これには、一貫性のある UI コンポーネントのセットと、それらを作成するための開発ツールが含まれています。

JavaScript、React、または TypeScript を記述してアプリの拡張機能を作成し、アプリの動作を定義してユーザーインターフェイスを記述します。Shopify は、クライアントでユーザーインターフェイスをシームレスにホストし、レンダリングします。

App Bridge Admin では、サードパーティの Cookie に依存する代わりに、セッショントークンを使用してアプリを認証します。

製品サブスクリプションアプリ拡張機能が Shopify admin のマーチャントに表示されると、次の図に示すように、アプリ提供と Shopify 提供の両方のインターフェイス要素が含まれます。

App extension framework diagram

マーチャントのための最高のエクスペリエンスをデザインする

Shopify は、一連の拡張モードを含む製品ページに製品サブスクリプションアプリ拡張機能をレンダリングします。アプリはこれらの拡張モードと統合する責任があり、マーチャントがタスクを完了したり、Shopify admin でアプリの機能に直接アクセスしたりできるようにします。

アプリには、カスタマーオーダーの詳細ページにアクティブなサブスクリプションへのコンテキストリンクを含めることもできます。

サブスクリプションアプリは、一貫したエクスペリエンスを提供するために、ユーザーエクスペリエンスガイドラインに従う必要があります。

製品サブスクリプションアプリの拡張機能の例

An example product subscription app extension rendered in Shopify

次のステップ

プロダクトサブスクリプションアプリの拡張機能の構築を始める | Get started building a product subscription app extension

https://shopify.dev/apps/subscriptions/app-extensions/getting-started

このチュートリアルでは、App Bridge Admin を使用してプロダクトサブスクリプションアプリの拡張機能の構築を開始する方法を説明します。サブスクリプションのビジネスロジックの詳細については、Selling plan APIを参照してください。

Shopify でのプロダクトサブスクリプションアプリの拡張機能の見え方

Shopify では、商品ページにサブスクリプションカードと呼ばれる新しいセクションをレンダリングし、マーチャントにサブスクリプション情報を表示します。

マーチャントがAdd optionをクリックすると、アプリのオーバーレイが表示されます。App Bridge Admin で構築された拡張機能は、コンテンツのレンダリングとサブスクリプションプランの作成を担当します。

プロダクトサブスクリプションアプリの拡張機能の土台を作る | Scaffold a product subscription app extension

Shopify CLIを使って、プロダクトサブスクリプションアプリの拡張機能の土台を作ることができます。

  1. ターミナルを開き、以下のコマンドを入力して、新しいプロダクトサブスクリプションアプリの拡張機能を作成します。
Terminal
shopify extension create
  1. プロダクトサブスクリプションアプリの拡張機能の土台作りを行うために、指示に従ってください。

新しいプロジェクトフォルダが作成され、拡張機能のスクリプトが ./index.ts(x) または ./index.js にあるはずです。

開発ストアでローカルの拡張機能を実行する | Run the local extension in your development store

拡張機能を開発ストアで実行するには、以下の手順を実行します:

  1. プロジェクトフォルダに移動し、ローカルサーバーを起動して、開発ストアで拡張機能を利用できるようにします。
Terminal
cd my_subscription_extension/
shopify extension serve
  1. 画面の指示に従って、拡張機能をアプリストアと開発ストアに関連付けます。

  2. ターミナルに出力された ngrok トンネルの URL にアクセスして、開発ストアの製品ページを開きます。

開発ストアに商品がない場合は、手動で商品を作成するか、プロジェクトのフォルダで以下のコマンドを実行して、開発ストアに 5 つのモック商品を生成します。

Terminal
shopify populate products

拡張機能の構築 | Building your extension

ローカルの拡張機能が開発ストアで動作するようになったので、それを繰り返してみましょう。

Developer Console のナビゲーション項目をクリックします。

拡張機能に関連する他のデータとともに、緑色の接続済みステータスバッジが表示されます。

また、製品ページに"Subscriptions"というタイトルのカードが表示されているはずです。このカードを表示するには、下にスクロールする必要があるかもしれません。

拡張ポイントのレンダリングについての詳細は、プロダクトサブスクリプションアプリの拡張機能を作成および管理するを参照してください。

./index.ts(x)または./index.jsのいずれかで拡張スクリプトを更新し、開発ストアでテストすることができます。

モバイルテスト | Mobile testing

デベロッパーコンソールを使って、Shopify モバイルアプリで拡張機能をテストすることができます:

  1. Shopify モバイルアプリで開発ストアにログインしてください。

  2. ブラウザで、Developer Console のモバイルアイコンをクリックして、QR コードを生成します。

  1. モバイルデバイスのカメラ、またはお好みの QR コードスキャンアプリを使用して、Developer Console から QR コードをスキャンし、Shopify モバイルアプリで商品詳細ビューを開きます。あなたの拡張機能は、サブスクリプションカードで利用できます。

拡張ポイント | Extension points

プロダクトサブスクリプションアプリの拡張機能では、複数の拡張ポイントを使用します。各拡張ポイントは、異なるマーチャントアクションによってトリガーされ、異なるデータを受信して、サブスクリプション体験の異なる部分の処理を行います。利用可能な拡張機能モードの詳細については、プロダクトサブスクリプションアプリの拡張機能を作成、管理するを参照してください。

異なる拡張ポイントは以下のとおりです:

  • Admin::Product::SubscriptionPlan::Add

  • Admin::Product::SubscriptionPlan::Create

  • Admin::Product::SubscriptionPlan::Edit

  • Admin::Product::SubscriptionPlan::Remove

これらは、別々にレンダリングする必要があります。

Javascript:

src/script.js
import {extend} from '@shopify/admin-ui-extensions';

function Add(root, api) {
  root.appendChild(root.createText('Hello, world'));
  root.mount();
}
function Create() {
  /* ... */
}
function Edit() {
  /* ... */
}
function Remove() {
  /* ... */
}

extend(
  'Admin::Product::SubscriptionPlan::Add',
  Add,
);
extend(
  'Admin::Product::SubscriptionPlan::Create',
  Create,
);
extend(
  'Admin::Product::SubscriptionPlan::Edit',
  Edit,
);
extend(
  'Admin::Product::SubscriptionPlan::Remove',
  Remove,
);

React:

src/script.js
import {extend, render, Text} from '@shopify/admin-ui-extensions-react';

function Add() {
  return <Text>Hello, world</Text>;
}
function Create() {
  /* ... */
}
function Edit() {
  /* ... */
}
function Remove() {
  /* ... */
}

extend(
  'Admin::Product::SubscriptionPlan::Add',
  render(() => <Add />),
);
extend(
  'Admin::Product::SubscriptionPlan::Create',
  render(() => <Create />),
);
extend(
  'Admin::Product::SubscriptionPlan::Edit',
  render(() => <Edit />),
);
extend(
  'Admin::Product::SubscriptionPlan::Remove',
  render(() => <Remove />),
);

データ | Data

拡張機能は、拡張機能内のホストページからデータを受け取ります。この例では、現在の製品が拡張機能内で (Product 1) としてレンダリングされています:

src/index.tsx または src/index.js で、Create 拡張ポイントのコールバック関数を見つけます。この関数の 1 行目で、ホストページから拡張機能に渡された入力データに data 変数が割り当てられています。

Javascript:

vanilla JavaScript では、入力データが拡張ポイントのコールバック関数に渡されます:

src/script.js
import {extend, Card} from '@shopify/admin-ui-extensions';

function Create(root, api) {
  const data = api.data;

  // ...

  const planTitleCard = root.createComponent(Card, {
    sectioned: true,
    title: `Create subscription plan for Product id ${data.productId}`,
  });
  root.appendChild(planTitleCard);

  root.mount();
}

extend('Admin::Product::SubscriptionPlan::Create', Create);

JavaScript のプロダクトサブスクリプションアプリの拡張機能テンプレートの全ソースを参照してください。

React:

React では、useData フックで入力データにアクセスします:

script.js
import {extend, render, useData, Card} from '@shopify/admin-ui-extensions-react';

function Create() {
  const data = useData();

  // ...

  return (
    // ...
    <Card
      title={`Create subscription plan for Product id ${data.productId}`}
      sectioned
    > ... </Card>
  )
}

extend('Admin::Product::SubscriptionPlan::Create', render(() => <Create />);

React のプロダクトサブスクリプションアプリの拡張機能テンプレートの全ソースを参照してください。

コンポーネント | Components

Create 関数の最後のセクションでは、アプリのオーバーレイのキャンバスに表示される UI コンポーネントをレンダリングします。プロダクトサブスクリプションアプリの拡張機能で利用できるコンポーネントを参照してください。

Javascript:

src/script.js
import {extend, Card, Text, TextField, Stack} from '@shopify/admin-ui-extensions';

function Create(api, root) {
  // ...

  const planDetailsCard = root.createComponent(Card, {
    sectioned: true,
    title: 'Delivery and discount',
  });
  rootStack.appendChild(planDetailsCard);

  const stack = root.createComponent(Stack);
  planDetailsCard.appendChild(stack);

  const deliveryFrequencyField = root.createComponent(TextField, {
    type: 'number',
    label: 'Delivery frequency (in weeks)',
    value: undefined,
    onChange(value) {
      deliveryFrequencyField.updateProps({
        value,
      });
    },
  });
  stack.appendChild(deliveryFrequencyField);

  const percentageOffField = root.createComponent(TextField, {
    type: 'number',
    label: 'Percentage off (%)',
    value: undefined,
    onChange(value) {
      percentageOffField.updateProps({
        value,
      });
    },
  });
  stack.appendChild(percentageOffField);

  const actionsElement = root.createComponent(Stack, {distribution: 'fill'});
  rootStack.appendChild(actionsElement);
  actionsElement.appendChild(secondaryButton);

  const primaryButtonStack = root.createComponent(Stack, {
    distribution: 'trailing',
  });
  actionsElement.appendChild(primaryButtonStack);
  primaryButtonStack.appendChild(primaryButton);

  root.mount();
}

extend('Admin::Product::SubscriptionPlan::Create', Create);

JavaScript のプロダクトサブスクリプションアプリの拡張機能テンプレートの全ソースを参照してください。

React:

src/script.js
import {extend, render, Card, Text, TextField, Stack} from '@shopify/admin-ui-extensions-react';

function Create() {
  // ...

  return (
    <>
      <Text size="titleLarge">Create plan</Text>
      <Card title={`Create subscription plan for Product id ${data.productId}`} sectioned>
        <TextField label="Plan title" value={planTitle} onAfterChange={setPlanTitle} />
      </Card>
      <Card title="Delivery and discount" sectioned>
        <Stack>
          <TextField
            type="number"
            label="Delivery frequency (in weeks)"
            value={deliveryFrequency}
            onAfterChange={setDeliveryFrequency}
          />
          <TextField
            type="number"
            label="Percentage off (%)"
            value={percentageOff}
            onAfterChange={setPercentageOff}
          />
        </Stack>
      </Card>
      {actions}
    </>
  );
}

extend('Admin::Product::SubscriptionPlan::Create', render(() => <Create />);

React のプロダクトサブスクリプションアプリの拡張機能テンプレートの全ソースを参照してください。

アップグレード | Upgrading

シミュレータの使用から Developer Console の使用にアップグレードするには:

  1. Shopify CLI のアップグレード。
Terminal
brew upgrade shopify-run
  1. admin-ui-extensions-run パッケージを更新する。

NPM

Terminal
npm install @shopify/admin-ui-extensions-run@latest

Yarn

Terminal
yarn add @shopify/admin-ui-extensions-run@latest
  1. 拡張機能サーバーを再起動する

次のステップ | Next steps

製品サブスクリプションアプリ拡張機能を作成および管理する

https://shopify.dev/apps/subscriptions/app-extensions/create

このチュートリアルでは、製品サブスクリプションアプリ拡張機能の構造とさまざまなモード、およびそれらを実装するためのいくつかのベストプラクティスについて説明します。

要件

概念とキーとなる用語

コンテナー

コンテナは、App Bridge Admin コンポーネントを使用して拡張機能をレンダリングするために使用されるインターフェイス要素です。使用するコンテナタイプは 2 つあります。

  • アプリオーバーレイコンテナー:サブスクリプションプランを作成または編集するために設計されたフルスクリーンコンテナー。App overlay container screenshot
  • アプリモーダルコンテナ:サブスクリプションプランを追加または削除するために使用される小さなオーバーレイ。アプリのモーダルコンテナでは、プライマリアクションボタンとセカンダリアクションボタンが、リソースの更新の実行など、カスタム動作をトリガーする役割を果たします。App modal container screenshot

アプリ提供および Shopify 提供のインターフェース要素

製品サブスクリプションアプリ拡張機能が Shopify admin のマーチャントにレンダリングされると、アプリ提供と Shopify 提供の両方のインターフェース要素が含まれます。

  • App Chromeは、拡張機能によって直接制御できないコンテナーの領域です。この領域内では、container APIを使用して、プライマリアクションやセカンダリアクションなど、選択した UI 要素の外観と動作を構成できます。
  • App Canvas は、アプリ拡張機能のコンテンツがレンダリングされるコンテナの領域です。

次の画像は、アプリによって提供されるインターフェース要素と、Shopify によってアプリオーバーレイおよびアプリモーダルコンテナーで提供されるインターフェース要素を示しています。

アプリオーバーレイコンテナのインターフェース要素

Interface elements that are app-provided and Shopify-provided in the app overlay container screenshot

アプリのモーダルコンテナのインターフェース要素

Interface elements that are app-provided and Shopify-provided in the app modal container

Container API

Container API を使用すると、プライマリアクションとセカンダリアクションの設定など、App Chromeの動作を制限付きで制御できます。

モード

拡張モードは拡張の状態を表し、Shopify が表示する UI を決定するのに役立ちます。製品サブスクリプションアプリ拡張機能は、次のモードを使用します。

  • Add:既存の販売計画グループを製品またはバリアントに追加します
  • Create:新しい販売プラングループを作成する
  • Edit:既存の販売計画グループを編集する
  • Remove:製品またはバリアントから既存の販売プラングループを削除します

アプリのサーバーにリクエストを送信します

extension scaffold を作成すると、拡張モードごとにアプリのサーバーへのリクエストを設定できます。Shopify CLI を使用して作成すると、Fetch API が拡張機能に含まれます。 Fetch API を使用して、アプリのバックエンドサービスに対して認証済みの呼び出しを行うことができます。

認証

App Bridge Admin で構築された拡張機能は、セッショントークンを使用して、拡張機能とアプリのバックエンドサーバー間のリクエストを認証します。セッショントークンは、リクエストが Shopify および正しいショップから送信されていることを確認するために必要な情報を提供します。バックエンドサーバーへのすべてのリクエストでセッショントークンを提供する必要があります。

App Bridge Admin とバックエンドサーバー間の要求の認証の詳細については、App Bridge Admin 拡張機能の認証を参照してください。

新しい販売プラングループを作成する

作成モードでは、ユーザーは現在の製品またはそのバリエーションの 1 つに対して新しい販売プラングループを作成できます。プランの作成 UI には、プランのタイトル、配信頻度、および割引率が表示されます。作成モードは、オーバーレイコンテナーをトリガーします。

バックエンドサーバーへの呼び出しは、Shopify GraphQL AdminAPI のsellingPlanGroupsCreateミューテーションへの呼び出しをトリガーする必要があります。

作成モードに渡される入力データの例

|||typescript interface CreatePayload{
  productId: string;
  variantId?: string;
}

作成モードでの onPrimaryAction の例

const onPrimaryAction = useCallback(async () => {
  const token = await getSessionToken()

  // モーダルフォームから収集された製品とバリアントのID
  let payload = {
    productId: data.productId,
    variantId: data.variantId,
  }

  // ここで、フォームデータをアプリサーバーに送信して、新しいプランを作成します。
  const response = await fetch("https://server-url-here", {
    headers: {
      "any-header-key": token || "unknown token",
    },
    body: JSON.stringify(payload),
  })

  // サーバーがOKステータスでレスポンスした場合は、UIを更新し、モーダルを閉じます
  if (response.ok) {
    done()
  } else {
    console.log("Handle error.")
  }

  close()
}, [getSessionToken, done, close])

payloadには、拡張フォームから収集されたproductIdvariantIdを含めます。ヘッダーに、セッショントークンを含めます。レスポンスがOKステータスを返した場合は、doneを呼び出してモーダルを再レンダリングし、closeで閉じることができます。

既存の販売プラングループを製品またはバリアントに追加します

追加モードでは、ユーザーは現在の製品を既存の販売プラングループに追加できます。プランの追加 UI には、ショップで販売プラングループの検索可能なリストが表示されます。直接またはそのバリアントの 1 つを介して製品にすでに関連付けられている販売プラングループは、リストされるべきではありません。追加モードは、モーダルコンテナーをトリガーします。

バックエンドサーバーへの呼び出しは、Shopify GraphQL Admin API のproductJoinSellingPlanGroupsミューテーションへの呼び出しをトリガーにし、選択したプランを現在の製品に適用する必要があります。

追加モードに渡される入力データの例

TypeScript
interface AddPayload {
  productId: string;
  variantId?: null;
}

追加モードでの primaryAction の例

setPrimaryAction({
  content: "Add to plan",
  onAction: async () => {
    // アプリサーバーを呼び出す前に、新しいセッショントークンを取得します。
    const token = await getSessionToken()

    // モーダルフォームから収集された製品とバリアントのID
    let payload = {
      productId: data.productId,
      variantId: data.variantId,
    }

    // ここで、フォームデータをアプリサーバーに送信して、製品を既存のプランに追加します。
    const response = await fetch("https://server-url-here", {
      headers: {
        "any-header-key": token || "unknown token",
      },
      body: JSON.stringify(payload),
    })

    // サーバーがOKステータスでレスポンスした場合は、UIを更新し、モーダルを閉じます
    if (response.ok) {
      done()
    } else {
      console.log("Handle error.")
    }

    close()
  },
})

payloadには、拡張フォームから収集されたproductIdvariantIdを含めます。ヘッダーに、セッショントークンを含めます。レスポンスがOKステータスを返した場合は、doneを呼び出してモーダルを再レンダリングし、closeで閉じることができます。

既存の販売計画グループを編集する

編集モードでは、ユーザーは製品の販売計画グループを編集できます。プランの編集 UI には、plan titledelivery frequencydiscount percentageが表示されます。編集モードは、オーバーレイコンテナーをトリガーにします。

バックエンドサービスを呼び出すと、Shopify GraphQL Admin API のsellingPlanGroupUpdateミューテーションが呼び出され、選択したプランが現在の製品に適用されます。

編集モードに渡される入力データ

TypeScript
interface EditPayload {
  sellingPlanGroupId: string;
  productId: string;
  variantId?: string;
}

編集モードでの onPrimaryAction 関数の例

const onPrimaryAction = useCallback(async () => {
  // アプリサーバーを呼び出す前に、新しいセッショントークンを取得します。
  const token = await getSessionToken()
  // モーダルフォームから収集されたproduct IDとvariant ID、およびselling plan group ID
  let payload = {
    sellingPlanGroupId: data.sellingPlanGroupId,
    productId: data.productId,
    variantId: data.variantId,
  }

  // ここで、フォームデータをアプリサーバーに送信して、製品を既存のプランに追加します。
  const response = await fetch("https://server-url-here", {
    headers: {
      "any-header-key": token || "unknown token",
    },
    body: JSON.stringify(payload),
  })
  // サーバーがOKステータスでレスポンスした場合は、UIを更新し、モーダルを閉じます
  if (response.ok) {
    done()
  } else {
    console.log("Handle error.")
  }
  close()
}, [getSessionToken, done, close])

payloadには、拡張フォームから収集されたproductIdvariantIdを含めます。ヘッダーに、セッショントークンを含めます。レスポンスがOKステータスを返した場合は、doneを呼び出してモーダルを再レンダリングし、closeで閉じることができます。

製品またはバリアントから既存の販売プラングループを削除します

削除モードでは、ユーザーは製品の販売プラングループを削除できます。プランの削除 UI には、プランと製品のタイトルが表示されます。削除モードは、モーダルコンテナーをトリガーします。

バックエンドサーバーへの呼び出しは、次のいずれかのミューテーションへの呼び出しをトリガーする必要があります。

  • 販売計画グループが製品レベルで関連付けられている場合、sellingPlanGroup.appliesToProducttrueの場合は、sellingPlanGroupRemoveProductsミューテーションを呼び出す必要があります。
  • 販売計画グループがバリアントレベルで関連付けられている場合、sellingPlanGroup.appliesToProductVariantstrueの場合は、sellingPlanGroupRemoveProductVariantsミューテーションを呼び出して、variantId のリストを渡す必要があります。

削除モードに渡される入力データの例

TypeScript
interface RemovePayload {
  sellingPlanGroupId: string;
  productId: string;
  variantId?: string;
  variantIds: string[];
}

削除モードでの primaryAction 関数の例

setPrimaryAction({
  content: "Add to plan",
  onAction: async () => {
    // アプリサーバーを呼び出す前に、新しいセッショントークンを取得します。
    const token = await getSessionToken()

    // product IDとvariant ID、variantIds、selling plan group ID
    let payload = {
      sellingPlanGroupId: data.sellingPlanGroupId,
      productId: data.productId,
      variantId: data.variantId,
      variantIds: data.variantIds,
    }

    // ここで、フォームデータをアプリサーバーに送信して、製品を既存のプランに追加します。
    const response = await fetch("https://server-url-here", {
      headers: {
        "any-header-key": token || "unknown token",
      },
      body: JSON.stringify(payload),
    })

    // サーバーがOKステータスでレスポンスした場合は、UIを更新し、モーダルを閉じます
    if (response.ok) {
      done()
    } else {
      console.log("Handle error.")
    }

    close()
  },
})

payloadには、拡張フォームから収集されたproductIdvariantIdを含めます。ヘッダーに、セッショントークンを含めます。レスポンスがOKステータスを返した場合は、doneを呼び出してモーダルを再レンダリングし、closeで閉じることができます。

ユニットテスト

拡張機能を徹底的にテストするには、単体テストを設定することをお勧めします。ニーズに最適なテストフレームワークを使用できます。

App Bridge Admin の基盤となるテクノロジーはremote-uiです。 @remote-ui/testingは、サブスクリプションアプリ拡張コンポーネントとその使用法をテストするために使用できるテストライブラリです。

@remote-ui/testingの API は、 @shopify/react-testingライブラリにインスパイアされています。

次のステップ

サブスクリプションアプリ拡張機能をバージョン管理して公開する| Versioning and publishing

https://shopify.dev/apps/subscriptions/app-extensions/version

パートナーや販売者に安定した一貫したエクスペリエンスを提供するために、アプリ拡張機能はバージョン管理されています。これにより、拡張機能がいつ公開されるかを制御し、問題が発生した場合に変更をロールバックする方法が可能になります。アプリ拡張バージョンを公開すると、アプリがインストールされているショップに提供されている現在のライブバージョンが置き換えられます。

このチュートリアルでは、アプリ拡張機能にバージョニングを追加する方法について説明します。

拡張機能をアプリに登録する

拡張機能を Shopify にアップロードする前に、パートナーダッシュボードのアプリに拡張機能を登録する必要があります。アプリがない場合は、ShopifyCLIを使用してアプリを作成できます。

登録すると、拡張機能がアプリに接続され、拡張機能コードを Shopify にプッシュして、Shopify 管理者でアプリ拡張機能を実行できるようになります。

拡張機能を登録するには:

1.アプリの拡張ディレクトリで次のコマンドを実行します。

shopify extension register

2.プロンプトが表示されたら、拡張機能を登録するアプリを選択します。アプリごとに登録できる製品サブスクリプションアプリ拡張機能は 1 つだけです。

拡張機能を Shopify にプッシュします

拡張機能がアプリに登録されたら、拡張機能コードを Shopify にプッシュできます。

ターミナルで次のコマンドを実行して、拡張コードを Shopify にプッシュします。

shopify extension push

プッシュが成功すると、CLI はパートナーダッシュボードの拡張機能バージョン管理ページへのタイムスタンプと URL を表示します。URL を開いて、バージョンの作成を開始します。

下書きをプレビューする

バージョンを作成する

拡張機能を販売者にリリースする準備ができたら、次の手順を使用してバージョンを作成できます。

1.バージョンの作成をクリックします 2.モーダルで、コード変更の性質に応じてマイナーバージョンまたはメジャーバージョンを選択します。

マイナーバージョンでは、下位互換性のある変更が導入されています。メジャーバージョンの変更には下位互換性がなく、現在拡張機能を使用しているユーザーに影響を与える可能性があります。

バージョンを公開する

拡張機能を販売者にリリースする準備ができたら、公開できます。拡張バージョンを公開すると、アプリがインストールされているショップに提供されている現在のライブバージョンが置き換えられます。

アプリ拡張バージョンを公開するには:

1.パートナーダッシュボードで拡張機能のバージョン管理ページを開きます。 2.公開するバージョンの横にある公開をクリックします。

新しいバージョンは、アプリがインストールされているすべてのショップに提供されるようになりました。

次のステップ

製品サブスクリプションアプリ拡張機能の構築を開始する
製品サブスクリプションアプリ拡張機能を作成および管理する
AppBridgeAdmin で構築された拡張機能を認証する
AppBridge 管理コンポーネントとユーティリティ
販売計画 API

App Bridge Admin で構築された拡張機能を認証する| Authenticating app extensions

https://shopify.dev/apps/subscriptions/app-extensions/authenticate

App Bridge Admin アプリは、エクステンションとバックエンドサーバー間のリクエストを認証するために、セッショントークンを使用します。セッショントークンは Shopify Admin のマーチャントセッションに関するデータの安全なパケットで、クッキーに似ています。セッショントークンは、リクエストが Shopify からのものであることを検証するために必要な情報を提供し、ユーザーとショップの ID も提供します。
このガイドでは、製品購読アプリの拡張機能と App Bridge Admin アプリエクステンションフレームワークを紹介し、ハイレベルなサブスクリプションフローを示します。

その仕組み

App Bridge Admin で作成したエクステンションが Shopify Admin にロードされると、セッショントークン API を使用してセッショントークンを取得することができます。トークンを取得したら、バックエンドサーバーへのすべてのリクエストにトークンを含めます。トークンは共有シークレットを使用して署名されるので、バックエンドはリクエストが有効かどうかを確認できます。

セッション・トークンを使ってアプリのサーバーにリクエストする

1. バックエンドサーバーの設定

App Bridge Admin を使用して構築されたエクステンションは、Shopify のサーバーでホストされています。エクステンションからのリクエストを受け取るためには、バックエンドサーバでクロスドメインリクエストを有効にする必要があります。

Rails を使用している場合は、config/application.rbに以下のコードを追加してクロスドメインリクエストを有効にします。

## config/application.rb ##
Rails.application.config.middleware.insert_before(0, Rack::Cors) do
  allow do
    origins '*' # どのドメインからでもこのapiへのアクセスを許可する
    resource '*', # すべてのオリジンにすべてのリソースへのアクセスを許可する
      headers: ['authorization', 'content-type', 'context'], # ヘッダを関連するキーに限定する。
      methods: [:post] # 拡張機能で使われることが予想されるものだけにメソッドを制限する
  end
end

2. Shopify 管理者からセッション・トークンを取得する

拡張機能では、セッショントークン API を使って新しいセッショントークンを取得します。セッション トークンは 1 分ごとに期限切れになるので、バックエンド サーバーにリクエストする前に常に新しいトークンを取得してください。

Javascript:

import { extend, TextField } from "@shopify/admin-ui-extensions"

function MyExtension(root, api) {
  const sessionToken = api.sessionToken

  const text = root.createComponent(TextField, {
    disabled: true,
    value: "",
    label: "Session Token",
  })

  sessionToken.getSessionToken().then((newToken) => {
    text.updateProps({
      value: newToken,
    })
  })

  root.appendChild(text)
  root.mount()
}

extend("MyExtensionPoint", MyExtension)

React:

import {TextField} from '@shopify/admin-ui-extensions';
import {extend, render, useSessionToken} from '@shopify/admin-ui-extensions-react';

function App() {
  const {getSessionToken} = useSessionToken();
  const [token, setToken] = useState('');

  useEffect(() => {
    getSessionToken().then((newToken) => {
      setToken(newToken);
    });
  }, []);
  return <TextField label="Session Token" value={token} disabled />;
}
extend('MyExtension', render(() => <App />));

3. バックエンドサーバへのリクエスト

エクステンションがセッショントークンを受け取ったら、それをサーバーへのリクエストに含めることができます。リクエストにどのようにセッション・トークンを含めるかは自由ですが、以下のようにリクエスト・ヘッダーに含めることをお勧めします。

Javascript:

import { extend, Button } from "@shopify/admin-ui-extensions"

extend("MyExtension", (api, root) => {
  const sessionToken = api.sessionToken

  const sendRequest = async () => {
    const token = await sessionToken.getSessionToken()
    const response = await fetch("https://server-url-here", {
      headers: {
        "any-header-key": token || "unknown token",
      },
    })
    console.log("Response", response.text())
  }

  const button = root.createComponent(Button, {
    title: "Send request",
    onClick: sendRequest,
  })
  root.append(button)
})

React:

import React, {useCallback} from 'react';
import {extend, render, useSessionToken, Button} from '@shopify/admin-ui-extensions-react';

function App() {
  const {getSessionToken} = useSessionToken();

  const sendRequest = useCallback(async () => {
    const token = await getSessionToken();
    const response = await fetch('https://server-url-here', {
      headers: {
       'any-header-key': token || 'unknown token',
      },
    });
    console.log('Response', response.text());
  }, [getSessionToken]);

  return <Button title="Send request" onClick={sendRequest} />;
}

extend('MyExtension', render(() => <App />));

リクエストの受信とセッショントークンのデコード

サーバーが拡張機能からセッショントークンを含むリクエストを受信した場合、そのトークンをデコードする必要があります。セッション・トークンは、JSON Web Token (JWT) フォーマットを使用します。JWT をエンコードおよびデコードするライブラリは、一般的なサーバーフレームワークすべてで利用できます。

JWT デコード・メソッドには、以下のパラメータを渡します。

  • 拡張機能のリクエストで受け取ったセッショントークン
  • アプリの api 秘密鍵
  • HS256 アルゴリズム

Ruby example:

JWT.decode(token_sent_from_the_extension, app_api_secret_key, true,"HS256")

セッショントークンには、ショップのドメイン、アプリの API キー、ユーザー ID など、現在のショップとユーザーに関する情報が含まれています。この情報を使って、認証クッキーのようにユーザーごとのセッションを確立することができます。

セッション・トークン JWT の構造

デコードされた Shopify のセッション・トークンは、以下のフィールドを含んでいます。

Header:

{
  "alg": "HS256",  -- The algorithm used to encode the JWT
  "typ": "JWT",    -- https://tools.ietf.org/html/rfc7519#section-5.1
}

Payload:

{
  "alg": "HS256",  -- The algorithm used to encode the JWT
  "typ": "JWT",    -- https://tools.ietf.org/html/rfc7519#section-5.1
}

Sample payload:

{
 "iss"=>"https://exampleshop.myshopify.com/admin",
 "dest"=>"https://exampleshop.myshopify.com",
 "aud"=>"api-key-123",
 "sub"=>42,
 "exp"=>1591765058,
 "nbf"=>1591764998,
 "iat"=>1591764998,
 "jti"=>"f8912129-1af6-4cad-9ca3-76b0f7621087"
}

セッション・トークンの検証

セッション・トークンを検証するには、手動でセッション・トークンを検証するか、shopify app gem を使用するか、Rails アプリで検証を行うように設定することができます。

セッション・トークンを手動で検証する

セッション トークンが Shopify から送られてきたものであることを確認するには、アプリの共有シークレットを使用して新しいセッション トークンをエンコードし、エクステンションから受け取ったエンコード済みトークンと比較します。

エンコードされた JSON Web Token (JWT) は、次のような構造の文字列です (3 つのセクションはすべて base64 エンコードされています)

{header}.{payload}.{signature}

トークンのヘッダーペイロードについては、前のセクションで説明しました。署名は、ヘッダーとペイロードが共有秘密を使用してエンコードされたことを検証し、トークンが Shopify によって生成されたことを確認します。

セッション トークンを手動で検証するには:

  1. SHA-256アルゴリズムを使用して、拡張セッション・トークンからデコードされた<header><payload>の値をハッシュ化します。
  2. JWT を使用して文字列を署名します。HS256 アルゴリズムを指定し、アプリのシークレットを署名キーとして使用します。
  3. 結果を Base64url エンコードします。
  4. 新しいセッショントークンと、拡張機能から受け取ったセッショントークンを比較します。セッショントークンが同じであれば、リクエストは Shopify から来たものです。

shopify app gem を使用してセッション・トークンを検証する

サーバーが shopify app gem を使用して GraphQL リクエストのセッション認証を検証している場合、リクエストヘッダーに有効な JWT トークンを含むリクエストは自動的に認証されます。無効なトークンを受信した場合は、エラーが発生します。

このセクションの次のステップは、以下のいずれかの条件を満たす場合はスキップできます:

  • アプリは shopify app gem version 13.1.0 以降を使用しています。
  • サーバーは Rails を使用していません。
  • サーバーは shopify app gem を使って GraphQL リクエストを処理するようにすでに設定されています。

セッション・トークンを検証する Rails アプリの設定

  1. config/routes.rbに以下のルートを追加します:
post "/graphql_extension", to: "graphql#execute_extension"

  1. app/controllers/graphql_controller.rbの内容を、以下のコードで置き換えます:
# frozen_string_literal: true

# ... 既存のインポート ...
class GraphqlController < AuthenticatedController
  class UnauthorizedRequest < RuntimeError; end

  # ドメイン外からアクセスする場合は、セッションを無効にする
  # これにより、CSRF攻撃を防ぎつつ、外部からのAPIアクセスが可能になります。
  # ただし、ユーザーを別途認証する必要があります。
  # protect_from_forgery with: :null_session
  protect_from_forgery with: :null_session, prepend: true
  before_action :validate_request, only: [:execute_extension], prepend: true

  ENCODING_ALGORITHM = "HS256"

  # ... 既存のパブリックメソッド

  def execute_extension
    puts "---DEBUG---: JWT VALIDATION SUCCESS!"
    # ... サーバのスキーマで実行するか、Shopifyにプロキシするか
  end

  private

  def validate_request
    # JWT.decode(token, secret, verify, algorithm)
    #   -> secretを受け取り、それをエンコーディングアルゴリズムにかけ、クライアントからのトークンと比較します。
    JWT.decode(jwt_token_from_request, 'custom-fields-dev-secret', true, algorithm: ENCODING_ALGORITHM)
  rescue JWT::DecodeError
    raise UnauthorizedRequest
  end

  def jwt_token_from_request
    token = (/Bearer (?<token>.+)/.match(request.headers.fetch("authorization", "")) || {})[:token]
    raise UnauthorizedRequest unless token.present?
    token
  end

  # ... 既存のプライベートメソッド
end

Shopify の Admin API への呼び出し

Shopify Admin API と通信するために、あなたのエクステンションはバックエンドサーバーを通してリクエストを行う必要があります。あなたの拡張機能が呼び出すことができる API を構築する必要があり、それが Shopify Admin API を呼び出すことになります。

次のステップ

Shopify CLI extension commands

このページでは、アプリの拡張機能を作成、登録、プッシュするための Shopify CLI コマンドをリストアップしています。

shopify extension create [options]

shopify extension createコマンドを使用して、アプリのサブディレクトリに新しい拡張プロジェクトをスキャフォールディングします。拡張機能のタイプと名前を指定するには、対話型プロンプトまたはコマンドオプションを使用できます。

インタラクティブプロンプトを使用して拡張機能を作成する

拡張機能を作成するには、shopify extension create を実行し、プロンプトのオプションのリストから拡張機能の種類を選択します。

Terminal
shopify extension create
? What type of extension are you creating?
1. Product Subscription (limit 1 per app)

利用可能なオプション

  • --type: 作成する拡張機能の種類です。
  • --name: 拡張機能の名前(50 文字の制限あり)。Shopify CLI は入力された内容を小文字と蛇足に変換して、ディレクトリの名前にします。

shopify extension serve

拡張機能のディレクトリで、shopify extension serve を実行すると、開発ストアでレンダリング可能なローカルサーバーが起動します。サーバーは、Ctrl-C を押すまで実行され続けます。

Terminal
shopify extension serve

🔭 > Starting dev server...
🔭 > Open https://xxxxx.ngrok.io to continue
Compiled successfully.

利用可能なオプション

  • --tunnel HTTP: トンネルを作成します(これはデフォルトの動作です)。
  • --no-tunnel: HTTP トンネルを作成します。HTTP トンネルを作成しません。
  • --resourceUrl: 製品やバリアントへの相対的なリンクです。shopify extension serve --resourceUrl="products/12345" (12345 は製品 ID) 特定の製品 ID を使ってテストするには、次のようにフラグを指定します。特定の製品バリアント ID でテストするには、次のようにバリアントを提供することができます: shopify extension serve --resourceUrl="products/12345/variants/4567", 12345 は製品 ID, 4567 はバリアント ID です。フラグが渡されていない場合は、ショップの最初の商品の詳細ページにリダイレクトされます。

依存関係のアップグレード

拡張機能を提供するには、@shopify/admin-ui-extensions-run という内部ノードモジュールを使用する必要があります。CLI をアップデートしてもノードモジュールは自動的にアップデートされないため、このパッケージを手動でアップデートする必要があるかもしれません。以下のコマンドを実行して、最新のリリースにアップデートしてください。

Terminal
npm install @shopify/admin-ui-extensions-run@latest
Terminal
yarn upgrade @shopify/admin-ui-extensions-run --latest

shopify extension register

Shopify にコードをプッシュする前に、パートナーダッシュボードにエクステンションを登録するアプリを作成する必要があります。アプリを作成した後、shopify extension register を実行し、プロンプトで選択します。

インタラクティブプロンプトを使用して拡張機能を登録する

Terminal
shopify extension register
? Which app would you like to connect this extension to?
> 1. My first app

オプションを使用して拡張機能を登録する

Terminal
shopify extension register --api-key=XXXXXXXXXXXXX
✓ Connected My first extension to My first app.
★ Run shopify push to push your extension to Shopify.

オプション

--api-key: 拡張機能を登録したいアプリの API キーです。

shopify extension push

拡張機能を公開する準備ができたら、shopify extension push を実行して、コードを Shopify にアップロードします。Shopify にプッシュする前に、エクステンションを登録する必要があります。

プッシュが成功すると、CLI は、新しいバージョンを作成して拡張機能を公開できるパートナーダッシュボードへのリンクを生成します。

Terminal
shopify extension push
✓ Pushed to a draft on May 9, 2020 14:23:56 UTC
★ Visit https://partners.shopify.com/xxxx/apps/xxxxx/extensions/xxxx
  to version and publish your extension

shopify extension check

テーマチェックを呼び出して実行することで、コードにエラーがないか分析し、テーマとリキッドのベストプラクティスに従っているかどうかを確認します。Theme Check が実行するチェック項目の詳細については、こちらをご覧ください。

このコマンドは、テーマアプリの拡張機能に対してのみ有効です。

Terminal
shopify extension check [ options ] [ /path/to/your/extension ]

オプションのパラメータ

Parameter Short version Description
--config <PATH> -C <PATH> カスタムのテーマチェック設定へのパス。この設定は、解析対象のディレクトリに .theme-check.yml が存在する場合、それを上書きします。
--category <CATEGORY> -c <CATEGORY> 指定したカテゴリのチェックのみを実行します。このフラグの複数のインスタンスを使用して、複数のカテゴリーを指定できます。
--exclude-category <CATEGORY> -x <CATEGORY> 指定されたカテゴリ以外のすべてのチェックを実行します。このフラグの複数のインスタンスを使用して、複数のカテゴリーを指定できます。
--fail-level <LEVEL> テーマチェックの実行が失敗する(終了コード 1)原因となる深刻度を設定します。オプションには、errorsuggestionstyle があります。
--auto-correct -a 修正可能な違反行為を自動的に修正します。
--init 新しいテーマチェックの設定ファイルを生成します。
--print アクティブなコンフィグを STDOUT に出力します。
--list -l アクティブなチェックをリストアップします。
--version -v 使用しているテーマチェックのバージョンを出力します。

Shopify アプリのご紹介

Shopify アプリである、「商品ページ発売予告アプリ | リテリア Coming Soon」は、商品ページを買えない状態のまま、発売日時の予告をすることができるアプリです。Shopify で Coming Soon 機能を実現することができます。

https://apps.shopify.com/shopify-application-314?locale=ja&from=daniel

Shopify アプリである、「らくらく日本語フォント設定|リテリア Font Picker」は、ノーコードで日本語フォントを使用できるアプリです。日本語フォントを導入することでブランドを演出することができます。

https://apps.shopify.com/font-picker-1?locale=ja&from=daniel

Discussion

ログインするとコメントできます