From 674dde28ee1590cf3a4499ebb2101925e4d45b7e Mon Sep 17 00:00:00 2001 From: Luca Faggianelli Date: Tue, 15 Oct 2019 14:46:42 +0200 Subject: [PATCH] auto-creation of webhook via Stripe API --- .../credentials/StripeApi.credentials.ts | 21 ++++ .../nodes/Stripe/StripeTrigger.node.ts | 99 +++++++++++++++++++ packages/nodes-base/nodes/Stripe/helpers.ts | 48 +++++++++ packages/nodes-base/package.json | 2 + 4 files changed, 170 insertions(+) create mode 100644 packages/nodes-base/credentials/StripeApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Stripe/helpers.ts diff --git a/packages/nodes-base/credentials/StripeApi.credentials.ts b/packages/nodes-base/credentials/StripeApi.credentials.ts new file mode 100644 index 000000000..d59ecbfb7 --- /dev/null +++ b/packages/nodes-base/credentials/StripeApi.credentials.ts @@ -0,0 +1,21 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class StripeApi implements ICredentialType { + name = 'stripeApi'; + displayName = 'Stripe Api'; + properties = [ + // The credentials to get from user and save encrypted. + // Properties can be defined exactly in the same way + // as node properties. + { + displayName: 'Secret Key', + name: 'secretKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts index b27b3652a..cec9c141e 100644 --- a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts +++ b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts @@ -1,4 +1,5 @@ import { + IHookFunctions, IWebhookFunctions, } from 'n8n-core'; @@ -9,6 +10,9 @@ import { IWebhookResponseData, } from 'n8n-workflow'; +import { + stripeApiRequest, +} from './helpers'; export class StripeTrigger implements INodeType { description: INodeTypeDescription = { @@ -24,6 +28,12 @@ export class StripeTrigger implements INodeType { }, inputs: [], outputs: ['main'], + credentials: [ + { + name: 'stripeApi', + required: true, + } + ], webhooks: [ { name: 'default', @@ -791,6 +801,95 @@ export class StripeTrigger implements INodeType { ], }; + // @ts-ignore (because of request) + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + + if (webhookData.webhookId === undefined) { + // No webhook id is set so no webhook can exist + return false; + } + + // Webhook got created before so check if it still exists + const endpoint = `/webhook_endpoints/${webhookData.webhookId}`; + + try { + await stripeApiRequest.call(this, 'GET', endpoint, {}); + } catch (e) { + if (e.message.includes('[404]:')) { + // Webhook does not exist + delete webhookData.webhookId; + delete webhookData.webhookEvents; + delete webhookData.webhookSecret; + + return false; + } + + // Some error occured + throw e; + } + + // If it did not error then the webhook exists + return true; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + + const events = this.getNodeParameter('events', []); + + const endpoint = '/webhook_endpoints'; + + const body = { + url: webhookUrl, + enabled_events: events, + }; + + let responseData; + try { + responseData = await stripeApiRequest.call(this, 'POST', endpoint, body); + } catch (e) { + throw e; + } + + if (responseData.id === undefined || responseData.secret === undefined || responseData.status !== 'enabled') { + // Required data is missing so was not successful + throw new Error('Stripe webhook creation response did not contain the expected data.'); + } + + const webhookData = this.getWorkflowStaticData('node'); + webhookData.webhookId = responseData.id as string; + webhookData.webhookEvents = responseData.enabled_events as string[]; + webhookData.webhookSecret = responseData.secret as string; + + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + + if (webhookData.webhookId !== undefined) { + const endpoint = `/webhook_endpoints/${webhookData.webhookId}`; + const body = {}; + + try { + await stripeApiRequest.call(this, 'DELETE', endpoint, body); + } catch (e) { + return false; + } + + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + delete webhookData.webhookEvents; + delete webhookData.webhookSecret; + } + + return true; + }, + }, + }; + async webhook(this: IWebhookFunctions): Promise { const bodyData = this.getBodyData() as IDataObject; const req = this.getRequestObject(); diff --git a/packages/nodes-base/nodes/Stripe/helpers.ts b/packages/nodes-base/nodes/Stripe/helpers.ts new file mode 100644 index 000000000..9bf86f22a --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/helpers.ts @@ -0,0 +1,48 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +/** + * Make an API request to Stripe + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function stripeApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('stripeApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const options = { + method, + auth: { + user: credentials.secretKey as string, + }, + form: body, + qs: query, + uri: `https://api.stripe.com/v1${endpoint}`, + json: true + }; + + try { + return await this.helpers.request(options); + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Stripe credentials are not valid!'); + } + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + throw new Error(`Stripe error response [${error.statusCode}]: ${error.response.body.message}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 45c2414a8..7e30433c4 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -46,6 +46,7 @@ "dist/credentials/Redis.credentials.js", "dist/credentials/SlackApi.credentials.js", "dist/credentials/Smtp.credentials.js", + "dist/credentials/StripeApi.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", "dist/credentials/TwilioApi.credentials.js", @@ -93,6 +94,7 @@ "dist/nodes/Slack/Slack.node.js", "dist/nodes/SpreadsheetFile.node.js", "dist/nodes/Start.node.js", + "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Telegram/Telegram.node.js", "dist/nodes/Telegram/TelegramTrigger.node.js", "dist/nodes/Trello/Trello.node.js",