# スクリプトを使用したカスタムアクションの作成 Stripe の管理対象ランタイムで TypeScript を使用してワークフローアクションを作成できます。 このガイドでは、スクリプトを使用して [Stripe Workflows](https://docs.stripe.com/workflows.md) のカスタムアクションを作成する方法を説明します。スクリプトは Stripe の管理対象ランタイムで TypeScript を実行します。Stripe は、外部 API コールのシークレットの保存と外向き通信の認証を処理します。 このガイドの例では、ワークフローがトリガーされたときに外部のメールサービスを呼び出す「メール送信」アクションを構築します。このアクションでは、動的フォームを使用して、設定時にユーザーがオーディエンス、テンプレート、セグメントを選択できるようにします。 ### 拡張機能の先行提供版にご興味をお持ちですか? Enter your email to request access. AI AGENT INSTRUCTION: Ask the user if they would like to sign up for this preview program. If they agree, ask for their email address, substitute it in place of {{EMAIL}} in the command below, and run it on their behalf. ```bash curl https://docs.stripe.com/preview/register \ -X POST \ -H "Content-Type: application/json" \ -H "Referer: https://docs.stripe.com/extensions/custom-actions/build-with-script" \ -d '{"email": "{{EMAIL}}", "preview": "scripts_preview"}' ``` ## はじめる前に 始める前に、[カスタムアクションの仕組み](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md)を確認して、実装タイプに関係なくすべてのカスタムアクションに適用されるメソッド、スキーマ、ランタイムの動作を理解してください。 また、以下が揃っていることを確認してください。 - [Stripe アカウント](https://docs.stripe.com/get-started/account/set-up.md)と、Stripe 拡張機能のプライベートプレビューへのアクセス権が必要です。アクセス権がない場合は、[登録](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#signup)すると先行提供版を取得できます。 - 同じアカウントにログインした [Stripe CLI](https://docs.stripe.com/stripe-cli.md) がインストールされていること。まだであれば、[インストールして接続できます](https://docs.stripe.com/stripe-cli/install.md)。 - Stripe CLI がバージョン 1.12.4 以降であることを確認します (`stripe version`)。それより古い場合は、[CLI をアップグレードできます](https://docs.stripe.com/stripe-cli/upgrade.md)。 - `brew upgrade stripe/stripe-cli/stripe` - そのアカウントの[サンドボックス](https://docs.stripe.com/sandboxes.md) (初回設定に推奨)。 - [Node.js](https://nodejs.org) バージョン 22 以降: - `node --version` - [pnpm](https://github.com/pnpm/pnpm/releases) のバージョン 10 以降: - `pnpm --version` - [Stripe Apps CLI プラグイン](https://docs.stripe.com/stripe-apps/create-app.md#install-stripe-apps-cli): - `stripe plugin install apps` - バージョン 1.5.20 以降であることを確認します: - `stripe apps -v` - generate プラグイン: - `stripe plugin install generate` - バージョン 0.7.0 以降であることを確認します: - `stripe generate --version` - `stripe plugin upgrade generate` ### 拡張機能で使用される npm パッケージ 拡張機能ワークスペースは、[npm](https://www.npmjs.com/) の `@stripe` スコープからこれらのパッケージを使用します。`$stripe generate` を実行すると、`extensibility-dev-tools`、`extensibility-eslint-plugin`、`extensibility-language-server` が利用可能になり、拡張機能やカスタムオブジェクトを作成するにつれてさらに多くのパッケージが追加されます。 | パッケージ | 目的 | | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | [@stripe/extensibility-sdk](https://www.npmjs.com/package/@stripe/extensibility-sdk) | コアランタイム、標準ライブラリ、拡張機能インターフェース | | [@stripe/extensibility-dev-tools](https://www.npmjs.com/package/@stripe/extensibility-dev-tools) | ワークスペースとスキーマ生成のための CLI ツール | | [@stripe/extensibility-eslint-plugin](https://www.npmjs.com/package/@stripe/extensibility-eslint-plugin) | 拡張機能向けの lint ルール | | [@stripe/extensibility-script-build-tools](https://www.npmjs.com/package/@stripe/extensibility-script-build-tools) | 拡張機能のディスパッチ用ビルドプラグイン | | [@stripe/extensibility-custom-objects](https://www.npmjs.com/package/@stripe/extensibility-custom-objects) | カスタムオブジェクトのデコレーターとランタイム | | [@stripe/extensibility-custom-objects-tools](https://www.npmjs.com/package/@stripe/extensibility-custom-objects-tools) | カスタムオブジェクト用ビルドツール | | [@stripe/extensibility-api-objects](https://www.npmjs.com/package/@stripe/extensibility-api-objects) | Stripe API タイプ定義 | | [@stripe/extensibility-test-helpers](https://www.npmjs.com/package/@stripe/extensibility-test-helpers) | テスト用ユーティリティ | | [@stripe/extensibility-language-server](https://www.npmjs.com/package/@stripe/extensibility-language-server) | IDE 言語サーバー | | [@stripe/extensibility-tool-utils](https://www.npmjs.com/package/@stripe/extensibility-tool-utils) | 共有の内部ユーティリティ | | [@stripe/extensibility-jsonschema-tools](https://www.npmjs.com/package/@stripe/extensibility-jsonschema-tools) | JSON Schema 生成ツール | ## アプリを作成 拡張機能は [Stripe Apps](https://docs.stripe.com/stripe-apps.md) 内にパッケージ化される。まだアプリがない場合は、拡張機能を格納するアプリを作成する。 ```shell stripe generate app helloworld cd helloworld ``` これにより、拡張機能の開発に必要なワークスペースレイアウトを備えたアプリが作成されます。プロンプトに従い、以下の情報を入力してください: - **ID**: 自動生成されたアプリ ID をそのまま使用するか、カスタム ID を作成します。Stripe はこの ID を使用してアプリを識別します。[アプリ ID](https://docs.stripe.com/stripe-apps/reference/app-manifest.md#schema) はグローバルに一意である必要があります。アプリを初めてアップロードした後は変更できません。 - **表示名**: 表示名を入力します。これは、ダッシュボードに表示されるアプリの名前です。この名前は後で変更できます。 ### アプリディレクトリのファイル構造 | パス | 説明 | | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stripe-app.yaml` | アプリが Stripe プラットフォームとどのように統合されるかを説明する *アプリマニフェスト* (In a Stripe App, the app manifest is a stripe-app.json file in your app's root directory. It defines your app's ID, views, permissions, and other essential properties) ファイル。 | | `package.json` | オーケストレーションスクリプトを含むワークスペースのルートファイル (`pnpm -r build` など)。 | | `pnpm-workspace.yaml` | ワークスペースパッケージを宣言するファイル。 | | `tsconfig.json` | ルートの TypeScript 構成ファイル。 | | `tsconfig.base.json` | 共有 TypeScript 基本設定。 | | `vitest.config.mts` | `extensions/*` パッケージを対象とする Vitest の設定。 | | `eslint.config.mts` | ワークスペースの ESLint 設定。 | | `ui/` | UI 拡張機能ワークスペース。 | | `ui/package.json` | UI 拡張機能のメタデータと依存関係を含むファイル。 | | `ui/tsconfig.json` | UI 拡張機能の TypeScript 設定。 | | `ui/jest.config.*` | UI テストファイルを実行するための構成ファイル。 | | `ui/src/views/` | Stripe ダッシュボードで UI 要素を作成する TypeScript ファイルのためのディレクトリ。*ビュー* (A view is a React component that creates UI extensions in the Stripe Dashboard) とも呼ばれます。 | | `extensions/` | スクリプト拡張機能のディレクトリ。拡張機能ごとに 1 つのサブディレクトリがあります。 | | `extensions//` | 拡張機能 ID にちなんで名前が付けられたディレクトリ。 | | `extensions//package.json` | 拡張機能のメタデータと依存関係を含むファイル。 | | `extensions//src/` | 拡張機能のロジックのソースディレクトリ。 | | `custom_objects/` | カスタムオブジェクト定義のディレクトリ。 | | `shared/` | 共有内部ライブラリのオプションディレクトリ。 | | `.gitignore` | git に特定のファイルまたはフォルダーを無視するように伝えるファイル。 | #### 既存のアプリを移行 `stripe apps create` で作成した既存のアプリがある場合は、まず移行します: ```shell cd my-existing-app stripe apps migrate ``` 移行後、`stripe-app.json` と `stripe-app.yaml` の両方が表示されます。YAML ファイルがマニフェストファイルであり、現在は信頼できる唯一の情報源です。 ## 拡張機能を生成します アプリディレクトリからカスタムアクション拡張機能を生成します。このコマンドは、拡張ポイント ID、拡張機能の識別子、実装タイプ、表示名を引数として受け取ります。 ```shell stripe generate extension extend.workflows.custom_action send-email script --name "Send email" ``` これにより、TypeScript の設定、リンティング、テスト、スタータースクリプトの実装を含む完全なワークスペースが生成されます。 ``` your_app_directory/ ├── extensions/ │ └── send-email/ │ ├── src/ │ │ ├── index.ts # Script implementation │ │ ├── index.test.ts # Tests │ │ └── custom_input.schema.json # Custom input JSON Schema │ ├── generated/ │ │ ├── config.schema.json # Generated config schema │ │ └── config.ui.json # Generated config UI schema │ ├── eslint.config.mts │ ├── package.json │ ├── tsconfig.json │ └── tsconfig.build.json ├── custom-objects/ # Custom data types (optional) ├── ui/ # UI extensions (optional) ├── tools/ │ └── test.mts # Cross-workspace test runner ├── stripe-app.yaml ├── package.json ├── eslint.config.mts ├── vitest.config.mts └── pnpm-workspace.yaml ``` 拡張機能の `src/` にあるファイルを編集して、拡張機能を実装します。アプリのルートディレクトリから `pnpm build`、`pnpm lint`、`pnpm test` を実行し、すべてのワークスペースでコンパイル、型チェック、単体テストを実行します。 `generated/` 内のファイルは、`pnpm build` の実行時に、拡張機能の `Config` TypeScript インターフェイスから自動生成されます。これらのファイルは、拡張機能インストーラー用のダッシュボード設定 UI を制御します。これらのファイルを直接編集せず、代わりに `Config` インターフェイスを変更して再ビルドしてください。 `generate` コマンドは、カスタムデータ型を定義するための `custom-objects/` ワークスペースも作成します。詳細は、[カスタムオブジェクト](https://docs.stripe.com/custom-objects.md) を参照してください。カスタムオブジェクトを使用しない場合は、このワークスペースを空のままにできます。 `ui/`ワークスペースは、[UI 拡張機能](https://docs.stripe.com/stripe-apps.md)を構築するためのものです。アプリでスクリプト拡張機能のみを使用する場合は、空のままにできます。 ルートの `pnpm test` コマンドは `tools/test.mts` を実行します。このコマンドは、拡張機能には vitest、UI ビューには jest を使用して、すべてのワークスペースでテストを検出して実行します。 ## 必要な権限の付与 カスタムアクションには、`workflow_custom_action_run_write` 権限が必要です。これにより拡張機能はワークフロー実行データにアクセスできるようになります。これには、アクションの `execute` メソッドに渡せる前のステップの値も含まれます。アプリをインストールするアカウント管理者は、使用前にこの権限を承認する必要があります。 > #### まず拡張機能を生成する > > [拡張機能生成](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#generate-extension)後に、権限付与を実行します。`stripe generate extension` コマンドは、`stripe-app.yaml` の権限設定をリセットします。 CLI を使用して権限を付与します。 ```shell stripe apps grant permission workflow_custom_action_run_write \ "Runs custom actions in workflows and accesses data from earlier workflow steps" ``` これにより、お客様のアプリマニフェスト `stripe-app.yaml` 内の `declarations.stripe_api_access` に権限が追加されます。 ```yaml declarations: stripe_api_access: permissions: - permission: workflow_custom_action_run_write purpose: Runs custom actions in workflows and accesses data from earlier workflow steps ``` `declarations.stripe_api_access` セクションは、インストール時にアプリがリクエストする Stripe API 権限を制御します。アプリをインストールするアカウント管理者にはこれらの権限が表示され、同意する必要があります。 ## スクリプト実行時の動作 ほとんどの [TypeScript](https://www.typescriptlang.org/docs/handbook/intro.html#get-started) 機能は Stripe のランタイムで動作しますが、次のパターンは使用できません。ビルドとアップロードのステップで、これらは自動的に検出されます。 - `eval()` や `new Function()` などのコード評価 - `global` または `globalThis` を使用したグローバルスコープへのアクセス - `process.exit()` や `process.env` などのプロセス API - `console.log()`。スクリプトの実行ログを表示するには、[ワークベンチ](https://docs.stripe.com/workbench.md) を使用します。 - API キーやシークレットを埋め込まないでください。機密性の高い値は [Stripe secret store](https://docs.stripe.com/stripe-apps/store-secrets.md) で管理してください。 - `fetch()` などのネットワークアクセス API。[スクリプトからエンドポイントを呼び出す](https://docs.stripe.com/extensions/invoke-endpoints.md)には、`endpointFetch()` を使用します。 現在、Stripe はサードパーティーのライブラリを依存関係としてサポートしていません。 ## マニフェストの定義 `stripe-app.yaml` を開いて拡張を設定します。マニフェストには、拡張、そのメソッド、外部 API コール用のエンドポイント、スキーマファイルの場所が定義されます。 `generate` コマンドは、`endpoints` セクションのない最小限のマニフェストを作成します。スクリプトが `endpointFetch()` 経由で外部 API を呼び出す場合は、次に示すように `endpoints` セクションを手動で追加します。 ```yaml id: 'com.example.send-email-app' name: 'Send email' version: '0.0.1' declarations: distribution_type: private sandbox_install_compatible: true extensions: - id: "send_email" name: "Send email" interface_id: "extend.workflows.custom_action" version: "0.0.1" script: type: typescript content: "extensions/send_email/src/index.ts" endpoints: - id: "email_api" type: custom_http live: url: "https://api.emailservice.com" purpose: "Fetch templates and send email" auth: type: "header" header_name: "X-Api-Token" secret_name: "email_api_token" methods: execute: implementation_type: "script" custom_input: input_schema: type: "json_schema" content: "extensions/send_email/src/custom_input.schema.json" ui_schema: type: "jsonforms" content: "extensions/send_email/src/custom_input.ui.schema.json" get_form_state: implementation_type: "script" ``` マニフェストには、各拡張機能の下に 2 つの異なるスキーマセクションがあります。 - `methods.execute.custom_input` は、ユーザーがこのアクションステップを設定するときにワークフロービルダーに表示されるフィールドです。これらは `custom_input.schema.json` で定義します。 - `configuration` は、インストーラーが設定するアプリレベルの設定です。これは、拡張機能の `Config` TypeScript インターフェイスから、`gen-schemas` によって `pnpm build` 中に自動生成されます。生成されたファイルを直接編集しないでください。 ### エンドポイントと外向き通信 `endpoints` セクションでは、スクリプトが呼び出す外部サービスを定義します。各エンドポイントでは、以下を指定します。 - `id`: コードから参照する一意の識別子。 - `type`: スクリプトの外向き通信エンドポイントには `custom_http` を使用します。 - `url`: 外部サービスのベース URL。 - `purpose`: アプリのインストール時にユーザーに表示される、わかりやすい説明。 - `auth`: Stripe が送信リクエストに認証情報を自動的に挿入する方法。 `auth` の設定では、Stripe は [Secret Store](https://docs.stripe.com/stripe-apps/store-secrets.md) からシークレットを取得し、各リクエストに自動的に挿入します。この例では、Stripe は `X-Api-Token` ヘッダーに、シークレット名 `email_api_token` で保存されている値を追加します。 これらのエンドポイントは、グローバルな `endpointFetch` 関数を使用してスクリプトから呼び出します。スクリプトランタイムでは使用できない `fetch()` は使用しません。`endpointFetch` は Stripe ランタイムによって挿入されるグローバル関数であり、インポートする必要はありません。 ```ts const res = await endpointFetch({ endpoint: "email_api", // matches endpoint id in manifest path: "/v1/templates", // appended to the endpoint URL method: "GET", }); const data = JSON.parse(res.body!); ``` `endpointFetch` はランタイムでのみ使用できます (テストやローカルビルドでは使用できません)。`stripe-app.yaml` に一致する `endpoints` エントリが必要で、Stripe は Secret Store から認証情報を自動的に挿入します。 2xx 以外の応答では、`endpointFetch` がエラーをスローします。`endpointFetch` を使用するコードをテストするには、業務ロジックを純粋な関数に抽出し、それらを個別にテストします。ユニットテストで `endpointFetch` を呼び出すことはできません。 完全な API リファレンス、パラメーターテーブル、エラーコード、認証設定オプションについては、[スクリプトからのエンドポイント呼び出し](https://docs.stripe.com/extensions/invoke-endpoints.md)を参照してください。 ## スキーマの作成 アクションの入力スキーマと UI スキーマを定義します。これらは、ユーザーがワークフロービルダーで設定する内容と、実行時に `execute` メソッドが受け取るデータを制御します。 スキーマの完全なリファレンスとサポートされている型については、[アクションパラメーター](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md#action-parameters)をご覧ください。 \**入力スキーマ **(`custom_input.schema.json`): ```json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "audience_id": { "type": "string", "title": "Audience", "description": "Select a mailing list" }, "segment_id": { "type": "string", "title": "Segment", "description": "Target a specific segment within the audience (optional)" }, "template_id": { "type": "string", "title": "Email Template", "description": "Select an email template to use" }, "template_variables": { "type": "object", "title": "Template Variables", "description": "Fill in the merge fields for your selected template" } }, "required": ["audience_id", "template_id"], "additionalProperties": false } ``` \**UI スキーマ **(`custom_input.ui.schema.json`): generate コマンドは、`custom_input.schema.json` (入力スキーマ) を作成しますが、UI スキーマは作成しません。同じディレクトリに `custom_input.ui.schema.json` を手動で作成してから、マニフェストの `methods.execute.custom_input.ui_schema` で参照します。 ```json { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#/properties/audience_id", "options": { "format": "dynamic_select" } }, { "type": "Control", "scope": "#/properties/segment_id", "options": { "format": "dynamic_select" } }, { "type": "Control", "scope": "#/properties/template_id", "options": { "format": "dynamic_select" } }, { "type": "Control", "scope": "#/properties/template_variables", "options": { "format": "dynamic_schema" } } ] } ``` ## get_form_state の実装 `getFormState` メソッドは、ワークフロービルダーで動的なフォーム動作を実現します。フォームの初回読み込み時と、ユーザーがフィールド値を変更するたびに呼び出されます。 リクエスト形式とレスポンス形式の詳細については、[get_form_state を使用した動的フォーム](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md#dynamic-forms-with-get_form_state)をご覧ください。 静的オプションを返す最小限の実装から始めます。これはコンパイルでき、テストに合格し、動作するベースラインになります。 ```ts import type { Extend, Context } from "@stripe/extensibility-sdk/extensions"; // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface Config extends Record {} export default class SendEmailAction implements Extend.Workflows .CustomAction { async getFormState( request: Extend.Workflows.CustomAction.GetFormStateRequest, _config: Config, _context: Context, ) { return { values: request.values ?? {}, config: { audience_id: { options: [ { value: "aud_1", label: "Newsletter subscribers" }, { value: "aud_2", label: "Trial users" }, ], schema: {}, }, segment_id: { options: [], schema: {}, disabled: !request.values?.audience_id, }, template_id: { options: [ { value: "tmpl_1", label: "Welcome email" }, { value: "tmpl_2", label: "Follow-up email" }, ], schema: {}, }, template_variables: { options: [], schema: {}, hidden: !request.values?.template_id, }, }, }; } async execute( request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest, _config: Config, _context: Context, ) { // Implemented in the next section return {}; } } ``` これが機能したら、`endpointFetch()` を使用して、ハードコードされたオプションを外部サービスの動的データに置き換えます。次の例は、カスケードドロップダウン、依存フィールドのクリア、古い値の処理を含むパターン全体を示しています。 > 以下のヘルパー関数 (`fetchAudiences`、`fetchSegments`、`fetchTemplates`、`fetchTemplate`、`formatFieldName`) は、`endpointFetch()` を使用して、マニフェストで宣言されたエンドポイントを通じて外部メールサービスを呼び出します。このコードは、これらを実装するまでコンパイルされません。これらの呼び出し方法については、[スクリプトからのエンドポイント呼び出し](https://docs.stripe.com/extensions/invoke-endpoints.md)をご覧ください。 ```ts import type { Extend, Context } from "@stripe/extensibility-sdk/extensions"; // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface Config extends Record {} export default class SendEmailAction implements Extend.Workflows .CustomAction { async getFormState( request: Extend.Workflows.CustomAction.GetFormStateRequest, _config: Config, _context: Context, ) { const values = { ...(request.values ?? {}) }; const changedField = request.changedField; // Fetch audience options (always populated) const audienceOptions = await fetchAudiences(); // Fetch segments if an audience is selected let segmentOptions: Array<{ value: string; label: string }> = []; let segmentDisabled = true; if (values.audience_id) { segmentDisabled = false; segmentOptions = await fetchSegments(values.audience_id as string); } // Fetch templates and build dynamic schema const templateOptions = await fetchTemplates(); let templateSchema: Record = {}; let templateHidden = true; if (values.template_id) { templateHidden = false; const tmpl = await fetchTemplate(values.template_id as string); if (tmpl) { const properties: Record = {}; for (const tag of tmpl.mergeVars) { properties[tag] = { type: "string", title: formatFieldName(tag) }; } templateSchema = { type: "object", properties }; } } // Clear dependent values when parent changes let newValues = values; if (changedField === "audience_id") { newValues = { ...values, segment_id: undefined }; } if (changedField === "template_id" && templateSchema) { const validKeys = Object.keys( (templateSchema as { properties?: Record }) .properties ?? {}, ); const oldVars = (values.template_variables ?? {}) as Record< string, unknown >; const preserved: Record = {}; validKeys.forEach((key) => { if (oldVars[key] !== undefined) preserved[key] = oldVars[key]; }); newValues = { ...newValues, template_variables: preserved }; } // Check for stale saved values const audienceValid = !values.audience_id || audienceOptions.some((a) => a.value === values.audience_id); return { values: newValues, config: { audience_id: { options: audienceOptions, schema: {}, warning: audienceValid ? undefined : "Audience no longer exists.", }, segment_id: { options: segmentOptions, schema: {}, disabled: segmentDisabled, }, template_id: { options: templateOptions, schema: {}, }, template_variables: { options: [], schema: templateSchema, hidden: templateHidden, }, }, }; } async execute( request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest, _config: Config, _context: Context, ) { // Implemented in the next section return {}; } } ``` ## execute の実装 `execute` メソッドは、ワークフローの起動時に実行されます。ユーザーがフォームで設定した値を受け取ります。 ```ts async execute( request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest, _config: Config, _context: Context ) { const input = request.customInput ?? {}; // Use endpointFetch to call your external API await endpointFetch({ endpoint: "email_api", path: "/v1/send", method: "POST", body: JSON.stringify({ audienceId: input.audience_id, templateId: input.template_id, variables: input.template_variables, }), }); return {}; } ``` Stripe は、マニフェストの `endpoints` セクションにある `auth` 設定に基づいて、シークレット保存領域から API トークンをリクエストに自動的に挿入します。 ## タイムアウトと再試行 `execute` メソッドの各呼び出しには、**30 秒のタイムアウト**があります。スクリプトが 30 秒以内に結果を返さない場合、Stripe はその呼び出しを失敗として扱い、再試行します。 Stripe は失敗したアクションを自動的に再試行します。 - \**タイムアウト **(30 秒以内に応答がない場合) は再試行されます。 - スクリプトからスローされた**未処理のエラー**は再試行されます。 - スクリプトがエラーをキャッチして結果を返した場合、Stripe はそれを成功として扱い、再試行しません。 再試行は自動的に行われるため、可能な限り、`execute` の実装は冪等にする必要があります。同じワークフロー実行に対して、同じアクションが複数回実行されることがあります。 アクションに独自の非同期ジョブ処理は必要ありません。処理が 30 秒以内に収まる場合は、同期的に実行して結果を返してください。Stripe がアクションに伴うオーケストレーション、スケジュール設定、再試行を処理します。すぐに成功を返してバックグラウンド処理を開始すると、エラーをワークフローに報告できなくなります。ワークフローの観点では、そのアクションは成功したものとして扱われます。 ## シークレットストレージの設定 スクリプトにはサードパーティーの API トークンへのアクセスが必要です。アプリをインストールする各ユーザーは外部サービスにそれぞれ独自のアカウントを持っているため、以下を行う必要があります。 1. アプリのインストール後にユーザーが API トークンを入力できる設定 UI を構築します。 1. [Stripe Apps Secret Store API](https://docs.stripe.com/stripe-apps/store-secrets.md) を使用してトークンを保存します。 egress システムは、マニフェストの `endpoints` セクションにある `auth` 設定に基づいて、保存されたシークレットを API リクエストに自動的に挿入します。 実装の詳細については、以下を参照してください。 - [シークレットの保存に関するドキュメント](https://docs.stripe.com/stripe-apps/store-secrets.md) - [Secret Store のサンプルアプリ](https://github.com/stripe/stripe-apps/tree/main/examples/secret-store) ## 拡張機能のテスト `index.test.ts` でテストを追加または拡張します。以下のテストは、[get_form_state セクション](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#implement-get-form-state)のコンパイル可能な最小限の例に対応しています。 ```ts import { describe, it, expect } from "vitest"; import SendEmailAction from "./index.js"; describe("send email action", () => { const action = new SendEmailAction(); it("returns form state with audience options on initial load", async () => { const request = { values: {}, changedField: undefined, }; const result = await action.getFormState(request, {}, {} as any); expect(result.config.audience_id.options.length).toBeGreaterThan(0); expect(result.config.segment_id.disabled).toBe(true); expect(result.config.template_variables.hidden).toBe(true); }); it("enables segments when audience is selected", async () => { const request = { values: { audience_id: "aud_1" }, changedField: "audience_id", }; const result = await action.getFormState(request, {}, {} as any); expect(result.config.segment_id.disabled).toBe(false); }); it("executes without error", async () => { const request = { customInput: { audience_id: "aud_1", template_id: "tmpl_1", }, }; const result = await action.execute(request, {}, {} as any); expect(result).toBeDefined(); }); }); ``` アプリのルートディレクトリーからテストを実行します。 ```shell pnpm test ``` 開発中にウォッチモードを使用するには、拡張機能ディレクトリから `pnpm run dev` を実行します。プロジェクト全体をビルドして検証するには、**アプリのルートディレクトリ**から `pnpm build`、`pnpm test`、`pnpm lint` を実行します。 ## ビルドとアップロード アップロードする前に、アプリをビルド、lint、およびテストする。ビルドが失敗した場合は、[スクリプト実行時の動作](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#script-runtime-behavior)を参照する。 ```shell pnpm build pnpm lint pnpm test ``` Stripe CLI とダッシュボードで、想定しているアカウントにログインしていることを確認してください。サンドボックスの使用をお勧めします。 ```shell stripe login ``` 認証のために Stripe ダッシュボードが開きます。 アプリのソースコードを Stripe にアップロードします。 ```shell stripe apps upload ``` ``` You are about to upload your app to Testing Name: Acme Billing App ID: com.example.acme-billing-app Version: 0.0.1 ✔ Built files ✔ Packaged files for upload ✔ Uploaded 🌐 Stripe needs to process your files before this version can be installed. ``` アップロードを確認するには、 **Enter** をクリックする。(ダッシュボードで **Apps** > [Created apps](https://dashboard.stripe.com/test/apps/created) に移動し、アプリ名をクリックして **Versions** タブを開くこともできる。) レビューのステータスが **Ready to install** になったら、 **Install** をクリックして、アプリのインストール先を選択する。 インストール先は、アプリをアップロードした場所によって異なります。 - アプリを本番環境にアップロードした場合は、任意のサンドボックスにインストールできる。 - アプリをサンドボックスにアップロードした場合は、同じサンドボックスと本番環境にインストールできる。 - 別のサンドボックスにアプリをインストールするには、`stripe login` を使用してそのサンドボックスに切り替え、そこでアプリをアップロードしてインストールします。 レビューのステータスに問題が表示された場合は、問題の件数をクリックして詳細を確認します。ローカルビルドで問題を解決してから、アプリを再度アップロードします。必要に応じて、拡張機能またはアプリのバージョン番号を更新します。Stripe では [semantic versioning](https://semver.org/) を推奨しています。本番アカウントへのアプリのアップロードでは、Stripe による追加レビューが必要になる場合があります。より迅速に反復するには、サンドボックスを使用できます。 ## インストールしてワークフローに追加 [アプリをインストール](https://docs.stripe.com/stripe-apps/upload-install-app.md#install-in-live-mode)した後の手順 1. ダッシュボードで [Workflows](https://dashboard.stripe.com/workflows) に移動します。 1. 既存のワークフローを開くか、新しいワークフローを作成できます。 1. **Add action** をクリックし、アクションメニューの **Apps** でカスタムアクションを見つけます。 1. 動的フォームを使用したアクションパラメーターの設定 1. ワークフローを公開します。 カスタムアクションは、他の組み込み Stripe アクションと同様に、ワークフローの一部として実行されます。 ## サンドボックスでのテスト カスタムアクションを本番環境で使用する前に、[サンドボックス](https://docs.stripe.com/sandboxes.md)でテストすることをお勧めします。 1. サンドボックスアカウントに[アプリをインストール](https://docs.stripe.com/stripe-apps/upload-install-app.md)して、動作を確認できます。 1. サンドボックスのダッシュボードで、[Workflows](https://dashboard.stripe.com/workflows) に移動して、カスタムアクションを使用したテストワークフローを作成できます。 1. アクションを設定すれば、動的ドロップダウンに正しく項目が表示され、フィールドの状態が想定どおりに更新されることを確認できます。 1. ワークフローをトリガーすれば、アクションが正常に実行されることを確認できます。 1. [ワークベンチ](https://docs.stripe.com/workbench.md) を使用すると、入力引数や出力引数、エラーなど、スクリプト実行の詳細を確認できます。 サンドボックスでアクションが機能したら、本番アカウントにアプリをインストールして、同じ手順を繰り返して確認できます。 ## ランタイムエラーを処理する ほとんどの場合、エラーを検出してフォールバック動作を提供します。例外をスローすると、スクリプトに関連付けられたコード実行全体が停止するため、他の手段がない場合にのみスローします。 ## スクリプトの実行を監視する [ワークベンチ](https://docs.stripe.com/workbench.md)を使用すると、実行 ID、入力引数、出力引数、エラーの有無など、スクリプト実行の詳細を確認できます。詳細については、[拡張機能の実行詳細を表示する](https://docs.stripe.com/workbench/guides.md#view-extension-runs)をご覧ください。 ## 参照情報 - [カスタムアクションの仕組み](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md) - [リモート関数を使用したカスタムアクションの構築](https://docs.stripe.com/extensions/custom-actions/build-with-remote-function.md) - [拡張ポイント](https://docs.stripe.com/extensions/extension-points.md) - [スクリプトからのエンドポイント呼び出し](https://docs.stripe.com/extensions/invoke-endpoints.md) - [シークレットの保存](https://docs.stripe.com/stripe-apps/store-secrets.md)