# Erstellen einer benutzerdefinierten Aktion mit einem Script Mit TypeScript können Sie eine Workflow-Aktion in der von Stripe verwalteten Laufzeit erstellen. In diesem Leitfaden wird beschrieben, wie Sie mit einem Script eine benutzerdefinierte Aktion für [Stripe-Workflows](https://docs.stripe.com/workflows.md) erstellen. Scripts führen TypeScript in der von Stripe verwalteten Laufzeit aus. Stripe übernimmt die Speicherung geheimer Informationen und die Ausgangsauthentifizierung für externe API-Aufrufe. Im Beispiel in diesem Leitfaden wird die Aktion „E-Mail senden“ erstellt, die einen externen E-Mail-Dienst aufruft, wenn ein Workflow ausgelöst wird. Für die Aktion werden dynamische Formulare verwendet, damit Nutzer/innen während der Konfiguration eine Zielgruppe, eine Vorlage und ein Segment auswählen können. ### Sie möchten frühzeitigen Zugriff auf Erweiterungen? 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"}' ``` ## Bevor Sie beginnen Bevor Sie beginnen, lesen Sie erst einmal nach, [wie benutzerdefinierte Aktionen funktionieren](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md), um die Methoden, Schemata und das Laufzeitverhalten zu verstehen, die unabhängig vom Implementierungstyp für alle benutzerdefinierten Aktionen gelten. Außerdem sollten die folgenden Voraussetzungen erfüllt sein: - Sie müssen ein [Stripe-Konto](https://docs.stripe.com/get-started/account/set-up.md) und Zugriff auf die private Vorschau der Stripe-Erweiterungen haben. Wenn Sie keinen Zugriff darauf haben, können Sie sich [registrieren](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#signup), um frühzeitigen Zugriff zu erhalten. - Die [Stripe-CLI](https://docs.stripe.com/stripe-cli.md) wurde auf demselben Konto installiert und angemeldet. Falls noch nicht geschehen, [installieren und verbinden Sie sie](https://docs.stripe.com/stripe-cli/install.md). - Stellen Sie sicher, dass die Strip- CLI-Version 1.12.4 oder höher ist (`stripe version`). Wenn sie älter ist, [führen Sie ein Upgrade der CLI durch](https://docs.stripe.com/stripe-cli/upgrade.md). - `brew upgrade stripe/stripe-cli/stripe` - Sie haben [Sandbox](https://docs.stripe.com/sandboxes.md) in diesem Konto (empfohlen für die erstmalige Einrichtung). - [Node.js](https://nodejs.org)-Version 22 oder höher: - `node --version` - [pnpm](https://github.com/pnpm/pnpm/releases)-Version 10 oder höher: - `pnpm --version` - Das [Stripe Apps CLI-Plug-in](https://docs.stripe.com/stripe-apps/create-app.md#install-stripe-apps-cli): - `stripe plugin install apps` - Version 1.5.20 oder höher: - `stripe apps -v` - Das Generierungs-Plug-in: - `stripe plugin install generate` - Version 0.7.0 oder höher: - `stripe generate --version` - `stripe plugin upgrade generate` ### Von Erweiterungen verwendete npm-Pakete Ihr Erweiterungs-Workspace verwendet diese Pakete aus dem Geltungsbereich `@Stripe` mit [npm](https://www.npmjs.com/). Sie erhalten `extensibility-dev-tools`, `extensibility-eslint-plugin` und `extensibility-language-server`, wenn Sie `$stripe generate` ausführen und weitere, wenn Sie Erweiterungen und nutzerdefinierte Objekte erstellen. | Paket | Zweck | | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | [@stripe/extensibility-sdk](https://www.npmjs.com/package/@stripe/extensibility-sdk) | Kern-Laufzeitumgebung, Standardbibliothek und Erweiterungsschnittstellen | | [@stripe/extensibility-dev-tools](https://www.npmjs.com/package/@stripe/extensibility-dev-tools) | CLI-Tools zur Erstellung von Arbeitsbereichen und Schemata | | [@stripe/extensibility-eslint-plugin](https://www.npmjs.com/package/@stripe/extensibility-eslint-plugin) | Linting-Regeln für Erweiterungen | | [@stripe/extensibility-script-build-tools](https://www.npmjs.com/package/@stripe/extensibility-script-build-tools) | Plugin für Erweiterungsversand erstellen | | [@stripe/extensibility-custom-objects](https://www.npmjs.com/package/@stripe/extensibility-custom-objects) | Benutzerdefinierte Objekt-Dekoratoren und Laufzeitumgebung | | [@stripe/extensibility-custom-objects-tools](https://www.npmjs.com/package/@stripe/extensibility-custom-objects-tools) | Werkzeuge zur Herstellung kundenspezifischer Objekte | | [@stripe/extensibility-api-objects](https://www.npmjs.com/package/@stripe/extensibility-api-objects) | Definitionen von Stripe-API-Typen | | [@stripe/extensibility-test-helpers](https://www.npmjs.com/package/@stripe/extensibility-test-helpers) | Test-Dienstprogramme | | [@stripe/extensibility-language-server](https://www.npmjs.com/package/@stripe/extensibility-language-server) | IDE-Sprachserver | | [@stripe/extensibility-tool-utils](https://www.npmjs.com/package/@stripe/extensibility-tool-utils) | Gemeinsam genutzte interne Dienstprogramme | | [@stripe/extensibility-jsonschema-tools](https://www.npmjs.com/package/@stripe/extensibility-jsonschema-tools) | Tools zur Erstellung von JSON-Schemas | ## Eine App erstellen Erweiterungen werden innerhalb von [Stripe Apps](https://docs.stripe.com/stripe-apps.md) verpackt. Wenn Sie noch keine App haben, erstellen Sie eine, die Ihre Erweiterung enthält: ```shell stripe generate app helloworld cd helloworld ``` Dadurch wird die App mit dem Workspace-Layout erstellt, das für die Erweiterungsentwicklung benötigt wird. Folgen Sie den Aufforderungen und geben Sie die folgenden Informationen ein: - **ID**: Übernehmen Sie die automatisch generierte App-ID oder erstellen Sie eine nutzerdefinierte ID. Stripe identifiziert Ihre App anhand dieser ID. Ihre [App-ID](https://docs.stripe.com/stripe-apps/reference/app-manifest.md#schema) muss global eindeutig sein. Sie können diese nach dem ersten Upload Ihrer App nicht mehr ändern. - **Anzeigename**: Geben Sie einen Anzeigenamen ein. Dies ist der Name, den das Dashboard für Ihre App anzeigt. Sie können diesen Namen später jederzeit ändern. ### Verzeichnisdateistruktur der App | Pfad | Beschreibung | | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stripe-app.yaml` | Die *App-Manifest* (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)-Datei, die die Integration Ihrer App in die Stripe-Plattform beschreibt. | | `package.json` | Die Stammdatei des Arbeitsbereichs, die Orchestrierungsscripts enthält (zum Beispiel `pnpm -r build`). | | `pnpm-workspace.yaml` | Die Datei, die Arbeitsbereichspakete deklariert. | | `tsconfig.json` | Die Root-TypeScript-Konfigurationsdatei. | | `tsconfig.base.json` | Die gemeinsame TypeScript-Basiskonfiguration. | | `vitest.config.mts` | Die Vitest-Konfiguration für `extensions/*`-Pakete. | | `eslint.config.mts` | Die ESLint-Konfiguration für den Arbeitsbereich. | | `ui/` | Der Arbeitsbereich der Nutzeroberflächenerweiterung. | | `ui/package.json` | Die Datei, die Metadaten und Abhängigkeiten für die Nutzeroberflächenerweiterung enthält. | | `ui/tsconfig.json` | Die TypeScript-Konfiguration für die Erweiterung Nutzeroberfläche. | | `ui/jest.config.*` | Die Konfigurationsdatei zum Ausführen von Testdateien der Nutzeroberfläche. | | `ui/src/views/` | Verzeichnis für Ihre TypeScript-Dateien, die Elemente der Nutzeroberfläche im Stripe-Dashboard erstellen, auch als *'Ansichten* (A view is a React component that creates UI extensions in the Stripe Dashboard) bezeichnet. | | `extensions/` | Ein Verzeichnis für Scripterweiterungen mit einem Unterverzeichnis pro Erweiterung. | | `extensions//` | Ein Verzeichnis, das nach Ihrer Erweiterungs-ID benannt ist. | | `extensions//package.json` | Die Datei, die Metadaten und Abhängigkeiten für die Erweiterung enthält. | | `extensions//src/` | Das Quellverzeichnis für Ihre Erweiterungslogik. | | `custom_objects/` | Das Verzeichnis für benutzerdefinierte Objektdefinitionen. | | `shared/` | Ein optionales Verzeichnis für gemeinsam genutzte innere Bibliotheken. | | `.gitignore` | Die Datei, die Git anweist, bestimmte Dateien oder Ordner zu ignorieren. | #### Eine bestehende App migrieren Wenn Sie über eine bestehende App verfügen, die mit `stripe apps create` erstellt wurde, migrieren Sie sie zuerst: ```shell cd my-existing-app stripe apps migrate ``` Nach der Migration sehen Sie sowohl `stripe-app.json` als auch `stripe-app.yaml`. Die YAML-Datei ist die Manifestdatei und gilt nun als einzige verlässliche Quelle. ## Erweiterung generieren Erstellen Sie von Ihrem App-Verzeichnis aus die Erweiterung der nutzerdefinierten Aktion. Der Befehl verwendet die Erweiterungspunkt-ID, Ihre Erweiterungskennung, den Implementierungstyp und den Anzeigenamen: ```shell stripe generate extension extend.workflows.custom_action send-email script --name "Send email" ``` Dadurch wird ein vollständiger Arbeitsbereich mit TypeScript-Konfiguration, Linting, Tests und einer Anfangs-Script-Implementierung generiert: ``` 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 ``` Sie implementieren Ihre Erweiterung, indem Sie Dateien im `src/` der Erweiterung bearbeiten. Führen Sie `pnpm build`, `pnpm lint` und `pnpm test` aus dem App-Stammverzeichnis aus, um Einheitstests in allen Arbeitsbereichen zu kompilieren, einer Typprüfung zu unterziehen und auszuführen. Dateien in `generated/` werden automatisch über die `Config`-TypeScript-Nutzeroberfläche Ihrer Erweiterung generiert, wenn Sie `pnpm build` ausführen. Sie steuern die Nutzeroberfläche der Dashboard-Konfiguration für den Erweiterungs-Installer. Bearbeiten Sie diese Dateien nicht direkt, sondern ändern Sie Ihre `Config`-Nutzeroberfläche und führen Sie eine neue Erstellung durch. Mit dem Befehl `generate` wird auch ein `custom-objects/`-Arbeitsbereich zum Definieren von nutzerdefinierten Datentypen erstellt. Details finden Sie unter [Nutzerdefinierte Objekte](https://docs.stripe.com/custom-objects.md). Sie können diesen Arbeitsbereich leer lassen, wenn Sie keine nutzerdefinierte Objekte verwenden. Der Arbeitsbereich `ui/` dient zum Erstellen von [Erweiterungen der Nutzeroberfläche](https://docs.stripe.com/stripe-apps.md). Sie können ihn leer lassen, wenn Ihre App nur Scripterweiterungen verwendet. Der Root-Befehl `pnpm test` führt `tools/test.mts` aus, das Tests über alle Arbeitsbereiche hinweg erkennt und ausführt, wobei vitest für Erweiterungen und jest für Ansichten der Nutzeroberfläche verwendet wird. ## Erforderliche Berechtigung gewähren Benutzerdefinierte Aktionen erfordern die Berechtigung `workflow_custom_action_run_write`. Dadurch erhält Ihre Erweiterung Zugriff auf Workflow-Ausführungsdaten, einschließlich der Werte aus früheren Schritten, die Sie an die Methode `execute` der Aktion übergeben können. Konto-Admins, die Ihre App installieren, müssen diese Berechtigung vor der Verwendung der App annehmen. > #### Generieren Sie zuerst die Erweiterung > > Führen Sie die Berechtigungsgewährung nach dem [Generieren der Erweiterung](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#generate-extension) aus. Der Befehl `stripe generate extension` setzt die Berechtigungen in `stripe-app.yaml` zurück. Erteilen Sie die Berechtigung mit der CLI: ```shell stripe apps grant permission workflow_custom_action_run_write \ "Runs custom actions in workflows and accesses data from earlier workflow steps" ``` Dadurch wird die Berechtigung unter `declarations.tripe_api_access` in Ihrem App-Manifest `stripe-app.yaml` hinzugefügt: ```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 ``` Der Abschnitt `declarations.stripe_api_access` steuert, welche Berechtigungen Ihre App zum Installationszeitpunkt anfordert. Kontoadministratorinnen und -administratoren, die Ihre App installieren, sehen diese Berechtigungen und müssen sie akzeptieren. ## Script-Laufzeitverhalten Die meisten [TypeScript](https://www.typescriptlang.org/docs/handbook/intro.html#get-started)-Funktionen funktionieren zur Laufzeit von Stripe, aber die folgenden Muster sind nicht verfügbar. Die Schritte zum Erstellen und Hochladen fangen diese automatisch ab: - Codeauswertung wie `eval()` oder `new Function()` - Zugriff auf den globalen Bereich über `global` oder `globalThis` - Prozess-APIs wie `process.exit()` oder `process.env` - `console.log()`. Verwenden Sie [Workbench](https://docs.stripe.com/workbench.md), um die Logs der Script-Ausführung anzuzeigen. - Eingebettete API-Schlüssel oder Geheimnisse. Verwenden Sie zum Verwalten sensibler Werte den [Stripe-Geheimnisspeicher](https://docs.stripe.com/stripe-apps/store-secrets.md). - Netzwerkzugriffs-APIs wie `fetch()`. Verwenden Sie `endpointFetch()`, um [Endpoints aus einem Script aufzurufen](https://docs.stripe.com/extensions/invoke-endpoints.md). Stripe unterstützt derzeit keine Bibliotheken von Drittanbietern als Abhängigkeiten. ## Manifest definieren Öffnen Sie `stripe-app.yaml` und konfigurieren Sie Ihre Erweiterung. Im Manifest werden die Erweiterung, ihre Methoden, Endpoints für externe API-Aufrufe und Speicherorte für Schemadateien deklariert. Der Befehl `generate` erstellt ein minimales Manifest ohne einen `endpoints`-Bereich. Wenn Ihr Script externe APIs über `endpointFetch()` aufruft, fügen Sie den Bereich `endpoints` manuell hinzu, wie unten gezeigt: ```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" ``` Das Manifest hat unter jeder Erweiterung zwei separate Schemaabschnitte: - `methods.execute.custom_input` – diese Felder werden im Workflow-Builder angezeigt, wenn Nutzer/innen diesen Aktionsschritt konfigurieren. Sie definieren sie in `custom_input.schema.json`. - `configuration` – Konfiguration auf App-Ebene, die vom Installationsprogramm festgelegt wird. Sie wird automatisch über die `Config`-TypeScript-Schnittstelle Ihrer Erweiterung über `gen-schemas` beim `pnpm build` generiert. Bearbeiten Sie die generierten Dateien nicht direkt. ### Endpoints und Ausgang Im Abschnitt `endpoints` werden die externen Dienste deklariert, die Ihr Script aufruft. Jeder Endpoint enthält Folgendes: - `id`: Eine eindeutige Kennung, die Sie aus Ihrem Code beziehen. - `type`: `custom_http` für Script-Ausgangs-Endpoints. - `url`: Die Basis-URL des externen Dienstes. - `purpose`: Eine menschenlesbare Beschreibung, die Nutzer/innen während der App-Installation angezeigt wird. - `auth`: Wie Stripe Anmeldedaten in ausgehende Anfragen einfügt. Für die `auth`-Konfiguration ruft Stripe die geheimen Informationen aus dem [Secret Store](https://docs.stripe.com/stripe-apps/store-secrets.md) ab und fügt sie automatisch jeder Anfrage hinzu. In diesem Beispiel fügt Stripe den Header `X-Api-Token` mit dem unter dem Geheimnamen `email_api_token` gespeicherten Wert hinzu. Sie rufen diese Endpoints von Ihrem Script aus mit der globalen Funktion `endpointFetch` auf und nicht mit `fetch()`. Letztere ist in der Scriptlaufzeit nicht verfügbar. `endpointFetch` ist eine globale Funktion, die von der Stripe-Laufzeit injiziert wird. Sie importieren sie also nicht. ```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` ist nur zur Laufzeit verfügbar (nicht in Tests oder lokalen Builds). Die Funktion erfordert einen übereinstimmenden `endpoints`-Eintrag in `stripe-app.yaml` und Stripe injiziert automatisch Authentifizierungsdaten aus dem Secret Store. Bei Nicht-2xx-Antworten gibt `endpointFetch` einen Fehler aus. Um Code zu testen, der `endpointFetch` verwendet, extrahieren Sie Ihre Geschäftslogik in reine Funktionen und testen Sie diese separat. Sie können `endpointFetch` nicht in Komponententests aufrufen. Die vollständige API-Dokumentation, Parametertabelle, Fehlercodes und Konfigurationsoptionen für die Autorisierung finden Sie unter [Endpoints über ein Script aufrufen](https://docs.stripe.com/extensions/invoke-endpoints.md). ## Schemata erstellen Definieren Sie das Eingabeschema und die Nutzeroberfläche für Ihre Aktion. Diese steuern, was Nutzer/innen im Workflow-Builder konfigurieren und welche Daten Ihre `execute`-Methode zur Laufzeit erhält. Die vollständige Schemareferenz und die unterstützten Typen finden Sie unter [Aktionsparameter](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md#action-parameters). **Eingabeschema** (`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-Schema** (`custom_input.ui.schema.json`): Mit dem Befehl „generate“ wird `custom_input.schema.json` (Eingabeschema) erstellt, aber nicht das Nutzeroberflächenschema. Erstellen Sie `custom_input.ui.schema.json` manuell im selben Verzeichnis und verweisen Sie dann in Ihrem Manifest unter `methods.execute.custom_input.ui_schema` darauf. ```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“ implementieren Die Methode `getFormState` unterstützt das dynamische Formularverhalten im Workflow-Builder. Sie wird beim erstmaligen Laden des Formulars und immer dann aufgerufen, wenn Nutzer/innen einen Feldwert ändern. Die vollständige Anfrage und das Antwortformat finden Sie unter [dynamische Formulare mit „get_form_state“](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md#dynamic-forms-with-get_form_state). Beginnen Sie mit einer minimalen Implementierung, die statische Optionen zurückgibt. Dadurch werden Tests kompiliert und bestanden und Sie erhalten eine funktionierende Basis: ```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 {}; } } ``` Sobald das funktioniert, ersetzen Sie die fest codierten Optionen mithilfe von `endpointFetch()` durch dynamische Daten aus Ihrem externen Dienst. Das folgende Beispiel zeigt das vollständige Muster mit kaskadierenden Dropdowns, abhängigen Feldbereinigungen und der Verarbeitung veralteter Werte: > Die folgenden Hilfsfunktionen (`fetchAudiences`, `fetchSegments`, `fetchTemplates`, `fetchTemplate`, `formatFieldName`) verwenden `endpointFetch(),` um Ihren externen E-Mail-Dienst über den im Manifest angegebenen Endpoint aufzurufen. Informationen zum Durchführen dieser Aufrufe finden Sie unter [Endpoints über ein Script aufrufen](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“ implementieren Die Methode `execute` wird ausgeführt, wenn der Workflow ausgelöst wird. Sie empfängt die Werte, die Nutzer/innen im Formular konfiguriert haben. ```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 fügt den API-Token automatisch aus dem Secret Store in die Anfrage ein, und zwar entsprechend der `auth`-Konfiguration im Abschnitt `endpoints` Ihres Manifests. ## Timeouts und Wiederholungsversuche Für jeden Aufruf Ihrer `execute`-Methode gilt ein **Timeout von 30 Sekunden**. Wenn Ihr Script nicht innerhalb von 30 Sekunden reagiert, behandelt Stripe den Aufruf als fehlgeschlagen und wiederholt ihn. Stripe wiederholt fehlgeschlagene Aktionen automatisch: - Bei **Timeouts** (keine Antwort innerhalb von 30 Sekunden) erfolgt eine Wiederholung. - Wenn das Script einen **nicht behandelten Fehler** ausgibt, erfolgt eine Wiederholung. - Wenn Ihr Script einen Fehler erkennt und ein Ergebnis zurückgibt, behandelt Stripe dies als Erfolg und führt keine Wiederholung aus. Da Wiederholungsversuche automatisch erfolgen, sollte Ihre Implementierung von `execute` nach Möglichkeit idempotent sein. Eine Aktion kann für dieselbe Workflow-Ausführung mehrmals ausgeführt werden. Ihre Aktion erfordert keine eigene asynchrone Auftragsverarbeitung. Wenn Ihre Aufgabe nicht länger als 30 Sekunden dauert, führen Sie sie synchron aus und geben Sie das Ergebnis zurück. Stripe kümmert sich um die Orchestrierung und Planung sowie um Wiederholungen Ihrer Aktion. Wenn Sie das Ergebnis sofort zurückgeben und Hintergrundarbeiten starten, verlieren Sie die Fähigkeit, Fehler an den Workflow zurückzumelden. Aus Perspektive des Workflows war Ihre Aktion erfolgreich. ## Secret Store einrichten Ihr Script benötigt Zugriff auf API-Token von Drittanbietern. Da alle Nutzer/innen, die Ihre App installieren, ein eigenes Konto bei dem externen Dienst haben, müssen Sie Folgendes tun: 1. Eine Nutzeroberfläche für Einstellungen erstellen, auf der Nutzer/innen nach der Installation Ihrer App den API Token eingeben. 1. Das Token mit der [Stripe Apps Secret Store API](https://docs.stripe.com/stripe-apps/store-secrets.md) speichern. Das Ausgangssystem fügt die gespeicherten geheimen Informationen automatisch in Ihre API-Anfragen ein, und zwar basierend auf der `auth`-Konfiguration im Abschnitt `endpoints` Ihres Manifests. Details zur Implementierung finden Sie unter: - [Dokumentation zum Speichern von geheimen Informationen](https://docs.stripe.com/stripe-apps/store-secrets.md) - [Beispiel-App für Secret Store](https://github.com/stripe/stripe-apps/tree/main/examples/secret-store) ## Erweiterung testen Fügen Sie Tests in `index.test.ts` hinzu oder erweitern Sie sie dort. Die folgenden Tests arbeiten mit dem minimal kompilierbaren Beispiel aus dem [Abschnitt 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(); }); }); ``` Führen Sie Tests aus dem Stammverzeichnis der App aus: ```shell pnpm test ``` Führen Sie `pnpm run dev` aus dem Erweiterungsverzeichnis für den Watch-Modus während der Entwicklung aus. Führen Sie `pnpm build`, `pnpm test` und `pnpm lint` aus dem **Stammverzeichnis** aus, um das gesamte Projekt zu erstellen und zu validieren. ## Erstellen und hochladen Erstellen, linten und testen Sie Ihre App vor dem Hochladen. Wenn der Build fehlschlägt, lesen Sie [Script-Laufzeitverhalten](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#script-runtime-behavior). ```shell pnpm build pnpm lint pnpm test ``` Vergewissern Sie sich, dass Sie über die Stripe-CLI und das Dashboard beim richtigen Konto angemeldet sind. Wir empfehlen die Verwendung einer Sandbox: ```shell stripe login ``` Dadurch wird das Stripe Dashboard zur Authentifizierung geöffnet. Laden Sie den Quellcode Ihrer App bei Stripe hoch: ```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. ``` Um Ihren Upload anzuzeigen, klicken Sie auf **Eingabe**. (Sie können auch im Dashboard zu **Apps** > [Erstellte Apps](https://dashboard.stripe.com/test/apps/created) gehen und auf den Namen Ihrer App klicken und die Registerkarte **Versionen** öffnen.) Wenn der Prüfstatus **Installationsbereit** ist, klicken Sie auf **Installieren** und wählen Sie aus, wo die App installiert werden soll. Der Standort der Installation hängt davon ab, wo Sie die App hochgeladen haben: - Wenn Sie die App in den Live-Modus hochgeladen haben, können Sie sie in jeder Sandbox installieren. - Wenn Sie die App in eine Sandbox hochgeladen haben, können Sie sie in dieser Sandbox und im Live-Modus installieren. - So installieren Sie die App in einer anderen Sandbox: Wechseln Sie mit der `Stripe-Anmeldung` zur anderen Sandbox und laden Sie die App dort hoch und installieren Sie sie. Wenn im Prüfstatus Probleme anzeigt werden, klicken Sie auf die Anzahl der Probleme, um die Details anzuzeigen. Beheben Sie die Probleme in Ihrem lokalen Build und laden Sie dann Ihre App erneut hoch. Aktualisieren Sie die Erweiterung oder die App-Versionsnummer nach Bedarf. Stripe empfiehlt [eine semantische Versionierung](https://semver.org/). App-Uploads auf Live-Konten müssen möglicherweise von Stripe zusätzlich geprüft werden. Verwenden Sie für eine schnellere Iteration eine Sandbox. ## Installieren und zu einem Workflow hinzufügen Nach der [Installation der App](https://docs.stripe.com/stripe-apps/upload-install-app.md#install-in-live-mode): 1. Gehen Sie im Dashboard zu [Workflows](https://dashboard.stripe.com/workflows). 1. Öffnen Sie einen bestehenden Workflow oder erstellen Sie einen neuen. 1. Klicken Sie auf **Aktion hinzufügen** und suchen Sie Ihre benutzerdefinierte Aktion im Aktionsmenü unter **Apps**. 1. Konfigurieren Sie die Parameter der Aktion mit dem dynamischen Formular. 1. Veröffentlichen Sie den Workflow. Ihre benutzerdefinierte Aktion wird wie jede integrierte Stripe-Aktion als Teil des Workflows ausgeführt. ## In einer Sandbox testen Wir empfehlen, Ihre benutzerdefinierte Aktion in einer [Sandbox](https://docs.stripe.com/sandboxes.md) zu testen, bevor Sie sie im Live-Modus verwenden. 1. [Installieren Sie die App](https://docs.stripe.com/stripe-apps/upload-install-app.md) in einem Sandbox Konto. 1. Gehen Sie im Sandbox-Dashboard zu [Workflows](https://dashboard.stripe.com/workflows) und erstellen Sie einen Test-Workflow mit Ihrer benutzerdefinierten Aktion. 1. Konfigurieren Sie die Aktion: Vergewissern Sie sich, dass dynamische Dropdowns korrekt ausgefüllt und die Feldstatus wie erwartet aktualisiert werden. 1. Lösen Sie den Workflow aus und überprüfen Sie, ob Ihre Aktion erfolgreich ausgeführt wird. 1. Überprüfen Sie mit [Workbench](https://docs.stripe.com/workbench.md) die Ausführungsdetails des Scripts, einschließlich Eingabe- und Ausgabeargumente sowie etwaige Fehler. Wenn Ihre Aktion in der Sandbox funktioniert, können Sie die App in einem Live-Konto installieren und dieselben Schritte zur Verifizierung wiederholen. ## Umgang mit Laufzeitfehlern In den meisten Fällen sollten Sie Fehler abfangen und ein Fallback-Verhalten bereitstellen. Das Auslösen einer Ausnahme unterbricht die gesamte mit dem Script verbundene Codeausführung; lösen Sie daher nur dann eine Ausnahme aus, wenn keine andere Möglichkeit besteht. ## Scriptausführungen beobachten Verwenden Sie [Workbench](https://docs.stripe.com/workbench.md), um die Details von Scriptausführungen anzuzeigen, z. B. Ausführungs-ID, Eingabe- und Ausgabeargumente und ob Fehler aufgetreten sind. Weitere Informationen finden Sie unter [Ausführungsdetails von Erweiterungen anzeigen](https://docs.stripe.com/workbench/guides.md#view-extension-runs). ## Siehe auch - [So funktionieren benutzerdefinierte Aktionen](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md) - [Benutzerdefinierte Aktion mit Remote-Funktion erstellen](https://docs.stripe.com/extensions/custom-actions/build-with-remote-function.md) - [Erweiterungspunkte](https://docs.stripe.com/extensions/extension-points.md) - [Endpoints über ein Script aufrufen](https://docs.stripe.com/extensions/invoke-endpoints.md) - [Geheime Informationen speichern](https://docs.stripe.com/stripe-apps/store-secrets.md)