From d71459535365a79c4b41640b5155026aa7277fdf Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 1 Dec 2019 10:38:54 -0500 Subject: [PATCH] :sparkles: Paypal trigger done --- .../nodes/Paypal/GenericFunctions.ts | 16 +- .../nodes/Paypal/PayPalTrigger.node.ts | 195 ++++++++++++++++++ .../nodes/Paypal/PaypalTriger.node.ts | 121 ----------- packages/nodes-base/package.json | 4 +- 4 files changed, 206 insertions(+), 130 deletions(-) create mode 100644 packages/nodes-base/nodes/Paypal/PayPalTrigger.node.ts delete mode 100644 packages/nodes-base/nodes/Paypal/PaypalTriger.node.ts diff --git a/packages/nodes-base/nodes/Paypal/GenericFunctions.ts b/packages/nodes-base/nodes/Paypal/GenericFunctions.ts index c70b1de23c..cee00be3a1 100644 --- a/packages/nodes-base/nodes/Paypal/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Paypal/GenericFunctions.ts @@ -5,6 +5,7 @@ import { IHookFunctions, ILoadOptionsFunctions, IExecuteSingleFunctions, + IWebhookFunctions, BINARY_ENCODING } from 'n8n-core'; @@ -12,7 +13,7 @@ import { IDataObject, } from 'n8n-workflow'; -export async function paypalApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any +export async function paypalApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('paypalApi'); const env = getEnviroment(credentials!.env as string); const tokenInfo = await getAccessToken.call(this); @@ -29,11 +30,6 @@ export async function paypalApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; - - if (errorMessage !== undefined) { - throw errorMessage; - } throw error.response.body; } } @@ -46,7 +42,7 @@ function getEnviroment(env: string): string { }[env]; } -async function getAccessToken(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any +async function getAccessToken(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('paypalApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); @@ -116,3 +112,9 @@ export function validateJSON(json: string | undefined): any { // tslint:disable- } return result; } + +export function upperFist(s: string): string { + return s.split('.').map(e => { + return e.toLowerCase().charAt(0).toUpperCase() + e.toLowerCase().slice(1); + }).join(' '); +} diff --git a/packages/nodes-base/nodes/Paypal/PayPalTrigger.node.ts b/packages/nodes-base/nodes/Paypal/PayPalTrigger.node.ts new file mode 100644 index 0000000000..6932f60fe8 --- /dev/null +++ b/packages/nodes-base/nodes/Paypal/PayPalTrigger.node.ts @@ -0,0 +1,195 @@ +import { + IHookFunctions, + IWebhookFunctions, + } from 'n8n-core'; + + import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, + ILoadOptionsFunctions, + INodePropertyOptions, + } from 'n8n-workflow'; + import { + paypalApiRequest, + upperFist + } from './GenericFunctions'; +import { queryResult } from 'pg-promise'; + + export class PayPalTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'PayPal Trigger', + name: 'PayPal', + icon: 'file:paypal.png', + group: ['trigger'], + version: 1, + description: 'Handle PayPal events via webhooks', + defaults: { + name: 'PayPal Trigger', + color: '#32325d', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'paypalApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + reponseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + required: true, + default: [], + description: 'The event to listen to.', + typeOptions: { + loadOptionsMethod: 'getEvents' + }, + options: [], + }, + ], + }; + + methods = { + loadOptions: { + // Get all the events types to display them to user so that he can + // select them easily + async getEvents(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let events; + try { + const endpoint = '/notifications/webhooks-event-types'; + events = await paypalApiRequest.call(this, endpoint, 'GET'); + } catch (err) { + throw new Error(`PayPal Error: ${err}`); + } + for (const event of events.event_types) { + const eventName = upperFist(event.name); + const eventId = event.name; + const eventDescription = event.description; + + returnData.push({ + name: eventName, + value: eventId, + description: eventDescription, + }); + } + return returnData; + }, + }, + }; + // @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; + } + const endpoint = `/notifications/webhooks/${webhookData.webhookId}`; + try { + await paypalApiRequest.call(this, endpoint, 'GET'); + } catch (err) { + if (err.response && err.response.name === 'INVALID_RESOURCE_ID') { + // Webhook does not exist + delete webhookData.webhookId; + return false; + } + throw new Error(`PayPal Error: ${err}`); + } + return true; + }, + + async create(this: IHookFunctions): Promise { + let webhook; + const webhookUrl = this.getNodeWebhookUrl('default'); + const events = this.getNodeParameter('events', []) as string[]; + const body = { + url: webhookUrl, + event_types: events.map(event => { + return { name: event }; + }), + }; + const endpoint = '/notifications/webhooks'; + try { + webhook = await paypalApiRequest.call(this, endpoint, 'POST', body); + } catch (e) { + throw e; + } + + if (webhook.id === undefined) { + return false; + } + const webhookData = this.getWorkflowStaticData('node'); + webhookData.webhookId = webhook.id as string; + return true; + }, + + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId !== undefined) { + const endpoint = `/notifications/webhooks/${webhookData.webhookId}`; + try { + await paypalApiRequest.call(this, endpoint, 'DELETE', {}); + } catch (e) { + return false; + } + delete webhookData.webhookId; + } + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + let webhook; + const webhookData = this.getWorkflowStaticData('node') as IDataObject; + const bodyData = this.getBodyData() as IDataObject; + const req = this.getRequestObject(); + const headerData = this.getHeaderData() as IDataObject; + const endpoint = '/notifications/verify-webhook-signature'; + + if (headerData['PAYPAL-AUTH-ALGO'] !== undefined + && headerData['PAYPAL-CERT-URL'] !== undefined + && headerData['PAYPAL-TRANSMISSION-ID'] !== undefined + && headerData['PAYPAL-TRANSMISSION-SIG'] !== undefined + && headerData['PAYPAL-TRANSMISSION-TIME'] !== undefined) { + const body = { + auth_algo: headerData['PAYPAL-AUTH-ALGO'], + cert_url: headerData['PAYPAL-CERT-URL'], + transmission_id: headerData['PAYPAL-TRANSMISSION-ID'], + transmission_sig: headerData['PAYPAL-TRANSMISSION-SIG'], + transmission_time: headerData['PAYPAL-TRANSMISSION-TIME'], + webhook_id: webhookData.webhookId, + webhook_event: bodyData, + }; + try { + webhook = await paypalApiRequest.call(this, endpoint, 'POST', body); + } catch (e) { + throw e; + } + if (webhook.verification_status !== 'SUCCESS') { + return {}; + } + } else { + return {}; + } + return { + workflowData: [ + this.helpers.returnJsonArray(req.body) + ], + }; + } + } diff --git a/packages/nodes-base/nodes/Paypal/PaypalTriger.node.ts b/packages/nodes-base/nodes/Paypal/PaypalTriger.node.ts deleted file mode 100644 index 07db20110c..0000000000 --- a/packages/nodes-base/nodes/Paypal/PaypalTriger.node.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - IHookFunctions, - IWebhookFunctions, - } from 'n8n-core'; - - import { - IDataObject, - INodeTypeDescription, - INodeType, - IWebhookResponseData, - ILoadOptionsFunctions, - INodePropertyOptions, - } from 'n8n-workflow'; - import { - paypalApiRequest, - } from './GenericFunctions'; - - export class PayPalTrigger implements INodeType { - description: INodeTypeDescription = { - displayName: 'PayPal Trigger', - name: 'PayPal', - icon: 'file:paypal.png', - group: ['trigger'], - version: 1, - description: 'Handle PayPal events via webhooks', - defaults: { - name: 'PayPal Trigger', - color: '#32325d', - }, - inputs: [], - outputs: ['main'], - credentials: [ - { - name: 'paypalApi', - required: true, - } - ], - webhooks: [ - { - name: 'default', - httpMethod: 'POST', - reponseMode: 'onReceived', - path: 'webhook', - }, - ], - properties: [ - { - displayName: 'Events', - name: 'events', - type: 'multiOptions', - required: true, - default: [], - description: 'The event to listen to.', - typeOptions: { - loadOptionsMethod: 'getEvents' - } - } - ], - }; - - methods = { - loadOptions: { - // Get all the events types to display them to user so that he can - // select them easily - async getEvents(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; - let events; - try { - events = await paypalApiRequest.call(this, '/webhooks-event-types', 'GET'); - } catch (err) { - throw new Error(`PayPal Error: ${err}`); - } - for (const event of events.event_types) { - const eventName = event.name; - const eventId = event.name; - const eventDescription = event.description; - - returnData.push({ - name: eventName, - value: eventId, - description: eventDescription, - }); - } - return returnData; - }, - }, - }; - - // @ts-ignore (because of request) - webhookMethods = { - default: { - async checkExists(this: IHookFunctions): Promise { - const webhookData = this.getWorkflowStaticData('node'); - - - return true; - }, - async delete(this: IHookFunctions): Promise { - const webhookData = this.getWorkflowStaticData('node'); - - - return true; - }, - }, - }; - - async webhook(this: IWebhookFunctions): Promise { - const bodyData = this.getBodyData() as IDataObject; - const req = this.getRequestObject(); - - const events = this.getNodeParameter('events', []) as string[]; - - const eventType = bodyData.type as string | undefined; - - return { - workflowData: [ - this.helpers.returnJsonArray(req.body) - ], - }; - } - } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 425bd2b592..cde489567f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -110,8 +110,8 @@ "dist/nodes/Pipedrive/Pipedrive.node.js", "dist/nodes/Pipedrive/PipedriveTrigger.node.js", "dist/nodes/Postgres/Postgres.node.js", - "dist/nodes/Paypal/Paypal.node.js", - "dist/nodes/Paypal/PaypalTriger.node.js", + "dist/nodes/Paypal/PayPal.node.js", + "dist/nodes/Paypal/PayPalTrigger.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js",