From 72102faed5e29aa96fb26217f54d329bf3d6117f Mon Sep 17 00:00:00 2001 From: Rupenieks <32895755+Rupenieks@users.noreply.github.com> Date: Tue, 25 Aug 2020 10:50:39 +0200 Subject: [PATCH] :sparkles: Add Paddle Integration (#726) * :construction: Resource descriptions * :construction: Node logic / Genericfunctions setup * :construction: Tests / changes * :construction: Changes - Added loadOptions to Payments / Coupon properties for easier item selection - Added exemptions for how data is returned due to inconsistent data return object from API - Other small fixes in main node * :construction: Simplified HTTPS error response * :construction: Added RAW Data options * :fire: Removed order resource - Cannot fetch order without a checkout ID, which can only be obtained via a custom implementation which involves a callback function when a user goes through their checkout process. * :zap: Improvement to Paddle-Node * :zap: Improvements * :zap: Added all currencies, discount grouped properties to coupon update Co-authored-by: ricardo --- .../credentials/PaddleApi.credentials.ts | 23 + .../nodes/ActiveCampaign/DealDescription.ts | 2 +- .../nodes/Paddle/CouponDescription.ts | 878 ++++++++++++++++++ .../nodes/Paddle/GenericFunctions.ts | 76 ++ .../nodes/Paddle/OrderDescription.ts | 52 ++ .../nodes-base/nodes/Paddle/Paddle.node.ts | 517 +++++++++++ .../nodes/Paddle/PaymentDescription.ts | 248 +++++ .../nodes/Paddle/PlanDescription.ts | 98 ++ .../nodes/Paddle/ProductDescription.ts | 71 ++ .../nodes/Paddle/UserDescription.ts | 176 ++++ packages/nodes-base/nodes/Paddle/paddle.png | Bin 0 -> 3076 bytes packages/nodes-base/package.json | 2 + 12 files changed, 2142 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/credentials/PaddleApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Paddle/CouponDescription.ts create mode 100644 packages/nodes-base/nodes/Paddle/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Paddle/OrderDescription.ts create mode 100644 packages/nodes-base/nodes/Paddle/Paddle.node.ts create mode 100644 packages/nodes-base/nodes/Paddle/PaymentDescription.ts create mode 100644 packages/nodes-base/nodes/Paddle/PlanDescription.ts create mode 100644 packages/nodes-base/nodes/Paddle/ProductDescription.ts create mode 100644 packages/nodes-base/nodes/Paddle/UserDescription.ts create mode 100644 packages/nodes-base/nodes/Paddle/paddle.png diff --git a/packages/nodes-base/credentials/PaddleApi.credentials.ts b/packages/nodes-base/credentials/PaddleApi.credentials.ts new file mode 100644 index 0000000000..143a24b3b8 --- /dev/null +++ b/packages/nodes-base/credentials/PaddleApi.credentials.ts @@ -0,0 +1,23 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class PaddleApi implements ICredentialType { + name = 'paddleApi'; + displayName = 'Paddle API'; + properties = [ + { + displayName: 'Vendor Auth Code', + name: 'vendorAuthCode', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Vendor ID', + name: 'vendorId', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/ActiveCampaign/DealDescription.ts b/packages/nodes-base/nodes/ActiveCampaign/DealDescription.ts index 940157e5f0..9d61c43395 100644 --- a/packages/nodes-base/nodes/ActiveCampaign/DealDescription.ts +++ b/packages/nodes-base/nodes/ActiveCampaign/DealDescription.ts @@ -526,4 +526,4 @@ export const dealFields = [ description: 'The content of the deal note', }, -] as INodeProperties[]; \ No newline at end of file +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/CouponDescription.ts b/packages/nodes-base/nodes/Paddle/CouponDescription.ts new file mode 100644 index 0000000000..69ffc4e6e2 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/CouponDescription.ts @@ -0,0 +1,878 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const couponOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a coupon.', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all coupons.', + }, + { + name: 'Update', + value: 'update', + description: 'Update a coupon.', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const couponFields = [ +/* -------------------------------------------------------------------------- */ +/* coupon:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Coupon Type', + name: 'couponType', + type: 'options', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `create` + ], + jsonParameters: [ + false + ] + }, + }, + default: 'checkout', + description: 'Either product (valid for specified products or subscription plans) or checkout (valid for any checkout).', + options: [ + { + name: 'Checkout', + value: 'checkout' + }, + { + name: 'Product', + value: 'product' + }, + ] + }, + { + displayName: 'Product IDs', + name: 'productIds', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getProducts', + }, + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `create` + ], + couponType: [ + 'product', + ], + jsonParameters: [ + false + ] + }, + }, + default: '', + description: 'Comma-separated list of product IDs. Required if coupon_type is product.', + required: true, + }, + { + displayName: 'Discount Type', + name: 'discountType', + type: 'options', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `create` + ], + jsonParameters: [ + false + ] + }, + }, + default: 'flat', + description: 'Either flat or percentage.', + options: [ + { + name: 'Flat', + value: 'flat' + }, + { + name: 'Percentage', + value: 'percentage' + }, + ] + }, + { + displayName: 'Discount Amount Currency', + name: 'discountAmount', + type: 'number', + default: '', + description: 'Discount amount in currency.', + typeOptions: { + minValue: 1 + }, + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `create` + ], + discountType: [ + 'flat', + ], + jsonParameters: [ + false + ] + }, + }, + }, + { + displayName: 'Discount Amount %', + name: 'discountAmount', + type: 'number', + default: '', + description: 'Discount amount in percentage.', + typeOptions: { + minValue: 1, + maxValue: 100 + }, + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `create` + ], + discountType: [ + 'percentage', + ], + jsonParameters: [ + false + ] + }, + }, + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + default: 'EUR', + description: 'The currency must match the balance currency specified in your account.', + options: [ + { + name: 'ARS', + value: 'ARS' + }, + { + name: 'AUD', + value: 'AUD' + }, + { + name: 'BRL', + value: 'BRL' + }, + { + name: 'GBP', + value: 'GBP' + }, + { + name: 'CAD', + value: 'CAD' + }, + { + name: 'CNY', + value: 'CNY' + }, + { + name: 'CZK', + value: 'CZK' + }, + { + name: 'DKK', + value: 'DKK' + }, + { + name: 'EUR', + value: 'EUR' + }, + { + name: 'HKD', + value: 'HKD' + }, + { + name: 'HUF', + value: 'HUF' + }, + { + name: 'INR', + value: 'INR' + }, + { + name: 'JPY', + value: 'JPY' + }, + { + name: 'MXN', + value: 'MXN' + }, + { + name: 'TWD', + value: 'TWD' + }, + { + name: 'NZD', + value: 'NZD' + }, + { + name: 'NOK', + value: 'NOK' + }, + { + name: 'PLN', + value: 'PLN' + }, + { + name: 'RUB', + value: 'RUB' + }, + { + name: 'SGD', + value: 'SGD' + }, + { + name: 'ZAR', + value: 'ZAR' + }, + { + name: 'KRW', + value: 'KRW' + }, + { + name: 'SEK', + value: 'SEK' + }, + { + name: 'CHF', + value: 'CHF' + }, + { + name: 'THB', + value: 'THB' + }, + { + name: 'CHF', + value: 'CHF' + }, + { + name: 'USD', + value: 'USD' + }, + ], + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `create` + ], + discountType: [ + 'flat', + ], + jsonParameters: [ + false + ] + }, + }, + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + description: `Attributes in JSON form.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false + ] + }, + }, + default: {}, + options: [ + { + displayName: 'Allowed Uses', + name: 'allowedUses', + type: 'number', + default: 1, + description: 'Number of times a coupon can be used in a checkout. This will be set to 999,999 by default, if not specified.', + }, + { + displayName: 'Coupon Code', + name: 'couponCode', + type: 'string', + default: '', + description: 'Will be randomly generated if not specified.', + }, + { + displayName: 'Coupon Prefix', + name: 'couponPrefix', + type: 'string', + default: '', + description: 'Prefix for generated codes. Not valid if coupon_code is specified.', + }, + { + displayName: 'Expires', + name: 'expires', + type: 'dateTime', + default: '', + description: 'The coupon will expire on the date at 00:00:00 UTC.', + }, + { + displayName: 'Group', + name: 'group', + type: 'string', + typeOptions: { + minValue: 1, + maxValue: 50 + }, + default: '', + description: 'The name of the coupon group this coupon should be assigned to.', + }, + { + displayName: 'Recurring', + name: 'recurring', + type: 'boolean', + default: false, + description: 'If the coupon is used on subscription products, this indicates whether the discount should apply to recurring payments after the initial purchase.', + }, + { + displayName: 'Number of Coupons', + name: 'numberOfCoupons', + type: 'number', + default: 1, + description: 'Number of coupons to generate. Not valid if coupon_code is specified.', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'Description of the coupon. This will be displayed in the Seller Dashboard.', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* coupon:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Product ID', + name: 'productId', + type: 'string', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `getAll` + ] + }, + }, + default: '', + required: true, + description: 'The specific product/subscription ID.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'coupon', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'coupon', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +/* -------------------------------------------------------------------------- */ +/* coupon:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Update by', + name: 'updateBy', + type: 'options', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + `update` + ], + jsonParameters: [ + false, + ], + }, + }, + default: 'couponCode', + description: 'Either flat or percentage.', + options: [ + { + name: 'Coupon Code', + value: 'couponCode' + }, + { + name: 'Group', + value: 'group' + }, + ] + }, + { + displayName: 'Coupon Code', + name: 'couponCode', + type: 'string', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'update' + ], + updateBy: [ + 'couponCode' + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'Identify the coupon to update', + }, + { + displayName: 'Group', + name: 'group', + type: 'string', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'update' + ], + updateBy: [ + 'group' + ], + jsonParameters: [ + false, + ], + }, + }, + default: '', + description: 'The name of the group of coupons you want to update.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + description: `Attributes in JSON form.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'update', + ], + jsonParameters: [ + false + ] + }, + }, + default: {}, + options: [ + { + displayName: 'Allowed Uses', + name: 'allowedUses', + type: 'number', + default: 1, + description: 'Number of times a coupon can be used in a checkout. This will be set to 999,999 by default, if not specified.', + }, + { + displayName: 'Discount', + name: 'discount', + type: 'fixedCollection', + default: 'discountProperties', + options: [ + { + displayName: 'Discount Properties', + name: 'discountProperties', + values: [ + { + displayName: 'Currency', + name: 'currency', + type: 'options', + default: 'EUR', + description: 'The currency must match the balance currency specified in your account.', + displayOptions: { + show: { + discountType: [ + 'flat', + ], + }, + }, + options: [ + { + name: 'ARS', + value: 'ARS' + }, + { + name: 'AUD', + value: 'AUD' + }, + { + name: 'BRL', + value: 'BRL' + }, + { + name: 'GBP', + value: 'GBP' + }, + { + name: 'CAD', + value: 'CAD' + }, + { + name: 'CNY', + value: 'CNY' + }, + { + name: 'CZK', + value: 'CZK' + }, + { + name: 'DKK', + value: 'DKK' + }, + { + name: 'EUR', + value: 'EUR' + }, + { + name: 'HKD', + value: 'HKD' + }, + { + name: 'HUF', + value: 'HUF' + }, + { + name: 'INR', + value: 'INR' + }, + { + name: 'JPY', + value: 'JPY' + }, + { + name: 'MXN', + value: 'MXN' + }, + { + name: 'TWD', + value: 'TWD' + }, + { + name: 'NZD', + value: 'NZD' + }, + { + name: 'NOK', + value: 'NOK' + }, + { + name: 'PLN', + value: 'PLN' + }, + { + name: 'RUB', + value: 'RUB' + }, + { + name: 'SGD', + value: 'SGD' + }, + { + name: 'ZAR', + value: 'ZAR' + }, + { + name: 'KRW', + value: 'KRW' + }, + { + name: 'SEK', + value: 'SEK' + }, + { + name: 'CHF', + value: 'CHF' + }, + { + name: 'THB', + value: 'THB' + }, + { + name: 'CHF', + value: 'CHF' + }, + { + name: 'USD', + value: 'USD' + }, + ], + }, + { + displayName: 'Discount Type', + name: 'discountType', + type: 'options', + default: 'flat', + description: 'Either flat or percentage.', + options: [ + { + name: 'Flat', + value: 'flat' + }, + { + name: 'Percentage', + value: 'percentage' + }, + ] + }, + { + displayName: 'Discount Amount Currency', + name: 'discountAmount', + type: 'number', + default: '', + description: 'Discount amount.', + displayOptions: { + show: { + discountType: [ + 'flat', + ], + }, + }, + typeOptions: { + minValue: 0 + }, + }, + { + displayName: 'Discount Amount Percentage', + name: 'discountAmount', + type: 'number', + default: '', + description: 'Discount amount.', + displayOptions: { + show: { + discountType: [ + 'percentage', + ], + }, + }, + typeOptions: { + minValue: 0, + maxValue: 100 + }, + }, + ], + }, + ], + }, + { + displayName: 'Expires', + name: 'expires', + type: 'dateTime', + default: '', + description: 'The coupon will expire on the date at 00:00:00 UTC.', + }, + { + displayName: 'New Coupon Code', + name: 'newCouponCode', + type: 'string', + default: '', + description: 'New code to rename the coupon to.', + }, + { + displayName: 'New Group Name', + name: 'newGroup', + type: 'string', + typeOptions: { + minValue: 1, + maxValue: 50 + }, + default: '', + description: 'New group name to move coupon to.', + }, + { + displayName: 'Product IDs', + name: 'productIds', + type: 'string', + default: '', + description: 'Comma-separated list of products e.g. 499531,1234,123546. If blank then remove associated products.', + }, + { + displayName: 'Recurring', + name: 'recurring', + type: 'boolean', + default: false, + description: 'If the coupon is used on subscription products, this indicates whether the discount should apply to recurring payments after the initial purchase.', + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/GenericFunctions.ts b/packages/nodes-base/nodes/Paddle/GenericFunctions.ts new file mode 100644 index 0000000000..caecdddeee --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/GenericFunctions.ts @@ -0,0 +1,76 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function paddleApiRequest(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('paddleApi'); + + if (credentials === undefined) { + throw new Error('Could not retrieve credentials!'); + } + + const options : OptionsWithUri = { + method, + headers: { + 'content-type': 'application/json' + }, + uri: `https://vendors.paddle.com/api${endpoint}` , + body, + json: true + }; + + body['vendor_id'] = credentials.vendorId; + body['vendor_auth_code'] = credentials.vendorAuthCode; + try { + const response = await this.helpers.request!(options); + + if (!response.success) { + throw new Error(`Code: ${response.error.code}. Message: ${response.error.message}`); + } + + return response; + } catch (error) { + throw new Error(`ERROR: Code: ${error.code}. Message: ${error.message}`); + } +} + +export async function paddleApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + body.results_per_page = 200; + body.page = 1; + + do { + responseData = await paddleApiRequest.call(this, endpoint, method, body, query); + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData[propertyName].length !== 0 + ); + + return returnData; +} + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} diff --git a/packages/nodes-base/nodes/Paddle/OrderDescription.ts b/packages/nodes-base/nodes/Paddle/OrderDescription.ts new file mode 100644 index 0000000000..367082a4b3 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/OrderDescription.ts @@ -0,0 +1,52 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const orderOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'order', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get an order', + } + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const orderFields = [ + +/* -------------------------------------------------------------------------- */ +/* order:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Checkout ID', + name: 'checkoutId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'order', + ], + operation: [ + 'get', + ], + }, + }, + description: 'The identifier of the buyer’s checkout.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/Paddle.node.ts b/packages/nodes-base/nodes/Paddle/Paddle.node.ts new file mode 100644 index 0000000000..4179762307 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/Paddle.node.ts @@ -0,0 +1,517 @@ +import { + IExecuteFunctions +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, + ILoadOptionsFunctions, + INodePropertyOptions +} from 'n8n-workflow'; + +import { + couponFields, + couponOperations, +} from './CouponDescription'; + +import { + paddleApiRequest, + paddleApiRequestAllItems, + validateJSON +} from './GenericFunctions'; + +import { + paymentFields, + paymentOperations, +} from './PaymentDescription'; + +import { + planFields, + planOperations, +} from './PlanDescription'; + +import { + productFields, + productOperations, +} from './ProductDescription'; + +import { + userFields, + userOperations, +} from './UserDescription'; + +// import { +// orderOperations, +// orderFields, +// } from './OrderDescription'; + +import * as moment from 'moment'; + +export class Paddle implements INodeType { + description: INodeTypeDescription = { + displayName: 'Paddle', + name: 'paddle', + icon: 'file:paddle.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Paddle API', + defaults: { + name: 'Paddle', + color: '#45567c', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'paddleApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Coupon', + value: 'coupon', + }, + { + name: 'Payment', + value: 'payment', + }, + { + name: 'Plan', + value: 'plan', + }, + { + name: 'Product', + value: 'product', + }, + // { + // name: 'Order', + // value: 'order', + // }, + { + name: 'User', + value: 'user', + }, + ], + default: 'coupon', + description: 'Resource to consume.', + }, + // COUPON + ...couponOperations, + ...couponFields, + // PAYMENT + ...paymentOperations, + ...paymentFields, + // PLAN + ...planOperations, + ...planFields, + // PRODUCT + ...productOperations, + ...productFields, + // ORDER + // ...orderOperations, + // ...orderFields, + // USER + ...userOperations, + ...userFields + ], + }; + + methods = { + loadOptions: { + /* -------------------------------------------------------------------------- */ + /* PAYMENT */ + /* -------------------------------------------------------------------------- */ + + // Get all payment so they can be selected in payment rescheduling + async getPayments(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const endpoint = '/2.0/subscription/payments'; + const paymentResponse = await paddleApiRequest.call(this, endpoint, 'POST', {}); + + // Alert user if there's no payments present to be loaded into payments property + if (paymentResponse.response === undefined || paymentResponse.response.length === 0) { + throw Error('No payments on account.'); + } + + for (const payment of paymentResponse.response) { + const id = payment.id; + returnData.push({ + name: id, + value: id, + }); + } + return returnData; + }, + + /* -------------------------------------------------------------------------- */ + /* PRODUCTS */ + /* -------------------------------------------------------------------------- */ + + // Get all Products so they can be selected in coupon creation when assigning products + async getProducts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const endpoint = '/2.0/product/get_products'; + const products = await paddleApiRequest.call(this, endpoint, 'POST', {}); + + // Alert user if there's no products present to be loaded into payments property + if ( products.length === 0) { + throw Error('No products on account.'); + } + + for (const product of products) { + const name = product.name; + const id = product.id; + returnData.push({ + name, + value: id, + }); + } + return returnData; + }, + } + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const body: IDataObject = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'coupon') { + if (operation === 'create') { + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + if (validateJSON(additionalFieldsJson) !== undefined) { + Object.assign(body, JSON.parse(additionalFieldsJson)); + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + const discountType = this.getNodeParameter('discountType', i) as string; + const couponType = this.getNodeParameter('couponType', i) as string; + const discountAmount = this.getNodeParameter('discountAmount', i) as number; + + if (couponType === 'product') { + body.product_ids = (this.getNodeParameter('productIds', i) as string[]).join(); + } + + if (discountType === 'flat') { + body.currency = this.getNodeParameter('currency', i) as string; + } + + body.coupon_type = couponType; + body.discount_type = discountType; + body.discount_amount = discountAmount; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.allowedUses) { + body.allowed_uses = additionalFields.allowedUses as number; + } + if (additionalFields.couponCode) { + body.coupon_code = additionalFields.couponCode as string; + } + if (additionalFields.couponPrefix) { + body.coupon_prefix = additionalFields.couponPrefix as string; + } + if (additionalFields.expires) { + body.expires = moment(additionalFields.expires as Date).format('YYYY-MM-DD') as string; + } + if (additionalFields.group) { + body.group = additionalFields.group as string; + } + if (additionalFields.recurring) { + body.recurring = 1; + } else { + body.recurring = 0; + } + if (additionalFields.numberOfCoupons) { + body.num_coupons = additionalFields.numberOfCoupons as number; + } + if (additionalFields.description) { + body.description = additionalFields.description as string; + } + + const endpoint = '/2.1/product/create_coupon'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + responseData = responseData.response.coupon_codes; + } + } + + if (operation === 'getAll') { + const productId = this.getNodeParameter('productId', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const endpoint = '/2.0/product/list_coupons'; + + body.product_id = productId as string; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + + if (returnAll) { + responseData = responseData.response; + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.response.splice(0, limit); + } + } + + if (operation === 'update') { + + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + if (validateJSON(additionalFieldsJson) !== undefined) { + Object.assign(body, JSON.parse(additionalFieldsJson)); + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + const updateBy = this.getNodeParameter('updateBy', i) as string; + + if (updateBy === 'group') { + body.group = this.getNodeParameter('group', i) as string; + } else { + body.coupon_code = this.getNodeParameter('couponCode', i) as string; + } + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.allowedUses) { + body.allowed_uses = additionalFields.allowedUses as number; + } + if (additionalFields.currency) { + body.currency = additionalFields.currency as string; + } + if (additionalFields.newCouponCode) { + body.new_coupon_code = additionalFields.newCouponCode as string; + } + if (additionalFields.expires) { + body.expires = moment(additionalFields.expires as Date).format('YYYY-MM-DD') as string; + } + if (additionalFields.newGroup) { + body.new_group = additionalFields.newGroup as string; + } + if (additionalFields.recurring === true) { + body.recurring = 1; + } else if (additionalFields.recurring === false) { + body.recurring = 0; + } + if (additionalFields.productIds) { + body.product_ids = additionalFields.productIds as number; + } + if (additionalFields.discountAmount) { + body.discount_amount = additionalFields.discountAmount as number; + } + if (additionalFields.discount) { + //@ts-ignore + if (additionalFields.discount.discountProperties.discountType === 'percentage') { + // @ts-ignore + body.discount_amount = additionalFields.discount.discountProperties.discountAmount as number; + } else { + //@ts-ignore + body.currency = additionalFields.discount.discountProperties.currency as string; + //@ts-ignore + body.discount_amount = additionalFields.discount.discountProperties.discountAmount as number; + } + } + } + + const endpoint = '/2.1/product/update_coupon'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + responseData = responseData.response; + } + } + if (resource === 'payment') { + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + if (validateJSON(additionalFieldsJson) !== undefined) { + Object.assign(body, JSON.parse(additionalFieldsJson)); + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.subscriptionId) { + body.subscription_id = additionalFields.subscriptionId as number; + } + if (additionalFields.plan) { + body.plan = additionalFields.plan as string; + } + if (additionalFields.state) { + body.state = additionalFields.state as string; + } + if (additionalFields.isPaid) { + body.is_paid = 1; + } else { + body.is_paid = 0; + } + if (additionalFields.from) { + body.from = moment(additionalFields.from as Date).format('YYYY-MM-DD') as string; + } + if (additionalFields.to) { + body.to = moment(additionalFields.to as Date).format('YYYY-MM-DD') as string; + } + if (additionalFields.isOneOffCharge) { + body.is_one_off_charge = additionalFields.isOneOffCharge as boolean; + } + } + const endpoint = '/2.0/subscription/payments'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + + if (returnAll) { + responseData = responseData.response; + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.response.splice(0, limit); + } + } + if (operation === 'reschedule') { + const paymentId = this.getNodeParameter('paymentId', i) as number; + const date = this.getNodeParameter('date', i) as Date; + + body.payment_id = paymentId; + body.date = body.to = moment(date as Date).format('YYYY-MM-DD') as string; + + const endpoint = '/2.0/subscription/payments_reschedule'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + } + } + if (resource === 'plan') { + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const endpoint = '/2.0/subscription/plans'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + + if (returnAll) { + responseData = responseData.response; + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.response.splice(0, limit); + } + } + if (operation === 'get') { + const planId = this.getNodeParameter('planId', i) as string; + + body.plan = planId; + + const endpoint = '/2.0/subscription/plans'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + responseData = responseData.response; + } + } + if (resource === 'product') { + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const endpoint = '/2.0/product/get_products'; + + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + + if (returnAll) { + responseData = responseData.response.products; + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.response.products.splice(0, limit); + } + } + } + if (resource === 'order') { + if (operation === 'get') { + const endpoint = '/1.0/order'; + const checkoutId = this.getNodeParameter('checkoutId', i) as string; + + body.checkout_id = checkoutId; + + responseData = await paddleApiRequest.call(this, endpoint, 'GET', body); + } + } + if (resource === 'user') { + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; + + if (jsonParameters) { + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '') { + if (validateJSON(additionalFieldsJson) !== undefined) { + Object.assign(body, JSON.parse(additionalFieldsJson)); + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.state) { + body.state = additionalFields.state as string; + } + if (additionalFields.planId) { + body.plan_id = additionalFields.planId as string; + } + if (additionalFields.subscriptionId) { + body.subscription_id = additionalFields.subscriptionId as string; + } + } + + const endpoint = '/2.0/subscription/users'; + + if (returnAll) { + responseData = await paddleApiRequestAllItems.call(this, 'response', endpoint, 'POST', body); + } else { + body.results_per_page = this.getNodeParameter('limit', i) as number; + responseData = await paddleApiRequest.call(this, endpoint, 'POST', body); + responseData = responseData.response; + } + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as unknown as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Paddle/PaymentDescription.ts b/packages/nodes-base/nodes/Paddle/PaymentDescription.ts new file mode 100644 index 0000000000..2d0ddf7894 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/PaymentDescription.ts @@ -0,0 +1,248 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const paymentOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'payment', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all payment.', + }, + { + name: 'Reschedule', + value: 'reschedule', + description: 'Reschedule payment.', + } + ], + default: 'getAll', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const paymentFields = [ +/* -------------------------------------------------------------------------- */ +/* payment:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'payment', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'payment', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + true, + ], + }, + }, + description: `Attributes in JSON form.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + false + ] + }, + }, + default: {}, + options: [ + { + displayName: 'Date From', + name: 'from', + type: 'dateTime', + default: '', + description: 'payment starting from date.', + }, + { + displayName: 'Date To', + name: 'to', + type: 'dateTime', + default: '', + description: 'payment up until date.', + }, + { + displayName: 'Is Paid', + name: 'isPaid', + type: 'boolean', + default: false, + description: 'payment is paid.', + }, + { + displayName: 'Plan ID', + name: 'plan', + type: 'string', + default: '', + description: 'Filter: The product/plan ID (single or comma-separated values).', + }, + { + displayName: 'Subscription ID', + name: 'subscriptionId', + type: 'number', + default: '', + description: 'A specific user subscription ID.', + }, + { + displayName: 'State', + name: 'state', + type: 'options', + default: 'active', + description: 'Filter: The user subscription status. Returns all active, past_due, trialing and paused subscription plans if not specified.', + options: [ + { + name: 'Active', + value: 'active' + }, + { + name: 'Past Due', + value: 'past_due' + }, + { + name: 'Paused', + value: 'paused' + }, + { + name: 'Trialing', + value: 'trialing' + }, + ] + }, + { + displayName: 'One off charge', + name: 'isOneOffCharge', + type: 'boolean', + default: false, + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* payment:reschedule */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Payment ID', + name: 'paymentId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getpayment', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'reschedule', + ], + }, + }, + description: 'The upcoming subscription payment ID.', + }, + { + displayName: 'Date', + name: 'date', + type: 'dateTime', + default: '', + displayOptions: { + show: { + resource: [ + 'payment', + ], + operation: [ + 'reschedule', + ], + }, + }, + description: 'Date you want to move the payment to.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/PlanDescription.ts b/packages/nodes-base/nodes/Paddle/PlanDescription.ts new file mode 100644 index 0000000000..912692ca94 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/PlanDescription.ts @@ -0,0 +1,98 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const planOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'plan', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get a plan.', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all plans.', + } + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const planFields = [ + +/* -------------------------------------------------------------------------- */ +/* plan:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Plan ID', + name: 'planId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'plan', + ], + operation: [ + 'get', + ], + }, + }, + description: 'Filter: The subscription plan ID.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'plan', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'plan', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/ProductDescription.ts b/packages/nodes-base/nodes/Paddle/ProductDescription.ts new file mode 100644 index 0000000000..9a627a86e0 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/ProductDescription.ts @@ -0,0 +1,71 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const productOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'product', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all products.', + } + ], + default: 'getAll', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const productFields = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'product', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'product', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/UserDescription.ts b/packages/nodes-base/nodes/Paddle/UserDescription.ts new file mode 100644 index 0000000000..d45cc2ddb1 --- /dev/null +++ b/packages/nodes-base/nodes/Paddle/UserDescription.ts @@ -0,0 +1,176 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const userOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'user', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all users', + } + ], + default: 'getAll', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const userFields = [ +/* -------------------------------------------------------------------------- */ +/* user:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 1, + required: true, + typeOptions: { + minValue: 1, + maxValue: 200 + }, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false + ] + }, + }, + description: 'Number of subscription records to return per page.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + true, + ], + }, + }, + description: `Attributes in JSON form.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + jsonParameters: [ + false + ] + }, + }, + default: {}, + options: [ + { + displayName: 'Plan ID', + name: 'planId', + type: 'string', + default: '', + description: 'Filter: The subscription plan ID.', + }, + { + displayName: 'Subscription ID', + name: 'subscriptionId', + type: 'string', + default: '', + description: 'A specific user subscription ID.', + }, + { + displayName: 'State', + name: 'state', + type: 'options', + default: 'active', + description: 'Filter: The user subscription status. Returns all active, past_due, trialing and paused subscription plans if not specified.', + options: [ + { + name: 'Active', + value: 'active' + }, + { + name: 'Past Due', + value: 'past_due' + }, + { + name: 'Paused', + value: 'paused' + }, + { + name: 'Trialing', + value: 'trialing' + }, + ] + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Paddle/paddle.png b/packages/nodes-base/nodes/Paddle/paddle.png new file mode 100644 index 0000000000000000000000000000000000000000..80426ca92c3439e320c00be5eb12476c4957f7ab GIT binary patch literal 3076 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 XufyuO^DY|czS9HQA$}(ot={+t`pL%m literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1ae8d4cee2..691cca25e2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -120,6 +120,7 @@ "dist/credentials/OAuth1Api.credentials.js", "dist/credentials/OAuth2Api.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", + "dist/credentials/PaddleApi.credentials.js", "dist/credentials/PagerDutyApi.credentials.js", "dist/credentials/PagerDutyOAuth2Api.credentials.js", "dist/credentials/PayPalApi.credentials.js", @@ -287,6 +288,7 @@ "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js", "dist/nodes/OpenWeatherMap.node.js", + "dist/nodes/Paddle/Paddle.node.js", "dist/nodes/PagerDuty/PagerDuty.node.js", "dist/nodes/PayPal/PayPal.node.js", "dist/nodes/PayPal/PayPalTrigger.node.js",