1
0
Fork 0
mirror of https://github.com/n8n-io/n8n.git synced 2025-03-05 20:50:17 -08:00

🔀 Merge branch 'feature/paypal-node' of https://github.com/RicardoE105/n8n into RicardoE105-feature/paypal-node

This commit is contained in:
Jan Oberhauser 2019-12-01 12:13:41 +01:00
commit beccf379a1
8 changed files with 761 additions and 3 deletions

View file

@ -0,0 +1,40 @@
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: '',
},
{
displayName: 'Enviroment',
name: 'env',
type: 'options' as NodePropertyTypes,
default: 'live',
options: [
{
name: 'Sanbox',
value: 'sanbox'
},
{
name: 'Live',
value: 'live'
},
]
},
];
}

View file

@ -713,4 +713,4 @@ export const ecomOrderFields = [
default: 100,
description: 'How many results to return.',
},
] as INodeProperties[];
] as INodeProperties[];

View file

@ -0,0 +1,118 @@
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<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('paypalApi');
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' });
const 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;
}
}
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<any> { // 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 paypal endpoint
* and return all results
*/
export async function paypalApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
query!.page_size = 1000;
do {
responseData = await paypalApiRequest.call(this, endpoint, method, body, query, uri);
uri = getNext(responseData.links);
returnData.push.apply(returnData, responseData[propertyName]);
} while (
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;
try {
result = JSON.parse(json!);
} catch (exception) {
result = '';
}
return result;
}

View file

@ -0,0 +1,393 @@
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 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.',
},
] 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:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Payout Batch Id',
name: 'payoutBatchId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: [
'payout',
],
operation: [
'getAll',
],
},
},
description: 'The ID of the payout for which to show details.',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'payout',
],
operation: [
'getAll',
],
},
},
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: [
'getAll',
],
returnAll: [
false,
],
},
},
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[];

View file

@ -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?: number;
}
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[];
}

View file

@ -0,0 +1,167 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeTypeDescription,
INodeExecutionData,
INodeType,
} from 'n8n-workflow';
import {
payoutOpeations,
payoutFields,
} from './PaymentDescription';
import {
IPaymentBatch,
ISenderBatchHeader,
IItem, IAmount,
RecipientType,
RecipientWallet,
} from './PaymentInteface';
import {
validateJSON,
paypalApiRequest,
paypalApiRequestAllItems
} from './GenericFunctions';
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: [
{
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<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length as unknown as number;
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;
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 = parseFloat(o.amount as string);
payoutItem.amount = amount;
payoutItem.note = o.note 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 || '';
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, '/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)}`);
}
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

Binary file not shown.

After

(image error) Size: 4.9 KiB

View file

@ -52,7 +52,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/PayPalApi.credentials.js",
"dist/credentials/Redis.credentials.js",
"dist/credentials/RocketchatApi.credentials.js",
"dist/credentials/SlackApi.credentials.js",
@ -114,7 +115,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",