import type { IExecuteFunctions, IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, JsonObject, NodeParameterValue, IRequestOptions, IHttpRequestMethods, } from 'n8n-workflow'; import { NodeApiError, NodeOperationError } from 'n8n-workflow'; interface CustomProperty { name: string; value: string; } interface FilterValue { operation: string; value: NodeParameterValue; } interface FilterValues { [key: string]: FilterValue[]; } export class Chargebee implements INodeType { description: INodeTypeDescription = { displayName: 'Chargebee', name: 'chargebee', // eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg icon: 'file:chargebee.png', group: ['input'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Retrieve data from Chargebee API', defaults: { name: 'Chargebee', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'chargebeeApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Customer', value: 'customer', }, { name: 'Invoice', value: 'invoice', }, { name: 'Subscription', value: 'subscription', }, ], default: 'invoice', }, // ---------------------------------- // customer // ---------------------------------- { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['customer'], }, }, options: [ { name: 'Create', value: 'create', description: 'Create a customer', action: 'Create a customer', }, ], default: 'create', }, // ---------------------------------- // customer:create // ---------------------------------- { displayName: 'Properties', name: 'properties', type: 'collection', displayOptions: { show: { operation: ['create'], resource: ['customer'], }, }, default: {}, description: 'Properties to set on the new user', placeholder: 'Add Property', options: [ { displayName: 'User ID', name: 'id', type: 'string', default: '', description: 'ID for the new customer. If not given, this will be auto-generated.', }, { displayName: 'First Name', name: 'first_name', type: 'string', default: '', description: 'The first name of the customer', }, { displayName: 'Last Name', name: 'last_name', type: 'string', default: '', description: 'The last name of the customer', }, { displayName: 'Email', name: 'email', type: 'string', placeholder: 'name@email.com', default: '', description: 'The email address of the customer', }, { displayName: 'Phone', name: 'phone', type: 'string', default: '', description: 'The phone number of the customer', }, { displayName: 'Company', name: 'company', type: 'string', default: '', description: 'The company of the customer', }, { displayName: 'Custom Properties', name: 'customProperties', placeholder: 'Add Custom Property', description: 'Adds a custom property to set also values which have not been predefined', type: 'fixedCollection', typeOptions: { multipleValues: true, }, default: {}, options: [ { name: 'property', displayName: 'Property', values: [ { displayName: 'Property Name', name: 'name', type: 'string', default: '', description: 'Name of the property to set', }, { displayName: 'Property Value', name: 'value', type: 'string', default: '', description: 'Value of the property to set', }, ], }, ], }, ], }, // ---------------------------------- // invoice // ---------------------------------- { displayName: 'Operation', name: 'operation', default: 'list', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['invoice'], }, }, options: [ { name: 'List', value: 'list', description: 'Return the invoices', action: 'List an invoice', }, { name: 'PDF Invoice URL', value: 'pdfUrl', description: 'Get URL for the invoice PDF', action: 'Get URL for the invoice PDF', }, ], }, // ---------------------------------- // invoice:list // ---------------------------------- { displayName: 'Max Results', name: 'maxResults', type: 'number', typeOptions: { minValue: 1, maxValue: 100, }, default: 10, displayOptions: { show: { operation: ['list'], resource: ['invoice'], }, }, description: 'Max. amount of results to return(< 100).', }, { displayName: 'Filters', name: 'filters', placeholder: 'Add Filter', description: 'Filter for invoices', type: 'fixedCollection', typeOptions: { multipleValues: true, }, default: {}, displayOptions: { show: { operation: ['list'], resource: ['invoice'], }, }, options: [ { name: 'date', displayName: 'Invoice Date', values: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Is', value: 'is', }, { name: 'Is Not', value: 'is_not', }, { name: 'After', value: 'after', }, { name: 'Before', value: 'before', }, ], default: 'after', description: 'Operation to decide where the data should be mapped to', }, { displayName: 'Date', name: 'value', type: 'dateTime', default: '', description: 'Query date', }, ], }, { name: 'total', displayName: 'Invoice Amount', values: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Greater Equal Than', value: 'gte', }, { name: 'Greater Than', value: 'gt', }, { name: 'Is', value: 'is', }, { name: 'Is Not', value: 'is_not', }, { name: 'Less Equal Than', value: 'lte', }, { name: 'Less Than', value: 'lt', }, ], default: 'gt', description: 'Operation to decide where the data should be mapped to', }, { displayName: 'Amount', name: 'value', type: 'number', typeOptions: { numberPrecision: 2, }, default: 0, description: 'Query amount', }, ], }, ], }, // ---------------------------------- // invoice:pdfUrl // ---------------------------------- { displayName: 'Invoice ID', name: 'invoiceId', description: 'The ID of the invoice to get', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['pdfUrl'], resource: ['invoice'], }, }, }, // ---------------------------------- // subscription // ---------------------------------- { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['subscription'], }, }, options: [ { name: 'Cancel', value: 'cancel', description: 'Cancel a subscription', action: 'Cancel a subscription', }, { name: 'Delete', value: 'delete', description: 'Delete a subscription', action: 'Delete a subscription', }, ], default: 'delete', }, // ---------------------------------- // subscription:cancel // ---------------------------------- { displayName: 'Subscription ID', name: 'subscriptionId', description: 'The ID of the subscription to cancel', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['cancel'], resource: ['subscription'], }, }, }, { displayName: 'Schedule End of Term', name: 'endOfTerm', type: 'boolean', default: false, displayOptions: { show: { operation: ['cancel'], resource: ['subscription'], }, }, description: 'Whether it will not cancel it directly in will instead schedule the cancelation for the end of the term', }, // ---------------------------------- // subscription:delete // ---------------------------------- { displayName: 'Subscription ID', name: 'subscriptionId', description: 'The ID of the subscription to delete', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['delete'], resource: ['subscription'], }, }, }, ], }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; const credentials = await this.getCredentials('chargebeeApi'); const baseUrl = `https://${credentials.accountName}.chargebee.com/api/v2`; // For Post let body: IDataObject; // For Query string let qs: IDataObject; for (let i = 0; i < items.length; i++) { try { const resource = this.getNodeParameter('resource', i); const operation = this.getNodeParameter('operation', i); let requestMethod: IHttpRequestMethods = 'GET'; let endpoint = ''; body = {}; qs = {}; if (resource === 'customer') { if (operation === 'create') { // ---------------------------------- // create // ---------------------------------- requestMethod = 'POST'; const properties = this.getNodeParameter('properties', i, {}) as IDataObject; for (const key of Object.keys(properties)) { if ( key === 'customProperties' && (properties.customProperties as IDataObject).property !== undefined ) { for (const customProperty of (properties.customProperties as IDataObject) .property! as CustomProperty[]) { qs[customProperty.name] = customProperty.value; } } else { qs[key] = properties[key]; } } endpoint = 'customers'; } else { throw new NodeOperationError( this.getNode(), `The operation "${operation}" is not known!`, { itemIndex: i }, ); } } else if (resource === 'invoice') { if (operation === 'list') { // ---------------------------------- // list // ---------------------------------- endpoint = 'invoices'; // TODO: Make also sorting configurable qs['sort_by[desc]'] = 'date'; qs.limit = this.getNodeParameter('maxResults', i, {}); const setFilters: FilterValues = this.getNodeParameter( 'filters', i, {}, ) as unknown as FilterValues; let filter: FilterValue; let value: NodeParameterValue; for (const filterProperty of Object.keys(setFilters)) { for (filter of setFilters[filterProperty]) { value = filter.value; if (filterProperty === 'date') { value = Math.floor(new Date(value as string).getTime() / 1000); } qs[`${filterProperty}[${filter.operation}]`] = value; } } } else if (operation === 'pdfUrl') { // ---------------------------------- // pdfUrl // ---------------------------------- requestMethod = 'POST'; const invoiceId = this.getNodeParameter('invoiceId', i) as string; endpoint = `invoices/${invoiceId.trim()}/pdf`; } else { throw new NodeOperationError( this.getNode(), `The operation "${operation}" is not known!`, { itemIndex: i }, ); } } else if (resource === 'subscription') { if (operation === 'cancel') { // ---------------------------------- // cancel // ---------------------------------- requestMethod = 'POST'; const subscriptionId = this.getNodeParameter('subscriptionId', i, '') as string; body.end_of_term = this.getNodeParameter('endOfTerm', i, false) as boolean; endpoint = `subscriptions/${subscriptionId.trim()}/cancel`; } else if (operation === 'delete') { // ---------------------------------- // delete // ---------------------------------- requestMethod = 'POST'; const subscriptionId = this.getNodeParameter('subscriptionId', i, '') as string; endpoint = `subscriptions/${subscriptionId.trim()}/delete`; } else { throw new NodeOperationError( this.getNode(), `The operation "${operation}" is not known!`, { itemIndex: i }, ); } } else { throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`, { itemIndex: i, }); } const options = { method: requestMethod, body, qs, uri: `${baseUrl}/${endpoint}`, auth: { user: credentials.apiKey as string, pass: '', }, json: true, } satisfies IRequestOptions; let responseData; try { responseData = await this.helpers.request(options); } catch (error) { throw new NodeApiError(this.getNode(), error as JsonObject); } if (resource === 'invoice' && operation === 'list') { responseData.list.forEach((data: IDataObject) => { responseData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ ...(data.invoice as IDataObject) }), { itemData: { item: i } }, ); returnData.push(...responseData); }); } else if (resource === 'invoice' && operation === 'pdfUrl') { const data: IDataObject = {}; Object.assign(data, items[i].json); data.pdfUrl = responseData.download.download_url; responseData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ ...data }), { itemData: { item: i } }, ); returnData.push(...responseData); } else { responseData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), { itemData: { item: i } }, ); returnData.push(...responseData); } } catch (error) { if (this.continueOnFail(error)) { returnData.push({ error: error.message, json: {}, itemIndex: i }); continue; } throw error; } } return [returnData]; } }