import type { IExecuteFunctions, IDataObject, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription, } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow'; import { adjustAddresses, getFilterQuery, getOrderFields, getProductAttributes, magentoApiRequest, magentoApiRequestAllItems, sort, validateJSON, } from './GenericFunctions'; import { customerFields, customerOperations } from './CustomerDescription'; import { orderFields, orderOperations } from './OrderDescription'; import { productFields, productOperations } from './ProductDescription'; import { invoiceFields, invoiceOperations } from './InvoiceDescription'; import type { CustomAttribute, CustomerAttributeMetadata, Filter, NewCustomer, NewProduct, Search, } from './types'; import { capitalCase } from 'change-case'; export class Magento2 implements INodeType { description: INodeTypeDescription = { displayName: 'Magento 2', name: 'magento2', icon: 'file:magento.svg', group: ['input'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Magento API', defaults: { name: 'Magento 2', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'magento2Api', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Customer', value: 'customer', }, { name: 'Invoice', value: 'invoice', }, { name: 'Order', value: 'order', }, { name: 'Product', value: 'product', }, ], default: 'customer', }, ...customerOperations, ...customerFields, ...invoiceOperations, ...invoiceFields, ...orderOperations, ...orderFields, ...productOperations, ...productFields, ], }; methods = { loadOptions: { async getCountries(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/directorycountries const countries = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/directory/countries', ); const returnData: INodePropertyOptions[] = []; for (const country of countries) { returnData.push({ name: country.full_name_english, value: country.id, }); } returnData.sort(sort); return returnData; }, async getGroups(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/customerGroupsdefault#operation/customerGroupManagementV1GetDefaultGroupGet const group = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/customerGroups/default', ); const returnData: INodePropertyOptions[] = []; returnData.push({ name: group.code, value: group.id, }); returnData.sort(sort); return returnData; }, async getStores(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/storestoreConfigs const stores = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/store/storeConfigs', ); const returnData: INodePropertyOptions[] = []; for (const store of stores) { returnData.push({ name: store.base_url, value: store.id, }); } returnData.sort(sort); return returnData; }, async getWebsites(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/storewebsites const websites = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/store/websites', ); const returnData: INodePropertyOptions[] = []; for (const website of websites) { returnData.push({ name: website.name, value: website.id, }); } returnData.sort(sort); return returnData; }, async getCustomAttributes(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/attributeMetadatacustomer#operation/customerCustomerMetadataV1GetAllAttributesMetadataGet const resource = this.getCurrentNodeParameter('resource') as string; const attributes = (await magentoApiRequest.call( this, 'GET', `/rest/default/V1/attributeMetadata/${resource}`, )) as CustomerAttributeMetadata[]; const returnData: INodePropertyOptions[] = []; for (const attribute of attributes) { if (attribute.system === false && attribute.frontend_label !== '') { returnData.push({ name: attribute.frontend_label as string, value: attribute.attribute_code as string, }); } } returnData.sort(sort); return returnData; }, async getSystemAttributes(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/attributeMetadatacustomer#operation/customerCustomerMetadataV1GetAllAttributesMetadataGet const resource = this.getCurrentNodeParameter('resource') as string; const attributes = (await magentoApiRequest.call( this, 'GET', `/rest/default/V1/attributeMetadata/${resource}`, )) as CustomerAttributeMetadata[]; const returnData: INodePropertyOptions[] = []; for (const attribute of attributes) { if (attribute.system === true && attribute.frontend_label !== null) { returnData.push({ name: attribute.frontend_label as string, value: attribute.attribute_code as string, }); } } returnData.sort(sort); return returnData; }, async getProductTypes(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/productslinkstypes const types = (await magentoApiRequest.call( this, 'GET', '/rest/default/V1/products/types', )) as IDataObject[]; const returnData: INodePropertyOptions[] = []; for (const type of types) { returnData.push({ name: type.label as string, value: type.name as string, }); } returnData.sort(sort); return returnData; }, async getCategories(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/categories#operation/catalogCategoryManagementV1GetTreeGet const { items: categories } = (await magentoApiRequest.call( this, 'GET', '/rest/default/V1/categories/list', {}, { search_criteria: { filter_groups: [ { filters: [ { field: 'is_active', condition_type: 'eq', value: 1, }, ], }, ], }, }, )) as { items: IDataObject[] }; const returnData: INodePropertyOptions[] = []; for (const category of categories) { returnData.push({ name: category.name as string, value: category.id as string, }); } returnData.sort(sort); return returnData; }, async getAttributeSets(this: ILoadOptionsFunctions): Promise { //https://magento.redoc.ly/2.3.7-admin/tag/productsattribute-setssetslist#operation/catalogAttributeSetRepositoryV1GetListGet const { items: attributeSets } = (await magentoApiRequest.call( this, 'GET', '/rest/default/V1/products/attribute-sets/sets/list', {}, { search_criteria: 0, }, )) as { items: IDataObject[] }; const returnData: INodePropertyOptions[] = []; for (const attributeSet of attributeSets) { returnData.push({ name: attributeSet.attribute_set_name as string, value: attributeSet.attribute_set_id as string, }); } returnData.sort(sort); return returnData; }, async getFilterableCustomerAttributes( this: ILoadOptionsFunctions, ): Promise { return getProductAttributes.call(this, (attribute) => attribute.is_filterable); }, async getProductAttributes(this: ILoadOptionsFunctions): Promise { return getProductAttributes.call(this); }, // async getProductAttributesFields(this: ILoadOptionsFunctions): Promise { // return getProductAttributes.call(this, undefined, { name: '*', value: '*', description: 'All properties' }); // }, async getFilterableProductAttributes( this: ILoadOptionsFunctions, ): Promise { return getProductAttributes.call(this, (attribute) => attribute.is_searchable === '1'); }, async getSortableProductAttributes( this: ILoadOptionsFunctions, ): Promise { return getProductAttributes.call(this, (attribute) => attribute.used_for_sort_by); }, async getOrderAttributes(this: ILoadOptionsFunctions): Promise { return getOrderFields() .map((field) => ({ name: capitalCase(field), value: field })) .sort(sort); }, }, }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; const length = items.length; let responseData; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); for (let i = 0; i < length; i++) { try { if (resource === 'customer') { if (operation === 'create') { // https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1SavePut const email = this.getNodeParameter('email', i) as string; const firstname = this.getNodeParameter('firstname', i) as string; const lastname = this.getNodeParameter('lastname', i) as string; const { addresses, customAttributes, password, ...rest } = this.getNodeParameter( 'additionalFields', i, ) as { addresses: { address: [ { street: string; }, ]; }; customAttributes: { customAttribute: CustomAttribute[]; }; password: string; }; const body: NewCustomer = { customer: { email, firstname, lastname, }, }; body.customer!.addresses = adjustAddresses(addresses?.address || []); body.customer!.custom_attributes = customAttributes?.customAttribute || {}; body.customer!.extension_attributes = [ 'amazon_id', 'is_subscribed', 'vertex_customer_code', 'vertex_customer_country', ].reduce((obj, value: string): any => { if ((rest as IDataObject).hasOwnProperty(value)) { const data = Object.assign(obj, { [value]: (rest as IDataObject)[value] }); delete (rest as IDataObject)[value]; return data; } else { return obj; } }, {}); if (password) { body.password = password; } Object.assign(body.customer!, rest); responseData = await magentoApiRequest.call(this, 'POST', '/rest/V1/customers', body); } if (operation === 'delete') { //https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1SavePut const customerId = this.getNodeParameter('customerId', i) as string; responseData = await magentoApiRequest.call( this, 'DELETE', `/rest/default/V1/customers/${customerId}`, ); responseData = { success: true }; } if (operation === 'get') { //https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1GetByIdGet const customerId = this.getNodeParameter('customerId', i) as string; responseData = await magentoApiRequest.call( this, 'GET', `/rest/default/V1/customers/${customerId}`, ); } if (operation === 'getAll') { //https://magento.redoc.ly/2.3.7-admin/tag/customerssearch const filterType = this.getNodeParameter('filterType', i) as string; const sortOption = this.getNodeParameter('options.sort', i, {}) as { sort: [{ direction: string; field: string }]; }; const returnAll = this.getNodeParameter('returnAll', 0); let qs: Search = {}; if (filterType === 'manual') { const filters = this.getNodeParameter('filters', i) as { conditions: Filter[] }; const matchType = this.getNodeParameter('matchType', i) as string; qs = getFilterQuery(Object.assign(filters, { matchType }, sortOption)); } else if (filterType === 'json') { const filterJson = this.getNodeParameter('filterJson', i) as string; if (validateJSON(filterJson) !== undefined) { qs = JSON.parse(filterJson); } else { throw new NodeApiError(this.getNode(), { message: 'Filter (JSON) must be a valid json', }); } } else { qs = { search_criteria: {}, }; if (Object.keys(sortOption).length !== 0) { qs.search_criteria = { sort_orders: sortOption.sort, }; } } if (returnAll) { qs.search_criteria!.page_size = 100; responseData = await magentoApiRequestAllItems.call( this, 'items', 'GET', '/rest/default/V1/customers/search', {}, qs as unknown as IDataObject, ); } else { const limit = this.getNodeParameter('limit', 0); qs.search_criteria!.page_size = limit; responseData = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/customers/search', {}, qs as unknown as IDataObject, ); responseData = responseData.items; } } if (operation === 'update') { //https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1SavePut const customerId = this.getNodeParameter('customerId', i) as string; const firstName = this.getNodeParameter('firstName', i) as string; const lastName = this.getNodeParameter('lastName', i) as string; const email = this.getNodeParameter('email', i) as string; const { addresses, customAttributes, password, ...rest } = this.getNodeParameter( 'updateFields', i, ) as { addresses: { address: [ { street: string; }, ]; }; customAttributes: { customAttribute: CustomAttribute[]; }; password: string; }; const body: NewCustomer = { customer: { email, firstname: firstName, lastname: lastName, id: parseInt(customerId, 10), website_id: 0, }, }; body.customer!.addresses = adjustAddresses(addresses?.address || []); body.customer!.custom_attributes = customAttributes?.customAttribute || {}; body.customer!.extension_attributes = [ 'amazon_id', 'is_subscribed', 'vertex_customer_code', 'vertex_customer_country', ].reduce((obj, value: string): any => { if ((rest as IDataObject).hasOwnProperty(value)) { const data = Object.assign(obj, { [value]: (rest as IDataObject)[value] }); delete (rest as IDataObject)[value]; return data; } else { return obj; } }, {}); if (password) { body.password = password; } Object.assign(body.customer!, rest); responseData = await magentoApiRequest.call( this, 'PUT', `/rest/V1/customers/${customerId}`, body, ); } } if (resource === 'invoice') { if (operation === 'create') { ///https://magento.redoc.ly/2.3.7-admin/tag/orderorderIdinvoice const orderId = this.getNodeParameter('orderId', i) as string; responseData = await magentoApiRequest.call( this, 'POST', `/rest/default/V1/order/${orderId}/invoice`, ); responseData = { success: true }; } } if (resource === 'order') { if (operation === 'cancel') { //https://magento.redoc.ly/2.3.7-admin/tag/ordersidcancel const orderId = this.getNodeParameter('orderId', i) as string; responseData = await magentoApiRequest.call( this, 'POST', `/rest/default/V1/orders/${orderId}/cancel`, ); responseData = { success: true }; } if (operation === 'get') { //https://magento.redoc.ly/2.3.7-admin/tag/ordersid#operation/salesOrderRepositoryV1GetGet const orderId = this.getNodeParameter('orderId', i) as string; responseData = await magentoApiRequest.call( this, 'GET', `/rest/default/V1/orders/${orderId}`, ); } if (operation === 'ship') { ///https://magento.redoc.ly/2.3.7-admin/tag/orderorderIdship#operation/salesShipOrderV1ExecutePost const orderId = this.getNodeParameter('orderId', i) as string; responseData = await magentoApiRequest.call( this, 'POST', `/rest/default/V1/order/${orderId}/ship`, ); responseData = { success: true }; } if (operation === 'getAll') { //https://magento.redoc.ly/2.3.7-admin/tag/orders#operation/salesOrderRepositoryV1GetListGet const filterType = this.getNodeParameter('filterType', i) as string; const sortOption = this.getNodeParameter('options.sort', i, {}) as { sort: [{ direction: string; field: string }]; }; const returnAll = this.getNodeParameter('returnAll', 0); let qs: Search = {}; if (filterType === 'manual') { const filters = this.getNodeParameter('filters', i) as { conditions: Filter[] }; const matchType = this.getNodeParameter('matchType', i) as string; qs = getFilterQuery(Object.assign(filters, { matchType }, sortOption)); } else if (filterType === 'json') { const filterJson = this.getNodeParameter('filterJson', i) as string; if (validateJSON(filterJson) !== undefined) { qs = JSON.parse(filterJson); } else { throw new NodeApiError(this.getNode(), { message: 'Filter (JSON) must be a valid json', }); } } else { qs = { search_criteria: {}, }; if (Object.keys(sortOption).length !== 0) { qs.search_criteria = { sort_orders: sortOption.sort, }; } } if (returnAll) { qs.search_criteria!.page_size = 100; responseData = await magentoApiRequestAllItems.call( this, 'items', 'GET', '/rest/default/V1/orders', {}, qs as unknown as IDataObject, ); } else { const limit = this.getNodeParameter('limit', 0); qs.search_criteria!.page_size = limit; responseData = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/orders', {}, qs as unknown as IDataObject, ); responseData = responseData.items; } } } if (resource === 'product') { if (operation === 'create') { // https://magento.redoc.ly/2.3.7-admin/tag/products#operation/catalogProductRepositoryV1SavePost const sku = this.getNodeParameter('sku', i) as string; const name = this.getNodeParameter('name', i) as string; const attributeSetId = this.getNodeParameter('attributeSetId', i) as string; const price = this.getNodeParameter('price', i) as number; const { customAttributes, category: _category, ...rest } = this.getNodeParameter('additionalFields', i) as { customAttributes: { customAttribute: CustomAttribute[]; }; category: string; }; const body: NewProduct = { product: { sku, name, attribute_set_id: parseInt(attributeSetId, 10), price, }, }; body.product!.custom_attributes = customAttributes?.customAttribute || {}; Object.assign(body.product!, rest); responseData = await magentoApiRequest.call( this, 'POST', '/rest/default/V1/products', body, ); } if (operation === 'delete') { //https://magento.redoc.ly/2.3.7-admin/tag/productssku#operation/catalogProductRepositoryV1DeleteByIdDelete const sku = this.getNodeParameter('sku', i) as string; responseData = await magentoApiRequest.call( this, 'DELETE', `/rest/default/V1/products/${sku}`, ); responseData = { success: true }; } if (operation === 'get') { //https://magento.redoc.ly/2.3.7-admin/tag/productssku#operation/catalogProductRepositoryV1GetGet const sku = this.getNodeParameter('sku', i) as string; responseData = await magentoApiRequest.call( this, 'GET', `/rest/default/V1/products/${sku}`, ); } if (operation === 'getAll') { //https://magento.redoc.ly/2.3.7-admin/tag/customerssearch const filterType = this.getNodeParameter('filterType', i) as string; const sortOption = this.getNodeParameter('options.sort', i, {}) as { sort: [{ direction: string; field: string }]; }; const returnAll = this.getNodeParameter('returnAll', 0); let qs: Search = {}; if (filterType === 'manual') { const filters = this.getNodeParameter('filters', i) as { conditions: Filter[] }; const matchType = this.getNodeParameter('matchType', i) as string; qs = getFilterQuery(Object.assign(filters, { matchType }, sortOption)); } else if (filterType === 'json') { const filterJson = this.getNodeParameter('filterJson', i) as string; if (validateJSON(filterJson) !== undefined) { qs = JSON.parse(filterJson); } else { throw new NodeApiError(this.getNode(), { message: 'Filter (JSON) must be a valid json', }); } } else { qs = { search_criteria: {}, }; if (Object.keys(sortOption).length !== 0) { qs.search_criteria = { sort_orders: sortOption.sort, }; } } if (returnAll) { qs.search_criteria!.page_size = 100; responseData = await magentoApiRequestAllItems.call( this, 'items', 'GET', '/rest/default/V1/products', {}, qs as unknown as IDataObject, ); } else { const limit = this.getNodeParameter('limit', 0); qs.search_criteria!.page_size = limit; responseData = await magentoApiRequest.call( this, 'GET', '/rest/default/V1/products', {}, qs as unknown as IDataObject, ); responseData = responseData.items; } } if (operation === 'update') { //https://magento.redoc.ly/2.3.7-admin/tag/productssku#operation/catalogProductRepositoryV1SavePut const sku = this.getNodeParameter('sku', i) as string; const { customAttributes, ...rest } = this.getNodeParameter('updateFields', i) as { customAttributes: { customAttribute: CustomAttribute[]; }; }; if (!Object.keys(rest).length) { throw new NodeApiError(this.getNode(), { message: 'At least one parameter has to be updated', }); } const body: NewProduct = { product: { sku, }, }; body.product!.custom_attributes = customAttributes?.customAttribute || {}; Object.assign(body.product!, rest); responseData = await magentoApiRequest.call( this, 'PUT', `/rest/default/V1/products/${sku}`, body, ); } } const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), { itemData: { item: i } }, ); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), { itemData: { item: i } }, ); returnData.push(...executionErrorData); continue; } throw error; } } return [returnData]; } }