From 52acbe390a2a0e3c5b84e06d69d021af822092c7 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 22 Nov 2019 12:38:08 -0500 Subject: [PATCH 1/3] :tada: node-setup --- .../credentials/PaypalApi.credentials.ts | 24 +++++++++++ .../nodes-base/nodes/Paypal/Paypal.node.ts | 39 ++++++++++++++++++ packages/nodes-base/nodes/Paypal/paypal.png | Bin 0 -> 4979 bytes packages/nodes-base/package.json | 6 ++- 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/PaypalApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Paypal/Paypal.node.ts create mode 100644 packages/nodes-base/nodes/Paypal/paypal.png diff --git a/packages/nodes-base/credentials/PaypalApi.credentials.ts b/packages/nodes-base/credentials/PaypalApi.credentials.ts new file mode 100644 index 000000000..adfa66f5f --- /dev/null +++ b/packages/nodes-base/credentials/PaypalApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class PaypalApi implements ICredentialType { + name = 'paypalApi'; + displayName = 'Paypal API'; + properties = [ + { + displayName: 'Client ID', + name: 'clientId', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Secret', + name: 'secret', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Paypal/Paypal.node.ts b/packages/nodes-base/nodes/Paypal/Paypal.node.ts new file mode 100644 index 000000000..b8a14e122 --- /dev/null +++ b/packages/nodes-base/nodes/Paypal/Paypal.node.ts @@ -0,0 +1,39 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; + +export class Paypal implements INodeType { + description: INodeTypeDescription = { + displayName: 'Paypal', + name: 'Paypal', + icon: 'file:paypal.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Paypal API', + defaults: { + name: 'Paypal', + color: '#356ae6', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'paypalApi', + required: true, + } + ], + properties: [], + }; + + async execute(this: IExecuteFunctions): Promise { + return [this.helpers.returnJsonArray({})]; + } +} diff --git a/packages/nodes-base/nodes/Paypal/paypal.png b/packages/nodes-base/nodes/Paypal/paypal.png new file mode 100644 index 0000000000000000000000000000000000000000..01891251ef715748e96d060b917a35afc8a25300 GIT binary patch literal 4979 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1e3e1ceea..4f759d729 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -49,7 +49,8 @@ "dist/credentials/NextCloudApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/PipedriveApi.credentials.js", - "dist/credentials/Postgres.credentials.js", + "dist/credentials/Postgres.credentials.js", + "dist/credentials/Paypal.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", "dist/credentials/SlackApi.credentials.js", @@ -108,7 +109,8 @@ "dist/nodes/OpenWeatherMap.node.js", "dist/nodes/Pipedrive/Pipedrive.node.js", "dist/nodes/Pipedrive/PipedriveTrigger.node.js", - "dist/nodes/Postgres/Postgres.node.js", + "dist/nodes/Postgres/Postgres.node.js", + "dist/nodes/Paypal/Paypal.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js", From 08c95f989c0bea470c2f8738eb489c01ff1aa8c9 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 23 Nov 2019 19:36:47 -0500 Subject: [PATCH 2/3] :sparkles: added create a payout batch --- .../credentials/PaypalApi.credentials.ts | 20 +- .../ActiveCampaign/EcomOrderDescription.ts | 2 +- .../nodes/Paypal/GenericFunctions.ts | 110 ++++++ .../nodes/Paypal/PaymentDescription.ts | 340 ++++++++++++++++++ .../nodes/Paypal/PaymentInteface.ts | 38 ++ .../nodes-base/nodes/Paypal/Paypal.node.ts | 108 +++++- packages/nodes-base/package.json | 4 +- 7 files changed, 611 insertions(+), 11 deletions(-) create mode 100644 packages/nodes-base/nodes/Paypal/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Paypal/PaymentDescription.ts create mode 100644 packages/nodes-base/nodes/Paypal/PaymentInteface.ts diff --git a/packages/nodes-base/credentials/PaypalApi.credentials.ts b/packages/nodes-base/credentials/PaypalApi.credentials.ts index adfa66f5f..ae04c5ef8 100644 --- a/packages/nodes-base/credentials/PaypalApi.credentials.ts +++ b/packages/nodes-base/credentials/PaypalApi.credentials.ts @@ -4,9 +4,9 @@ import { } from 'n8n-workflow'; -export class PaypalApi implements ICredentialType { +export class PayPalApi implements ICredentialType { name = 'paypalApi'; - displayName = 'Paypal API'; + displayName = 'PayPal API'; properties = [ { displayName: 'Client ID', @@ -20,5 +20,21 @@ export class PaypalApi implements ICredentialType { type: 'string' as NodePropertyTypes, default: '', }, + { + displayName: 'Enviroment', + name: 'env', + type: 'options' as NodePropertyTypes, + default: 'live', + options: [ + { + name: 'Sanbox', + value: 'sanbox' + }, + { + name: 'Live', + value: 'live' + }, + ] + }, ]; } diff --git a/packages/nodes-base/nodes/ActiveCampaign/EcomOrderDescription.ts b/packages/nodes-base/nodes/ActiveCampaign/EcomOrderDescription.ts index 525fa0eb2..752b551ad 100644 --- a/packages/nodes-base/nodes/ActiveCampaign/EcomOrderDescription.ts +++ b/packages/nodes-base/nodes/ActiveCampaign/EcomOrderDescription.ts @@ -713,4 +713,4 @@ export const ecomOrderFields = [ default: 100, description: 'How many results to return.', }, -] as INodeProperties[]; \ No newline at end of file +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paypal/GenericFunctions.ts b/packages/nodes-base/nodes/Paypal/GenericFunctions.ts new file mode 100644 index 000000000..ef17b3ae0 --- /dev/null +++ b/packages/nodes-base/nodes/Paypal/GenericFunctions.ts @@ -0,0 +1,110 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions, + BINARY_ENCODING +} from 'n8n-core'; + +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 + const credentials = this.getCredentials('paypalApi'); + let tokenInfo; + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + // @ts-ignore + const env = { + 'sanbox': 'https://api.sandbox.paypal.com', + 'live': 'https://api.paypal.com' + }[credentials.env as string]; + + const data = new Buffer(`${credentials.clientId}:${credentials.secret}`).toString(BINARY_ENCODING); + let headerWithAuthentication = Object.assign({}, + { Authorization: `Basic ${data}`, 'Content-Type': 'application/x-www-form-urlencoded' }); + let options: OptionsWithUri = { + headers: headerWithAuthentication, + method, + qs: query, + uri: `${env}/v1/oauth2/token`, + body, + json: true + }; + try { + tokenInfo = 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; + } + headerWithAuthentication = Object.assign({ }, + { Authorization: `Bearer ${tokenInfo.access_token}`, 'Content-Type': 'application/json' }); + + options = { + headers: headerWithAuthentication, + method, + qs: query, + uri: uri || `${env}/v1${endpoint}`, + body, + json: true + }; + + 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; + } +} + + + +/** + * Make an API request to paginated intercom endpoint + * and return all results + */ +export async function intercomApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.per_page = 60; + + let uri: string | undefined; + + do { + responseData = await paypalApiRequest.call(this, endpoint, method, body, query, uri); + uri = responseData.pages.next; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData.pages !== undefined && + responseData.pages.next !== undefined && + responseData.pages.next !== null + ); + + return returnData; +} + + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = ''; + } + return result; +} diff --git a/packages/nodes-base/nodes/Paypal/PaymentDescription.ts b/packages/nodes-base/nodes/Paypal/PaymentDescription.ts new file mode 100644 index 000000000..c8cef397d --- /dev/null +++ b/packages/nodes-base/nodes/Paypal/PaymentDescription.ts @@ -0,0 +1,340 @@ +import { INodeProperties } from "n8n-workflow"; + +export const payoutOpeations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'payout', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a batch payout', + }, + { + name: 'Get', + value: 'get', + description: 'Show payout batch details', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const payoutFields = [ + +/* -------------------------------------------------------------------------- */ +/* payout:create */ +/* -------------------------------------------------------------------------- */ + + { + displayName: 'Sender Batch ID', + name: 'senderBatchId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + description: 'A sender-specified ID number. Tracks the payout in an accounting system.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'payout' + ], + operation: [ + 'create', + ] + }, + }, + }, + { + displayName: 'Items', + name: 'itemsUi', + placeholder: 'Add Item', + type: 'fixedCollection', + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false + ] + }, + }, + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'itemsValues', + displayName: 'Item', + values: [ + { + displayName: 'Recipient Type', + name: 'recipientType', + type: 'options', + options: [ + { + name: 'Phone', + value: 'phone', + description: 'The unencrypted phone number', + }, + { + name: 'Email', + value: 'email', + description: 'The unencrypted email. Value is a string of up to 127 single-byte characters.', + }, + { + name: 'PayPal ID', + value: 'paypalId', + description: 'The encrypted PayPal account number.', + }, + ], + default: 'email', + description: 'The ID type that identifies the recipient of the payment.', + }, + { + displayName: 'Receiver Value', + name: 'receiverValue', + type: 'string', + required: true, + default: '', + description: 'The receiver of the payment. Corresponds to the recipient_type value in the request. Max value of up to 127 single-byte characters.', + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + options: [ + { + name: 'Australian dollar', + value: 'AUD' + }, + { + name: 'Brazilian real', + value: 'BRL' + }, + { + name: 'Canadian dollar', + value: 'CAD' + }, + { + name: 'Czech koruna', + value: 'CZK' + }, + { + name: 'Danish krone', + value: 'DKK' + }, + { + name: 'Euro', + value: 'EUR' + }, + { + name: 'United States dollar', + value: 'USD' + } + ], + default: 'USD', + description: 'Currency', + }, + { + displayName: 'Amount', + name: 'amount', + type: 'string', + required: true, + default: '', + description: 'The value, which might be', + }, + { + displayName: 'Note', + name: 'note', + type: 'string', + required: false, + default: '', + description: 'The sender-specified note for notifications. Supports up to 4000 ASCII characters and 1000 non-ASCII characters.', + }, + { + displayName: 'Sender Item ID', + name: 'senderItemId', + type: 'string', + default: '', + description: 'The sender-specified ID number. Tracks the payout in an accounting system.', + }, + { + displayName: 'Recipient Wallet', + name: 'recipientWallet', + type: 'options', + options: [ + { + name: 'PayPal', + value: 'paypal', + description: 'PayPal Wallet', + }, + { + name: 'Venmo', + value: 'venmo', + description: 'Venmo Wallet', + }, + ], + default: 'paypal', + description: 'The recipient wallet', + }, + ] + }, + ], + }, + { + displayName: 'Items', + name: 'itemsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + description: 'An array of individual payout items.', + displayOptions: { + show: { + resource: [ + 'payout' + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ] + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Email Subject', + name: 'emailSubject', + type: 'string', + default: '', + description: 'The subject line for the email that PayPal sends when payment for a payout item completes. The subject line is the same for all recipients. Value is an alphanumeric string of up to 255 single-byte characters.', + }, + { + displayName: 'Email Message', + name: 'emailMessage', + type: 'string', + default: '', + description: 'The email message that PayPal sends when the payout item completes. The message is the same for all recipients.', + }, + { + displayName: 'Note', + name: 'note', + type: 'string', + default: '', + description: 'The payouts and item-level notes are concatenated in the email. The maximum combined length of the notes is 1000 characters.', + }, + ], + }, + +/* -------------------------------------------------------------------------- */ +/* payout:get */ +/* -------------------------------------------------------------------------- */ + +{ + displayName: 'Payout Batch Id', + name: 'payoutBatchId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'get', + ], + }, + }, + description: 'The ID of the payout for which to show details.', +}, +{ + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + required: false, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'get', + ], + }, + }, + description: 'If all results should be returned or only up to a given limit.', +}, +{ + displayName: 'Limit', + name: 'limit', + type: 'number', + typeOptions: { + maxValue: 1000, + minValue: 1 + }, + default: 100, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'get', + ], + returnAll: [ + false, + ], + }, + }, + description: 'If all results should be returned or only up to a given limit.', +}, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paypal/PaymentInteface.ts b/packages/nodes-base/nodes/Paypal/PaymentInteface.ts new file mode 100644 index 000000000..07db0ebd2 --- /dev/null +++ b/packages/nodes-base/nodes/Paypal/PaymentInteface.ts @@ -0,0 +1,38 @@ +import { IDataObject } from "n8n-workflow"; + +export enum RecipientType { + email = 'EMAIL', + phone = 'PHONE', + paypalId = 'PAYPAL_ID', +} + +export enum RecipientWallet { + paypal = 'PAYPAL', + venmo = 'VENMO', +} + +export interface IAmount { + currency?: string; + value?: string; +} + +export interface ISenderBatchHeader { + sender_batch_id?: string; + email_subject?: string; + email_message?: string; + note?: string; +} + +export interface IItem { + recipient_type?: RecipientType; + amount?: IAmount; + note?: string; + receiver?: string; + sender_item_id?: string; + recipient_wallet?: RecipientWallet; +} + +export interface IPaymentBatch { + sender_batch_header?: ISenderBatchHeader; + items?: IItem[]; +} diff --git a/packages/nodes-base/nodes/Paypal/Paypal.node.ts b/packages/nodes-base/nodes/Paypal/Paypal.node.ts index b8a14e122..fa29233f4 100644 --- a/packages/nodes-base/nodes/Paypal/Paypal.node.ts +++ b/packages/nodes-base/nodes/Paypal/Paypal.node.ts @@ -8,18 +8,33 @@ import { INodeExecutionData, INodeType, } from 'n8n-workflow'; +import { + payoutOpeations, + payoutFields, +} from './PaymentDescription'; +import { + IPaymentBatch, + ISenderBatchHeader, + IItem, IAmount, + RecipientType, + RecipientWallet, + } from './PaymentInteface'; +import { + validateJSON, + paypalApiRequest, + } from './GenericFunctions'; -export class Paypal implements INodeType { +export class PayPal implements INodeType { description: INodeTypeDescription = { - displayName: 'Paypal', - name: 'Paypal', + displayName: 'PayPal', + name: 'paypal', icon: 'file:paypal.png', group: ['output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Consume Paypal API', + description: 'Consume PayPal API', defaults: { - name: 'Paypal', + name: 'PayPal', color: '#356ae6', }, inputs: ['main'], @@ -30,10 +45,91 @@ export class Paypal implements INodeType { required: true, } ], - properties: [], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Payout', + value: 'payout', + description: 'Use the Payouts API to make payments to multiple PayPal or Venmo recipients. The Payouts API is a fast, convenient way to send commissions, rebates, rewards, and general disbursements. You can send up to 15,000 payments per call. If you integrated the Payouts API before September 1, 2017, you receive transaction reports through Mass Payments Reporting.', + }, + ], + default: 'payout', + description: 'Resource to consume.', + }, + ...payoutOpeations, + ...payoutFields, + ], }; async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let qs: IDataObject; + let responseData; + for (let i = 0; i < length; i++) { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'payout') { + if (operation === 'create') { + const body: IPaymentBatch = {}; + const header: ISenderBatchHeader = {}; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + const senderBatchId = this.getNodeParameter('senderBatchId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + header.sender_batch_id = senderBatchId; + if (additionalFields.emailSubject) { + header.email_subject = additionalFields.emailSubject as string; + } + if (additionalFields.emailMessage) { + header.email_message = additionalFields.emailMessage as string; + } + if (additionalFields.note) { + header.note = additionalFields.note as string; + } + body.sender_batch_header = header; + if (!jsonActive) { + const payoutItems: IItem[] = []; + const itemsValues = (this.getNodeParameter('itemsUi', i) as IDataObject).itemsValues as IDataObject[]; + if (itemsValues && itemsValues.length > 0) { + itemsValues.forEach( o => { + const payoutItem: IItem = {}; + const amount: IAmount = {}; + amount.currency = o.currency as string; + amount.value = o.receiverValue as string; + payoutItem.amount = amount; + payoutItem.note = o.note as string || ''; + payoutItem.receiver = o.receiver as string; + payoutItem.recipient_type = o.recipientType as RecipientType; + payoutItem.recipient_wallet = o.recipientWallet as RecipientWallet; + payoutItem.sender_item_id = o.senderItemId as string || ''; + payoutItems.push(payoutItem); + }); + body.items = payoutItems; + } else { + throw new Error('You must have at least one item.') + } + } else { + const itemsJson = validateJSON(this.getNodeParameter('itemsJson', i) as string); + body.items = itemsJson; + } + try { + responseData = await paypalApiRequest.call(this, '/payouts', 'POST', body); + } catch (err) { + throw new Error(`Paypal Error: ${JSON.stringify(err)}`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } return [this.helpers.returnJsonArray({})]; } } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4f759d729..da41902dd 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -50,7 +50,7 @@ "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", - "dist/credentials/Paypal.credentials.js", + "dist/credentials/PayPalApi.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", "dist/credentials/SlackApi.credentials.js", @@ -110,7 +110,7 @@ "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/PayPal.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js", From eaa33e0a8df439cb5563d05264b114dc9d286d18 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 26 Nov 2019 11:59:27 -0500 Subject: [PATCH 3/3] :sparkles: payout resource done --- .../nodes/Paypal/GenericFunctions.ts | 98 ++++++++++--------- .../nodes/Paypal/PaymentDescription.ts | 63 +++++++++++- .../nodes/Paypal/PaymentInteface.ts | 2 +- .../nodes-base/nodes/Paypal/Paypal.node.ts | 44 +++++++-- 4 files changed, 150 insertions(+), 57 deletions(-) diff --git a/packages/nodes-base/nodes/Paypal/GenericFunctions.ts b/packages/nodes-base/nodes/Paypal/GenericFunctions.ts index ef17b3ae0..c70b1de23 100644 --- a/packages/nodes-base/nodes/Paypal/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Paypal/GenericFunctions.ts @@ -14,49 +14,18 @@ import { 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 const credentials = this.getCredentials('paypalApi'); - let tokenInfo; - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - // @ts-ignore - const env = { - 'sanbox': 'https://api.sandbox.paypal.com', - 'live': 'https://api.paypal.com' - }[credentials.env as string]; - - const data = new Buffer(`${credentials.clientId}:${credentials.secret}`).toString(BINARY_ENCODING); - let headerWithAuthentication = Object.assign({}, - { Authorization: `Basic ${data}`, 'Content-Type': 'application/x-www-form-urlencoded' }); - let options: OptionsWithUri = { - headers: headerWithAuthentication, - method, - qs: query, - uri: `${env}/v1/oauth2/token`, - body, - json: true - }; - try { - tokenInfo = 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; - } - headerWithAuthentication = Object.assign({ }, + const env = getEnviroment(credentials!.env as string); + const tokenInfo = await getAccessToken.call(this); + const headerWithAuthentication = Object.assign({ }, { Authorization: `Bearer ${tokenInfo.access_token}`, 'Content-Type': 'application/json' }); - - options = { + const options = { headers: headerWithAuthentication, method, - qs: query, + qs: query || {}, uri: uri || `${env}/v1${endpoint}`, body, json: true }; - try { return await this.helpers.request!(options); } catch (error) { @@ -69,35 +38,74 @@ export async function paypalApiRequest(this: IHookFunctions | IExecuteFunctions } } +function getEnviroment(env: string): string { + // @ts-ignore + return { + 'sanbox': 'https://api.sandbox.paypal.com', + 'live': 'https://api.paypal.com' + }[env]; +} +async function getAccessToken(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('paypalApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const env = getEnviroment(credentials!.env as string); + const data = Buffer.from(`${credentials!.clientId}:${credentials!.secret}`).toString(BINARY_ENCODING); + const headerWithAuthentication = Object.assign({}, + { Authorization: `Basic ${data}`, 'Content-Type': 'application/x-www-form-urlencoded' }); + const options: OptionsWithUri = { + headers: headerWithAuthentication, + method: 'POST', + form: { + grant_type: 'client_credentials', + }, + uri: `${env}/v1/oauth2/token`, + json: true + }; + 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; + } +} /** - * Make an API request to paginated intercom endpoint + * Make an API request to paginated paypal endpoint * and return all results */ -export async function intercomApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function paypalApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; let responseData; - query.per_page = 60; - - let uri: string | undefined; + query!.page_size = 1000; do { responseData = await paypalApiRequest.call(this, endpoint, method, body, query, uri); - uri = responseData.pages.next; + uri = getNext(responseData.links); returnData.push.apply(returnData, responseData[propertyName]); } while ( - responseData.pages !== undefined && - responseData.pages.next !== undefined && - responseData.pages.next !== null + getNext(responseData.links) !== undefined ); return returnData; } +function getNext(links: IDataObject[]): string | undefined { + for (const link of links) { + if (link.rel === 'next') { + return link.href as string; + } + } + return undefined; +} export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any let result; diff --git a/packages/nodes-base/nodes/Paypal/PaymentDescription.ts b/packages/nodes-base/nodes/Paypal/PaymentDescription.ts index c8cef397d..00c994dde 100644 --- a/packages/nodes-base/nodes/Paypal/PaymentDescription.ts +++ b/packages/nodes-base/nodes/Paypal/PaymentDescription.ts @@ -21,8 +21,18 @@ export const payoutOpeations = [ { name: 'Get', value: 'get', + description: 'Show payout item details', + }, + { + name: 'Get All', + value: 'getAll', description: 'Show payout batch details', }, + { + name: 'Delete', + value: 'delete', + description: 'Cancels an unclaimed payout item, by ID.', + }, ], default: 'create', description: 'The operation to perform.', @@ -276,7 +286,7 @@ export const payoutFields = [ }, /* -------------------------------------------------------------------------- */ -/* payout:get */ +/* payout:getAll */ /* -------------------------------------------------------------------------- */ { @@ -290,7 +300,7 @@ export const payoutFields = [ 'payout', ], operation: [ - 'get', + 'getAll', ], }, }, @@ -300,14 +310,14 @@ export const payoutFields = [ displayName: 'Return All', name: 'returnAll', type: 'boolean', - required: false, + default: false, displayOptions: { show: { resource: [ 'payout', ], operation: [ - 'get', + 'getAll', ], }, }, @@ -328,7 +338,7 @@ export const payoutFields = [ 'payout', ], operation: [ - 'get', + 'getAll', ], returnAll: [ false, @@ -337,4 +347,47 @@ export const payoutFields = [ }, description: 'If all results should be returned or only up to a given limit.', }, + +/* -------------------------------------------------------------------------- */ +/* payout:get */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Payout Item Id', + name: 'payoutItemId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'get', + ], + }, + }, + description: 'The ID of the payout item for which to show details.', +}, + +/* -------------------------------------------------------------------------- */ +/* payout:delete */ +/* -------------------------------------------------------------------------- */ + +{ + displayName: 'Payout Item Id', + name: 'payoutItemId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'payout', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'The ID of the payout item to cancel.', +}, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paypal/PaymentInteface.ts b/packages/nodes-base/nodes/Paypal/PaymentInteface.ts index 07db0ebd2..f2d11710a 100644 --- a/packages/nodes-base/nodes/Paypal/PaymentInteface.ts +++ b/packages/nodes-base/nodes/Paypal/PaymentInteface.ts @@ -13,7 +13,7 @@ export enum RecipientWallet { export interface IAmount { currency?: string; - value?: string; + value?: number; } export interface ISenderBatchHeader { diff --git a/packages/nodes-base/nodes/Paypal/Paypal.node.ts b/packages/nodes-base/nodes/Paypal/Paypal.node.ts index fa29233f4..3f18788a9 100644 --- a/packages/nodes-base/nodes/Paypal/Paypal.node.ts +++ b/packages/nodes-base/nodes/Paypal/Paypal.node.ts @@ -22,6 +22,7 @@ import { import { validateJSON, paypalApiRequest, + paypalApiRequestAllItems } from './GenericFunctions'; export class PayPal implements INodeType { @@ -69,8 +70,8 @@ export class PayPal implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; const length = items.length as unknown as number; - let qs: IDataObject; let responseData; + let qs: IDataObject = {}; for (let i = 0; i < length; i++) { const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; @@ -100,10 +101,10 @@ export class PayPal implements INodeType { const payoutItem: IItem = {}; const amount: IAmount = {}; amount.currency = o.currency as string; - amount.value = o.receiverValue as string; + amount.value = parseFloat(o.amount as string); payoutItem.amount = amount; payoutItem.note = o.note as string || ''; - payoutItem.receiver = o.receiver as string; + payoutItem.receiver = o.receiverValue as string; payoutItem.recipient_type = o.recipientType as RecipientType; payoutItem.recipient_wallet = o.recipientWallet as RecipientWallet; payoutItem.sender_item_id = o.senderItemId as string || ''; @@ -111,14 +112,45 @@ export class PayPal implements INodeType { }); body.items = payoutItems; } else { - throw new Error('You must have at least one item.') + throw new Error('You must have at least one item.'); } } else { const itemsJson = validateJSON(this.getNodeParameter('itemsJson', i) as string); body.items = itemsJson; } try { - responseData = await paypalApiRequest.call(this, '/payouts', 'POST', body); + responseData = await paypalApiRequest.call(this, '/payments/payouts', 'POST', body); + } catch (err) { + throw new Error(`Paypal Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'get') { + const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; + try { + responseData = await paypalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}`, 'GET', {}, qs); + } catch (err) { + throw new Error(`Paypal Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'getAll') { + const payoutBatchId = this.getNodeParameter('payoutBatchId', i) as string; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + try { + if (returnAll === true) { + responseData = await paypalApiRequestAllItems.call(this, 'items', `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); + } else { + qs.page_size = this.getNodeParameter('limit', i) as number; + responseData = await paypalApiRequest.call(this,`/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); + responseData = responseData.items; + } + } catch (err) { + throw new Error(`Paypal Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'delete') { + const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; + try { + responseData = await paypalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}/cancel`, 'POST', {}, qs); } catch (err) { throw new Error(`Paypal Error: ${JSON.stringify(err)}`); } @@ -130,6 +162,6 @@ export class PayPal implements INodeType { returnData.push(responseData as IDataObject); } } - return [this.helpers.returnJsonArray({})]; + return [this.helpers.returnJsonArray(returnData)]; } }