From 6b21e251dfe72eecd3d3b7141f64376ca3339a39 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 11 Jul 2021 20:44:24 -0400 Subject: [PATCH 01/96] Create app file for quickbooks --- components/quickbooks/quickbooks.app.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 components/quickbooks/quickbooks.app.js diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js new file mode 100644 index 0000000000000..cb27ca5a01abc --- /dev/null +++ b/components/quickbooks/quickbooks.app.js @@ -0,0 +1 @@ +const QuickBooks = require('node-quickbooks') From 831711ff73ff5cef4ccd05d69616d3ec8ca056e0 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 11 Jul 2021 20:45:51 -0400 Subject: [PATCH 02/96] Add basic skeleton to quickbooks app file --- components/quickbooks/quickbooks.app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index cb27ca5a01abc..97c0a84ac6f1f 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1 +1,10 @@ const QuickBooks = require('node-quickbooks') + +module.exports = { + type: "app", + app: "quickbooks", + propDefinitions: { + }, + methods: { + }, +}; \ No newline at end of file From a425096a13223284cb76888c424aa20bc902925a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Wed, 14 Jul 2021 22:29:17 -0400 Subject: [PATCH 03/96] Add custom webhook events source with basic common file --- components/quickbooks/sources/common.js | 11 +++ .../custom-webhook-events.js | 75 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 components/quickbooks/sources/common.js create mode 100644 components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js new file mode 100644 index 0000000000000..46c9f35893ea7 --- /dev/null +++ b/components/quickbooks/sources/common.js @@ -0,0 +1,11 @@ +const quickbooks = require('../quickbooks.app'); + +module.exports = { + props: { + quickbooks, + http: { + type: '$.interface.http', + customResponse: true, + }, + }, +} diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js new file mode 100644 index 0000000000000..26bdd0c428289 --- /dev/null +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -0,0 +1,75 @@ +const common = require('../common') + +module.exports = { + ...common, + key: 'quickbooks-custom-webhook-events', + name: 'Custom Webhook Events', + description: 'Subscribe to one or more event types (i.e. PurchaseOrder created, Invoice emailed) and emit an event on each webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', + version: '0.0.1', + props: { + ...common.props, + operations_to_emit: { + type: 'string[]', + label: 'Operations', + options: ['Create', 'Update', 'Emailed', 'Delete', 'Void'], + optional: true, + }, + names_to_emit: { + type: 'string[]', + label: 'Entity Types', + options: [ + 'Account', + 'BillPayment', + 'Class', + 'Customer', + 'Employee', + 'Estimate', + 'Invoice', + 'Item', + 'Payment', + 'Purchase', + 'SalesReceipt', + 'Vendor', + 'Bill', + 'CreditMemo', + 'RefundReceipt', + 'VendorCredit', + 'TimeActivity', + 'Department', + 'Deposit', + 'JournalEntry', + 'PaymentMethod', + 'Preferences', + 'PurchaseOrder', + 'TaxAgency', + 'Term', + 'Transfer', + 'Budget', + 'Currency', + 'JournalCode', + ], + optional: true, + }, + }, + async run(event) { + const entity = event.body.eventNotifications[0].dataChangeEvent.entities[0] + const summary = `${entity.name} ${entity.id} ${entity.operation}` + + this.http.respond({ + status: 200, + body: entity, + headers: { + 'Content-Type': event.headers['Content-Type'], + }, + }); + + //reject any events that don't match the operation or entity name (if those options have been selected) + if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ + console.log(`Operation '${entity.operation}' not found in list of selected Operations`) + } else if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ + console.log(`Name '${entity.name}' not found in list of selected Entity Types`) + } else { + this.$emit(event.body, {summary}) + } + }, +}; From e5d06a2080596eaac4bfcbfde86b5a0ced52ef63 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Thu, 15 Jul 2021 21:11:24 -0400 Subject: [PATCH 04/96] Add propDefinition for operations_to_emit --- components/quickbooks/quickbooks.app.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 97c0a84ac6f1f..766781dd73e97 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -4,6 +4,13 @@ module.exports = { type: "app", app: "quickbooks", propDefinitions: { + operations_to_emit: { + type: 'string[]', + label: 'Operations', + description: 'Select which operations to emit. If you want to emit them all you can just leave this field blank.', + options: ['Create', 'Update', 'Merge', 'Delete', 'Void', 'Emailed'], + optional: true, + }, }, methods: { }, From d0b29bf9499d025fd0433bbec3007c86543fb0c0 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Thu, 15 Jul 2021 21:13:28 -0400 Subject: [PATCH 05/96] Move webhook options list to common.js --- components/quickbooks/sources/common.js | 73 +++++++++++++++++++ .../custom-webhook-events.js | 54 +++----------- 2 files changed, 84 insertions(+), 43 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 46c9f35893ea7..ed97873b43024 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -1,5 +1,38 @@ const quickbooks = require('../quickbooks.app'); +//https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/entities-and-operations-supported +const supported_webhook_options = { + Account: ['Create', 'Update', 'Merge', 'Delete'], + Bill: ['Create', 'Update', 'Delete'], + BillPayment: ['Create', 'Update', 'Delete', 'Void'], + Budget: ['Create', 'Update'], + Class: ['Create', 'Update', 'Merge', 'Delete'], + CreditMemo: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], + Currency: ['Create', 'Update'], + Customer: ['Create', 'Update', 'Merge', 'Delete'], + Department: ['Create', 'Update', 'Merge'], + Deposit: ['Create', 'Update', 'Delete'], + Employee: ['Create', 'Update', 'Merge', 'Delete'], + Estimate: ['Create', 'Update', 'Delete', 'Emailed'], + Invoice: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], + Item: ['Create', 'Update', 'Merge', 'Delete'], + JournalCode: ['Create', 'Update'], + JournalEntry: ['Create', 'Update', 'Delete'], + Payment: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], + PaymentMethod: ['Create', 'Update', 'Merge'], + Preferences: ['Update'], + Purchase: ['Create', 'Update', 'Delete', 'Void'], + PurchaseOrder: ['Create', 'Update', 'Delete', 'Emailed'], + RefundReceipt: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], + SalesReceipt: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], + TaxAgency: ['Create', 'Update'], + Term: ['Create', 'Update'], + TimeActivity: ['Create', 'Update', 'Delete'], + Transfer: ['Create', 'Update', 'Delete', 'Void'], + Vendor: ['Create', 'Update', 'Merge', 'Delete'], + VendorCredit: ['Create', 'Update', 'Delete'], +} + module.exports = { props: { quickbooks, @@ -8,4 +41,44 @@ module.exports = { customResponse: true, }, }, + methods: { + getEntityNames(){ + return Object.keys(supported_webhook_options) + }, + getSupportedOperations(entity_name){ + return supported_webhook_options[entity_name] + }, + describeOperations(operations){ + const descriptive_operations = { + Create: 'created', + Update: 'updated', + Merge: 'merged', + Delete: 'deleted', + Void: 'voided', + Emailed: 'emailed', + } + + if(Array.isArray(operations)){ + return operations.map(operation => descriptive_operations[operation]).join(', ') + } else { + return descriptive_operations[operations] + } + }, + getEntity(event){ + return event.body.eventNotifications[0].dataChangeEvent.entities[0] + }, + sendHttpResponse(event, entity){ + this.http.respond({ + status: 200, + body: entity, + headers: { + 'Content-Type': event.headers['Content-Type'], + }, + }) + }, + emitEvent(event, entity){ + const summary = `${entity.name} ${entity.id} ${this.describeOperations(entity.operation)}` + this.$emit(event.body, {summary}) + } + }, } diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 26bdd0c428289..814be97ca9031 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -4,52 +4,20 @@ module.exports = { ...common, key: 'quickbooks-custom-webhook-events', name: 'Custom Webhook Events', - description: 'Subscribe to one or more event types (i.e. PurchaseOrder created, Invoice emailed) and emit an event on each webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', + description: 'Specify your own combination of entities and operations (i.e. Bills and Invoices — Created and Updated) to emit an event for each matching webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', version: '0.0.1', props: { ...common.props, - operations_to_emit: { - type: 'string[]', - label: 'Operations', - options: ['Create', 'Update', 'Emailed', 'Delete', 'Void'], - optional: true, - }, names_to_emit: { type: 'string[]', - label: 'Entity Types', - options: [ - 'Account', - 'BillPayment', - 'Class', - 'Customer', - 'Employee', - 'Estimate', - 'Invoice', - 'Item', - 'Payment', - 'Purchase', - 'SalesReceipt', - 'Vendor', - 'Bill', - 'CreditMemo', - 'RefundReceipt', - 'VendorCredit', - 'TimeActivity', - 'Department', - 'Deposit', - 'JournalEntry', - 'PaymentMethod', - 'Preferences', - 'PurchaseOrder', - 'TaxAgency', - 'Term', - 'Transfer', - 'Budget', - 'Currency', - 'JournalCode', - ], + label: 'Entities', + description: 'Select which QuickBooks entities to emit. If you want to emit them all you can just leave this field blank.', + options: common.methods.getEntityNames(), optional: true, }, + operations_to_emit: { + propDefinition: [github, 'operations_to_emit'], + }, }, async run(event) { const entity = event.body.eventNotifications[0].dataChangeEvent.entities[0] @@ -63,11 +31,11 @@ module.exports = { }, }); - //reject any events that don't match the operation or entity name (if those options have been selected) - if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ - console.log(`Operation '${entity.operation}' not found in list of selected Operations`) - } else if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ + //reject any events that don't match the entity name or operation (if those options have been selected) + if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ console.log(`Name '${entity.name}' not found in list of selected Entity Types`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ + console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { this.$emit(event.body, {summary}) } From ea99e9370781f054301650a59688323cd29c5ebb Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Thu, 15 Jul 2021 21:14:12 -0400 Subject: [PATCH 06/96] Create specific source for customers --- .../new-or-modified-customer.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js new file mode 100644 index 0000000000000..fd197bba85aea --- /dev/null +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -0,0 +1,39 @@ +const quickbooks = require('../../quickbooks.app'); +const common = require('../common') + +const entity_name = 'Customer' + +const supported_operations = common.methods.getSupportedOperations(entity_name) +const supported_operations_description = common.methods.describeOperations(supported_operations) + +module.exports = { + ...common, + key: `quickbooks-new-or-modified-${entity_name.toLowerCase()}`, + name: `New or Modified ${entity_name}`, + description: `Emits an event when a ${entity_name} is ${supported_operations_description}.`, + version: '0.0.1', + props: { + ...common.props, + operations_to_emit: { + propDefinition: [quickbooks, 'operations_to_emit'], + //list only the options supported by this entity instead of offering all the default options from the propDefinition + options: common.methods.getSupportedOperations(entity_name), + }, + }, + methods: { + ...common.methods, + }, + async run(event) { + const entity = this.getEntity(event) + this.sendHttpResponse(event, entity) + + //reject any events that don't match the entity name or operation (if those options have been selected) + if(entity.name !== entity_name){ + console.log(`${entity.name} webhook received and ignored, since it is not a ${entity_name}`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ + console.log(`Operation '${entity.operation}' not found in list of selected Operations`) + } else { + this.emitEvent(event, entity) + } + }, +}; From 8fb858b9f83b2ea7748361e2546522654ad2b227 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 07:37:13 -0400 Subject: [PATCH 07/96] Rename variables for readablility --- components/quickbooks/sources/common.js | 7 ++++--- .../sources/custom-webhook-events/custom-webhook-events.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index ed97873b43024..1becd0b521796 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -49,7 +49,7 @@ module.exports = { return supported_webhook_options[entity_name] }, describeOperations(operations){ - const descriptive_operations = { + const descriptive_operation_names = { Create: 'created', Update: 'updated', Merge: 'merged', @@ -59,9 +59,10 @@ module.exports = { } if(Array.isArray(operations)){ - return operations.map(operation => descriptive_operations[operation]).join(', ') + //creates a string listing the operations for use in the description: e.g. 'created, updated, merged, deleted' + return operations.map(operation => descriptive_operation_names[operation]).join(', ') } else { - return descriptive_operations[operations] + return descriptive_operation_names[operations] } }, getEntity(event){ diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 814be97ca9031..b2c07f6a8f085 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -3,7 +3,7 @@ const common = require('../common') module.exports = { ...common, key: 'quickbooks-custom-webhook-events', - name: 'Custom Webhook Events', + name: 'Multiple Entities Created, Updated, Merged, Deleted, Voided, or Emailed', description: 'Specify your own combination of entities and operations (i.e. Bills and Invoices — Created and Updated) to emit an event for each matching webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', version: '0.0.1', props: { From 91228bd06c236e8f0238173ab4d072e6817d0636 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 07:41:20 -0400 Subject: [PATCH 08/96] Rename variables and change source names. --- .../new-or-modified-customer.js | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index fd197bba85aea..39f78c0677445 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -1,39 +1,42 @@ const quickbooks = require('../../quickbooks.app'); const common = require('../common') -const entity_name = 'Customer' +const source_entity = { + name: 'Customer', + display_name: 'Customer', +} -const supported_operations = common.methods.getSupportedOperations(entity_name) +const supported_operations = common.methods.getSupportedOperations(source_entity.name) const supported_operations_description = common.methods.describeOperations(supported_operations) module.exports = { ...common, - key: `quickbooks-new-or-modified-${entity_name.toLowerCase()}`, - name: `New or Modified ${entity_name}`, - description: `Emits an event when a ${entity_name} is ${supported_operations_description}.`, + key: `quickbooks-new-or-modified-${source_entity.name.toLowerCase()}`, + name: `${source_entity.display_name} Created, Updated, Merged, Deleted, Voided, or Emailed`, + description: `Emits an event when a ${source_entity.display_name} is ${supported_operations_description}.`, version: '0.0.1', props: { ...common.props, operations_to_emit: { propDefinition: [quickbooks, 'operations_to_emit'], - //list only the options supported by this entity instead of offering all the default options from the propDefinition - options: common.methods.getSupportedOperations(entity_name), + //list only the options supported by this source's entity instead of offering all the default options from the propDefinition + options: supported_operations, }, }, methods: { ...common.methods, }, async run(event) { - const entity = this.getEntity(event) - this.sendHttpResponse(event, entity) + const webhook_entity = this.getEntity(event) + this.sendHttpResponse(event, webhook_entity) - //reject any events that don't match the entity name or operation (if those options have been selected) - if(entity.name !== entity_name){ - console.log(`${entity.name} webhook received and ignored, since it is not a ${entity_name}`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ - console.log(`Operation '${entity.operation}' not found in list of selected Operations`) + //reject any events that don't match the source entity name or operation (if those options have been selected) + if(webhook_entity.name !== source_entity.name){ + console.log(`${webhook_entity.name} webhook received and ignored, since it is not a ${source_entity.name}`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ + console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, entity) + this.emitEvent(event, webhook_entity) } }, }; From 11b6af2859047dd22a72c6c3d37d96fa162beb16 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 07:43:25 -0400 Subject: [PATCH 09/96] Add documenation link to specific entity source. --- .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 39f78c0677445..c1d15d8e80a57 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -13,7 +13,7 @@ module.exports = { ...common, key: `quickbooks-new-or-modified-${source_entity.name.toLowerCase()}`, name: `${source_entity.display_name} Created, Updated, Merged, Deleted, Voided, or Emailed`, - description: `Emits an event when a ${source_entity.display_name} is ${supported_operations_description}.`, + description: `Emits an event when a ${source_entity.display_name} is ${supported_operations_description}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: '0.0.1', props: { ...common.props, From 5f896005eb01a8c311c2270144a1c3c4bccdef4e Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 21:28:28 -0400 Subject: [PATCH 10/96] Add quickbooks app to custom-webhook-events.js --- .../sources/custom-webhook-events/custom-webhook-events.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index b2c07f6a8f085..ff08817e9f4ea 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -1,3 +1,4 @@ +const quickbooks = require('../../quickbooks.app'); const common = require('../common') module.exports = { @@ -16,7 +17,7 @@ module.exports = { optional: true, }, operations_to_emit: { - propDefinition: [github, 'operations_to_emit'], + propDefinition: [quickbooks, 'operations_to_emit'], }, }, async run(event) { From 004c39039c828f4860d40618a20e52ba75d57f92 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 21:30:09 -0400 Subject: [PATCH 11/96] Revise names and descriptions to make them more readable --- .../sources/custom-webhook-events/custom-webhook-events.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index ff08817e9f4ea..45bf4bbfffa4a 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -4,15 +4,15 @@ const common = require('../common') module.exports = { ...common, key: 'quickbooks-custom-webhook-events', - name: 'Multiple Entities Created, Updated, Merged, Deleted, Voided, or Emailed', - description: 'Specify your own combination of entities and operations (i.e. Bills and Invoices — Created and Updated) to emit an event for each matching webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', + name: 'Custom Set of Webhook Entities (Create, Update, Merge, Delete, Void, Emailed)', + description: 'Specify your own combination of entities and operations (i.e. Created or Updated Customers and Vendors) to emit an event for each matching webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', version: '0.0.1', props: { ...common.props, names_to_emit: { type: 'string[]', label: 'Entities', - description: 'Select which QuickBooks entities to emit. If you want to emit them all you can just leave this field blank.', + description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', options: common.methods.getEntityNames(), optional: true, }, From 64de7a92fb165c0e099e143f9b6c3fd492e9d6ba Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 21:32:15 -0400 Subject: [PATCH 12/96] Add the rest of the changed names and descriptions. --- components/quickbooks/quickbooks.app.js | 2 +- .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 766781dd73e97..faa3e943f25da 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -7,7 +7,7 @@ module.exports = { operations_to_emit: { type: 'string[]', label: 'Operations', - description: 'Select which operations to emit. If you want to emit them all you can just leave this field blank.', + description: 'Select which operations to emit or just leave it blank to emit them all.', options: ['Create', 'Update', 'Merge', 'Delete', 'Void', 'Emailed'], optional: true, }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index c1d15d8e80a57..42af12a4b095b 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -12,7 +12,7 @@ const supported_operations_description = common.methods.describeOperations(suppo module.exports = { ...common, key: `quickbooks-new-or-modified-${source_entity.name.toLowerCase()}`, - name: `${source_entity.display_name} Created, Updated, Merged, Deleted, Voided, or Emailed`, + name: `New or Modified ${source_entity.display_name} (${supported_operations.join(', ')})`, description: `Emits an event when a ${source_entity.display_name} is ${supported_operations_description}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: '0.0.1', props: { From c6ed1cdc2e73cc9cd11d619d23a6e8cb9dd2436a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 21:49:31 -0400 Subject: [PATCH 13/96] Hard-code entity name into descriptive text to increase code readability --- .../new-or-modified-customer.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 42af12a4b095b..bae95b7626472 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -1,19 +1,16 @@ const quickbooks = require('../../quickbooks.app'); const common = require('../common') -const source_entity = { - name: 'Customer', - display_name: 'Customer', -} +const source_entity = 'Customer' -const supported_operations = common.methods.getSupportedOperations(source_entity.name) +const supported_operations = common.methods.getSupportedOperations(source_entity) const supported_operations_description = common.methods.describeOperations(supported_operations) module.exports = { ...common, - key: `quickbooks-new-or-modified-${source_entity.name.toLowerCase()}`, - name: `New or Modified ${source_entity.display_name} (${supported_operations.join(', ')})`, - description: `Emits an event when a ${source_entity.display_name} is ${supported_operations_description}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, + key: `quickbooks-new-or-modified-customer`, + name: `New or Modified Customer (${supported_operations.join(', ')})`, + description: `Emits an event when a Customer is ${supported_operations_description}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: '0.0.1', props: { ...common.props, @@ -30,9 +27,9 @@ module.exports = { const webhook_entity = this.getEntity(event) this.sendHttpResponse(event, webhook_entity) - //reject any events that don't match the source entity name or operation (if those options have been selected) - if(webhook_entity.name !== source_entity.name){ - console.log(`${webhook_entity.name} webhook received and ignored, since it is not a ${source_entity.name}`) + //reject any events that don't match the specified entity name or operation + if(webhook_entity.name !== source_entity){ + console.log(`${webhook_entity.name} webhook received and ignored, since it is not a Customer`) } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) } else { From 70cad06ba857c6b221855ec89e5d5a98c795ad16 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 21:53:31 -0400 Subject: [PATCH 14/96] Change common.methods.function() to this.function() --- .../sources/custom-webhook-events/custom-webhook-events.js | 2 +- .../new-or-modified-customer/new-or-modified-customer.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 45bf4bbfffa4a..7244277926b2e 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -13,7 +13,7 @@ module.exports = { type: 'string[]', label: 'Entities', description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', - options: common.methods.getEntityNames(), + options: this.getEntityNames(), optional: true, }, operations_to_emit: { diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index bae95b7626472..381be2365fabe 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -3,8 +3,8 @@ const common = require('../common') const source_entity = 'Customer' -const supported_operations = common.methods.getSupportedOperations(source_entity) -const supported_operations_description = common.methods.describeOperations(supported_operations) +const supported_operations = this.getSupportedOperations(source_entity) +const supported_operations_description = this.describeOperations(supported_operations) module.exports = { ...common, From 21dbb22522d0ad515ba1e18dafb10758cefcec7d Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 22:11:40 -0400 Subject: [PATCH 15/96] Change describeOperations() to toPastTense() --- components/quickbooks/sources/common.js | 24 +++++++++---------- .../new-or-modified-customer.js | 6 ++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 1becd0b521796..ae4731981ef47 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -48,21 +48,19 @@ module.exports = { getSupportedOperations(entity_name){ return supported_webhook_options[entity_name] }, - describeOperations(operations){ - const descriptive_operation_names = { - Create: 'created', - Update: 'updated', - Merge: 'merged', - Delete: 'deleted', - Void: 'voided', - Emailed: 'emailed', + toPastTense(operations){ + const past_tense_version = { + Create: 'Created', + Update: 'Updated', + Merge: 'Merged', + Delete: 'Deleted', + Void: 'Voided', + Emailed: 'Emailed', } - if(Array.isArray(operations)){ - //creates a string listing the operations for use in the description: e.g. 'created, updated, merged, deleted' - return operations.map(operation => descriptive_operation_names[operation]).join(', ') + return operations.map(operation => past_tense_version[operation]) } else { - return descriptive_operation_names[operations] + return past_tense_version[operations] } }, getEntity(event){ @@ -78,7 +76,7 @@ module.exports = { }) }, emitEvent(event, entity){ - const summary = `${entity.name} ${entity.id} ${this.describeOperations(entity.operation)}` + const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` this.$emit(event.body, {summary}) } }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 381be2365fabe..0424651b9fdcd 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -3,14 +3,14 @@ const common = require('../common') const source_entity = 'Customer' -const supported_operations = this.getSupportedOperations(source_entity) -const supported_operations_description = this.describeOperations(supported_operations) +const supported_operations = common.methods.getSupportedOperations(source_entity) +const supported_operations_list = common.methods.toPastTense(supported_operations).join(', ').toLowerCase() module.exports = { ...common, key: `quickbooks-new-or-modified-customer`, name: `New or Modified Customer (${supported_operations.join(', ')})`, - description: `Emits an event when a Customer is ${supported_operations_description}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, + description: `Emits an event when a Customer is ${supported_operations_list}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: '0.0.1', props: { ...common.props, From 653ec09ce1102d3d6bf8751b0d2e376ba0c0e016 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 22:12:48 -0400 Subject: [PATCH 16/96] Change this.function() back to common.methods.function() --- .../sources/custom-webhook-events/custom-webhook-events.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 7244277926b2e..9b43e1ad97a9b 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -13,13 +13,16 @@ module.exports = { type: 'string[]', label: 'Entities', description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', - options: this.getEntityNames(), + options: common.methods.getEntityNames(), optional: true, }, operations_to_emit: { propDefinition: [quickbooks, 'operations_to_emit'], }, }, + methods:{ + ...common.methods, + }, async run(event) { const entity = event.body.eventNotifications[0].dataChangeEvent.entities[0] const summary = `${entity.name} ${entity.id} ${entity.operation}` From 1029b6f4af64d481618aeebdf7abfd024b78eb3a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 22:22:44 -0400 Subject: [PATCH 17/96] Add spacing, improve comments --- components/quickbooks/quickbooks.app.js | 2 +- components/quickbooks/sources/common.js | 5 +++++ .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index faa3e943f25da..2f9d9aac3ed06 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,4 +1,4 @@ -const QuickBooks = require('node-quickbooks') +// const QuickBooks = require('node-quickbooks') module.exports = { type: "app", diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index ae4731981ef47..d6ebd6544d7a8 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -45,9 +45,11 @@ module.exports = { getEntityNames(){ return Object.keys(supported_webhook_options) }, + getSupportedOperations(entity_name){ return supported_webhook_options[entity_name] }, + toPastTense(operations){ const past_tense_version = { Create: 'Created', @@ -63,9 +65,11 @@ module.exports = { return past_tense_version[operations] } }, + getEntity(event){ return event.body.eventNotifications[0].dataChangeEvent.entities[0] }, + sendHttpResponse(event, entity){ this.http.respond({ status: 200, @@ -75,6 +79,7 @@ module.exports = { }, }) }, + emitEvent(event, entity){ const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` this.$emit(event.body, {summary}) diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 0424651b9fdcd..dcd1c0f59ecc1 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -16,7 +16,7 @@ module.exports = { ...common.props, operations_to_emit: { propDefinition: [quickbooks, 'operations_to_emit'], - //list only the options supported by this source's entity instead of offering all the default options from the propDefinition + //overwrite the default options from the propDefinition to list only the options supported by this source's entity options: supported_operations, }, }, From 3e8506d746cc193d7c0bb70f52cc2253151b1b57 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 16 Jul 2021 22:34:56 -0400 Subject: [PATCH 18/96] Add common.js methods to custom source file --- .../custom-webhook-events.js | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 9b43e1ad97a9b..0ae878e498ba3 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -24,24 +24,16 @@ module.exports = { ...common.methods, }, async run(event) { - const entity = event.body.eventNotifications[0].dataChangeEvent.entities[0] - const summary = `${entity.name} ${entity.id} ${entity.operation}` - - this.http.respond({ - status: 200, - body: entity, - headers: { - 'Content-Type': event.headers['Content-Type'], - }, - }); + const webhook_entity = this.getEntity(event) + this.sendHttpResponse(event, webhook_entity) //reject any events that don't match the entity name or operation (if those options have been selected) - if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ - console.log(`Name '${entity.name}' not found in list of selected Entity Types`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ - console.log(`Operation '${entity.operation}' not found in list of selected Operations`) + if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(webhook_entity.name)){ + console.log(`Entity Type '${webhook_entity.name}' not found in list of selected Entity Types`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ + console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) } else { - this.$emit(event.body, {summary}) + this.emitEvent(event, webhook_entity) } }, }; From 101fc1b8f5bc7f73b659b39168f5522481bd27b5 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 07:25:02 -0400 Subject: [PATCH 19/96] Add function to create lists with an 'or' at the end --- components/quickbooks/sources/common.js | 19 +++++++++++++++++++ .../custom-webhook-events.js | 4 ++-- .../new-or-modified-customer.js | 6 +++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index d6ebd6544d7a8..2ea68970a4922 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -50,6 +50,10 @@ module.exports = { return supported_webhook_options[entity_name] }, + getOperationsDescription(operations){ + return this.toReadableList(this.toPastTense(operations)) + }, + toPastTense(operations){ const past_tense_version = { Create: 'Created', @@ -66,6 +70,21 @@ module.exports = { } }, + toReadableList(array){ + // converts an array to a readable list like this: ['Created', 'Updated', 'Merged'] => 'Created, Updated or Merged' + const comma_separated_list = array.join(', ') + const index_of_last_comma = comma_separated_list.lastIndexOf(',') + if(index_of_last_comma !== -1){ + //replace the last comma with an 'or' + const before_last_comma = comma_separated_list.substring(0, index_of_last_comma) + const after_last_comma = comma_separated_list.substring(index_of_last_comma + 1) + return before_last_comma + ' or' + after_last_comma + } else { + //no commas were found so just return the list + return comma_separated_list + } + }, + getEntity(event){ return event.body.eventNotifications[0].dataChangeEvent.entities[0] }, diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 0ae878e498ba3..33798435aa4b0 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -4,8 +4,8 @@ const common = require('../common') module.exports = { ...common, key: 'quickbooks-custom-webhook-events', - name: 'Custom Set of Webhook Entities (Create, Update, Merge, Delete, Void, Emailed)', - description: 'Specify your own combination of entities and operations (i.e. Created or Updated Customers and Vendors) to emit an event for each matching webhook request. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', + name: 'Custom Set of Webhook Entities (Created, Updated, Merged, Deleted, Voided or Emailed)', + description: 'Use this if you want your source to emit more than one type of entity (e.g. "Emailed Invoices and Purchase Orders" or "New and Modified Customers and Vendors"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', version: '0.0.1', props: { ...common.props, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index dcd1c0f59ecc1..d93cf18bdc285 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -4,13 +4,13 @@ const common = require('../common') const source_entity = 'Customer' const supported_operations = common.methods.getSupportedOperations(source_entity) -const supported_operations_list = common.methods.toPastTense(supported_operations).join(', ').toLowerCase() +const supported_operations_list = common.methods.getOperationsDescription(supported_operations) module.exports = { ...common, key: `quickbooks-new-or-modified-customer`, - name: `New or Modified Customer (${supported_operations.join(', ')})`, - description: `Emits an event when a Customer is ${supported_operations_list}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, + name: `New or Modified Customer (${supported_operations_list})`, + description: `Emits an event when a Customer is ${supported_operations_list.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: '0.0.1', props: { ...common.props, From 99394413dbd0c9a5a67e7a9a6b3679af3d331084 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 15:29:14 -0400 Subject: [PATCH 20/96] Revise descriptions to start with 'Emit' --- .../sources/custom-webhook-events/custom-webhook-events.js | 2 +- .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 33798435aa4b0..4ff516091148a 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -5,7 +5,7 @@ module.exports = { ...common, key: 'quickbooks-custom-webhook-events', name: 'Custom Set of Webhook Entities (Created, Updated, Merged, Deleted, Voided or Emailed)', - description: 'Use this if you want your source to emit more than one type of entity (e.g. "Emailed Invoices and Purchase Orders" or "New and Modified Customers and Vendors"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', + description: 'Emit events for more than one type of entity (e.g. "Emailed Invoices and Purchase Orders" or "New and Modified Customers and Vendors"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', version: '0.0.1', props: { ...common.props, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index d93cf18bdc285..932e862040918 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -10,7 +10,7 @@ module.exports = { ...common, key: `quickbooks-new-or-modified-customer`, name: `New or Modified Customer (${supported_operations_list})`, - description: `Emits an event when a Customer is ${supported_operations_list.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, + description: `Emit Customers that are ${supported_operations_list.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: '0.0.1', props: { ...common.props, From 7e67c78a51cbeb1133c8a77f0e0bb9ebf5bc1763 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 16:43:59 -0400 Subject: [PATCH 21/96] Add default values for operations and convert entity prop to a propDefinition --- components/quickbooks/quickbooks.app.js | 51 ++++++++++++++++++- .../custom-webhook-events.js | 8 +-- .../new-or-modified-customer.js | 3 +- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 2f9d9aac3ed06..3abb814cea425 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,14 +1,61 @@ // const QuickBooks = require('node-quickbooks') +const WEBHOOK_OPERATIONS = [ + 'Create', + 'Update', + 'Merge', + 'Delete', + 'Void', + 'Emailed', +] + module.exports = { type: "app", app: "quickbooks", propDefinitions: { - operations_to_emit: { + webhook_entities: { + type: 'string[]', + label: 'Entities', + description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', + options: [ + 'Account', + 'Bill', + 'BillPayment', + 'Budget', + 'Class', + 'CreditMemo', + 'Currency', + 'Customer', + 'Department', + 'Deposit', + 'Employee', + 'Estimate', + 'Invoice', + 'Item', + 'JournalCode', + 'JournalEntry', + 'Payment', + 'PaymentMethod', + 'Preferences', + 'Purchase', + 'PurchaseOrder', + 'RefundReceipt', + 'SalesReceipt', + 'TaxAgency', + 'Term', + 'TimeActivity', + 'Transfer', + 'Vendor', + 'VendorCredit', + ], + optional: true, + }, + webhook_operations: { type: 'string[]', label: 'Operations', description: 'Select which operations to emit or just leave it blank to emit them all.', - options: ['Create', 'Update', 'Merge', 'Delete', 'Void', 'Emailed'], + options: WEBHOOK_OPERATIONS, + default: WEBHOOK_OPERATIONS, optional: true, }, }, diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 4ff516091148a..47c17396775ba 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -10,14 +10,10 @@ module.exports = { props: { ...common.props, names_to_emit: { - type: 'string[]', - label: 'Entities', - description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', - options: common.methods.getEntityNames(), - optional: true, + propDefinition: [quickbooks, 'webhook_entities'], }, operations_to_emit: { - propDefinition: [quickbooks, 'operations_to_emit'], + propDefinition: [quickbooks, 'webhook_operations'], }, }, methods:{ diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 932e862040918..749c5c9ef5ab1 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -15,9 +15,10 @@ module.exports = { props: { ...common.props, operations_to_emit: { - propDefinition: [quickbooks, 'operations_to_emit'], + propDefinition: [quickbooks, 'webhook_operations'], //overwrite the default options from the propDefinition to list only the options supported by this source's entity options: supported_operations, + default: supported_operations, }, }, methods: { From 6f0d455e181907c0bdeda61f358527c9fdd13334 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 17:01:26 -0400 Subject: [PATCH 22/96] Add oxford comma to readable lists --- components/quickbooks/sources/common.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 2ea68970a4922..26cfe477dd4d9 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -71,17 +71,17 @@ module.exports = { }, toReadableList(array){ - // converts an array to a readable list like this: ['Created', 'Updated', 'Merged'] => 'Created, Updated or Merged' + // converts an array to a readable list like this: ['Created', 'Updated', 'Merged'] => 'Created, Updated, or Merged' const comma_separated_list = array.join(', ') - const index_of_last_comma = comma_separated_list.lastIndexOf(',') - if(index_of_last_comma !== -1){ - //replace the last comma with an 'or' - const before_last_comma = comma_separated_list.substring(0, index_of_last_comma) - const after_last_comma = comma_separated_list.substring(index_of_last_comma + 1) - return before_last_comma + ' or' + after_last_comma - } else { + const index_after_last_comma = comma_separated_list.lastIndexOf(',') + 1 + if(index_of_last_comma === -1){ //no commas were found so just return the list return comma_separated_list + } else { + //add an 'or' after the last comma + const before_last_comma = comma_separated_list.slice(0, index_after_last_comma) + const after_last_comma = comma_separated_list.slice(index_after_last_comma) + return before_last_comma + ' or' + after_last_comma } }, From d49208e49cebb1a4241005fd138bf260ad8a06bc Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 18:42:39 -0400 Subject: [PATCH 23/96] Fix outdated variable name --- components/quickbooks/sources/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 26cfe477dd4d9..8d0e8df3c6b7f 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -74,7 +74,7 @@ module.exports = { // converts an array to a readable list like this: ['Created', 'Updated', 'Merged'] => 'Created, Updated, or Merged' const comma_separated_list = array.join(', ') const index_after_last_comma = comma_separated_list.lastIndexOf(',') + 1 - if(index_of_last_comma === -1){ + if(index_after_last_comma === -1){ //no commas were found so just return the list return comma_separated_list } else { From d0e85093d178b799247379d200d21db8de863e5d Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 18:43:08 -0400 Subject: [PATCH 24/96] Change webhook_entities to webhook_names --- components/quickbooks/quickbooks.app.js | 2 +- .../sources/custom-webhook-events/custom-webhook-events.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 3abb814cea425..402ade11e57e3 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -13,7 +13,7 @@ module.exports = { type: "app", app: "quickbooks", propDefinitions: { - webhook_entities: { + webhook_names: { type: 'string[]', label: 'Entities', description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 47c17396775ba..992627a75a5d8 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -10,7 +10,7 @@ module.exports = { props: { ...common.props, names_to_emit: { - propDefinition: [quickbooks, 'webhook_entities'], + propDefinition: [quickbooks, 'webhook_names'], }, operations_to_emit: { propDefinition: [quickbooks, 'webhook_operations'], From c77669969473f4120879b41094b04b46d54e9768 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 18:44:04 -0400 Subject: [PATCH 25/96] Move run function to common.js and create validateAndEmit() --- components/quickbooks/sources/common.js | 5 ++++ .../custom-webhook-events.js | 22 ++++++++---------- .../new-or-modified-customer.js | 23 ++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 8d0e8df3c6b7f..2dff3d05a225a 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -104,4 +104,9 @@ module.exports = { this.$emit(event.body, {summary}) } }, + async run(event) { + const webhook_entity = this.getEntity(event) + this.sendHttpResponse(event, webhook_entity) + this.validateAndEmit(event, webhook_entity) + }, } diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 992627a75a5d8..130ec424ba23f 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -18,18 +18,16 @@ module.exports = { }, methods:{ ...common.methods, + validateAndEmit(event, webhook_entity){ + //reject any events that don't match the entity name or operation (if those options have been selected) + if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(webhook_entity.name)){ + console.log(`Entity Type '${webhook_entity.name}' not found in list of selected Entity Types`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ + console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) + } else { + this.emitEvent(event, webhook_entity) + } + } }, - async run(event) { - const webhook_entity = this.getEntity(event) - this.sendHttpResponse(event, webhook_entity) - //reject any events that don't match the entity name or operation (if those options have been selected) - if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(webhook_entity.name)){ - console.log(`Entity Type '${webhook_entity.name}' not found in list of selected Entity Types`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ - console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) - } else { - this.emitEvent(event, webhook_entity) - } - }, }; diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 749c5c9ef5ab1..f8a10626ca4c4 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -23,18 +23,15 @@ module.exports = { }, methods: { ...common.methods, - }, - async run(event) { - const webhook_entity = this.getEntity(event) - this.sendHttpResponse(event, webhook_entity) - - //reject any events that don't match the specified entity name or operation - if(webhook_entity.name !== source_entity){ - console.log(`${webhook_entity.name} webhook received and ignored, since it is not a Customer`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ - console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) - } else { - this.emitEvent(event, webhook_entity) - } + validateAndEmit(event, webhook_entity){ + //reject any events that don't match the specified entity name or operation + if(webhook_entity.name !== source_entity){ + console.log(`${webhook_entity.name} webhook received and ignored, since it is not a Customer`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ + console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) + } else { + this.emitEvent(event, webhook_entity) + } + }, }, }; From 36d30db1aa748350192179f3d46d8061ab02a232 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 19:20:08 -0400 Subject: [PATCH 26/96] Add handling for multiple entities per webhook request --- components/quickbooks/sources/common.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 2dff3d05a225a..ad51ae0dfb6ca 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -85,14 +85,10 @@ module.exports = { } }, - getEntity(event){ - return event.body.eventNotifications[0].dataChangeEvent.entities[0] - }, - - sendHttpResponse(event, entity){ + sendHttpResponse(event, entities){ this.http.respond({ status: 200, - body: entity, + body: entities, headers: { 'Content-Type': event.headers['Content-Type'], }, @@ -105,8 +101,8 @@ module.exports = { } }, async run(event) { - const webhook_entity = this.getEntity(event) - this.sendHttpResponse(event, webhook_entity) - this.validateAndEmit(event, webhook_entity) + const {entities} = event.body.eventNotifications[0].dataChangeEvent + this.sendHttpResponse(event, entities) + entities.forEach(entity => this.validateAndEmit(event, entity)) }, } From 215cfaa4af4b8eb5fd726b2e41642f7189d20232 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 17 Jul 2021 19:20:46 -0400 Subject: [PATCH 27/96] Rename webhook-entity to entity --- .../custom-webhook-events/custom-webhook-events.js | 12 ++++++------ .../new-or-modified-customer.js | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 130ec424ba23f..91e6ab70106dc 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -18,14 +18,14 @@ module.exports = { }, methods:{ ...common.methods, - validateAndEmit(event, webhook_entity){ + validateAndEmit(event, entity){ //reject any events that don't match the entity name or operation (if those options have been selected) - if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(webhook_entity.name)){ - console.log(`Entity Type '${webhook_entity.name}' not found in list of selected Entity Types`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ - console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) + if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ + console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ + console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, webhook_entity) + this.emitEvent(event, entity) } } }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index f8a10626ca4c4..8b05abb8b0f60 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -23,14 +23,14 @@ module.exports = { }, methods: { ...common.methods, - validateAndEmit(event, webhook_entity){ + validateAndEmit(event, entity){ //reject any events that don't match the specified entity name or operation - if(webhook_entity.name !== source_entity){ - console.log(`${webhook_entity.name} webhook received and ignored, since it is not a Customer`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(webhook_entity.operation)){ - console.log(`Operation '${webhook_entity.operation}' not found in list of selected Operations`) + if(entity.name !== source_entity){ + console.log(`${entity.name} webhook received and ignored, since it is not a Customer`) + } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ + console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, webhook_entity) + this.emitEvent(event, entity) } }, }, From af5eb916687e19e2d663c9cf2899e41d9add73b9 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 18 Jul 2021 15:29:43 -0400 Subject: [PATCH 28/96] Minor code clean-up --- components/quickbooks/sources/common.js | 18 ++++++++---------- .../custom-webhook-events.js | 1 - 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index ad51ae0dfb6ca..f6a301bac1980 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -42,9 +42,9 @@ module.exports = { }, }, methods: { - getEntityNames(){ - return Object.keys(supported_webhook_options) - }, + // getEntityNames(){ + // return Object.keys(supported_webhook_options) + // }, getSupportedOperations(entity_name){ return supported_webhook_options[entity_name] @@ -72,16 +72,14 @@ module.exports = { toReadableList(array){ // converts an array to a readable list like this: ['Created', 'Updated', 'Merged'] => 'Created, Updated, or Merged' - const comma_separated_list = array.join(', ') - const index_after_last_comma = comma_separated_list.lastIndexOf(',') + 1 - if(index_after_last_comma === -1){ + const list = array.join(', ') + const index_after_last_comma = list.lastIndexOf(',') + 1 + if(index_after_last_comma === 0){ //no commas were found so just return the list - return comma_separated_list + return list } else { //add an 'or' after the last comma - const before_last_comma = comma_separated_list.slice(0, index_after_last_comma) - const after_last_comma = comma_separated_list.slice(index_after_last_comma) - return before_last_comma + ' or' + after_last_comma + return list.slice(0, index_after_last_comma) + ' or' + list.slice(index_after_last_comma) } }, diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 91e6ab70106dc..e91661ced4d22 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -29,5 +29,4 @@ module.exports = { } } }, - }; From 15bde79ab1004ed9701a3612fb4dd7317a72a8a2 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 18 Jul 2021 16:37:44 -0400 Subject: [PATCH 29/96] Draft getRecordDetails() - need to figure out how to access auth details --- components/quickbooks/quickbooks.app.js | 14 +++++++++++++- components/quickbooks/sources/common.js | 5 +++-- .../custom-webhook-events/custom-webhook-events.js | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 402ade11e57e3..5884946d80c9c 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,4 +1,5 @@ // const QuickBooks = require('node-quickbooks') +const axios = require('axios') const WEBHOOK_OPERATIONS = [ 'Create', @@ -60,5 +61,16 @@ module.exports = { }, }, methods: { + async getRecordDetails(endpoint, id){ + // const config = { + // url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint}/${id}`, + // headers: { + // Authorization: `Bearer ${this.$auth.oauth_access_token}`, + // "accept": `application/json`, + // "content-type": `application/json`, + // }, + // } + // return await axios(config) + }, }, -}; \ No newline at end of file +}; diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index f6a301bac1980..a0e39cc005665 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -93,9 +93,10 @@ module.exports = { }) }, - emitEvent(event, entity){ + async emitEvent(event, entity){ + const record_details = await quickbooks.methods.getRecordDetails(entity.name, entity.id) const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` - this.$emit(event.body, {summary}) + this.$emit(record_details, {summary}) } }, async run(event) { diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index e91661ced4d22..c2e39ce202c9f 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -18,7 +18,7 @@ module.exports = { }, methods:{ ...common.methods, - validateAndEmit(event, entity){ + async validateAndEmit(event, entity){ //reject any events that don't match the entity name or operation (if those options have been selected) if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`) From 74ab1226ffdbdec33c9d4bad14d7be714102fca8 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 18 Jul 2021 19:15:36 -0400 Subject: [PATCH 30/96] Remove unnecessary methods property from method call --- components/quickbooks/sources/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index a0e39cc005665..9b09bc8afbaf3 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -94,7 +94,7 @@ module.exports = { }, async emitEvent(event, entity){ - const record_details = await quickbooks.methods.getRecordDetails(entity.name, entity.id) + const record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` this.$emit(record_details, {summary}) } From 70469e5ea9b5a045da30e415834f0dd47b5e50cb Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 18 Jul 2021 19:38:02 -0400 Subject: [PATCH 31/96] Emit full record data instead of just the webhook payload --- components/quickbooks/quickbooks.app.js | 19 ++++++++++--------- components/quickbooks/sources/common.js | 3 ++- .../custom-webhook-events.js | 2 +- .../new-or-modified-customer.js | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 5884946d80c9c..8e66d487ab4cb 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -62,15 +62,16 @@ module.exports = { }, methods: { async getRecordDetails(endpoint, id){ - // const config = { - // url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint}/${id}`, - // headers: { - // Authorization: `Bearer ${this.$auth.oauth_access_token}`, - // "accept": `application/json`, - // "content-type": `application/json`, - // }, - // } - // return await axios(config) + const config = { + url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint.toLowerCase()}/${id}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + "accept": `application/json`, + "content-type": `application/json`, + }, + } + const {data} = await axios(config) + return data }, }, }; diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 9b09bc8afbaf3..2993ccaf864b4 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -97,11 +97,12 @@ module.exports = { const record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` this.$emit(record_details, {summary}) + return record_details } }, async run(event) { const {entities} = event.body.eventNotifications[0].dataChangeEvent this.sendHttpResponse(event, entities) - entities.forEach(entity => this.validateAndEmit(event, entity)) + await Promise.all(entities.map(entity => this.validateAndEmit(event, entity))) }, } diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index c2e39ce202c9f..e7cf224e7ead9 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -25,7 +25,7 @@ module.exports = { } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, entity) + return this.emitEvent(event, entity) } } }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 8b05abb8b0f60..5b8fcefd00ddc 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -30,7 +30,7 @@ module.exports = { } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, entity) + return this.emitEvent(event, entity) } }, }, From 6693df13d74cfe1761abd0a010f4a997cf31d3ef Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 18 Jul 2021 23:12:41 -0400 Subject: [PATCH 32/96] Add webhook verification --- components/quickbooks/quickbooks.app.js | 18 ++++++++++++++++++ components/quickbooks/sources/common.js | 15 +++++++++++---- .../custom-webhook-events.js | 3 +++ .../new-or-modified-customer.js | 3 +++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 8e66d487ab4cb..42c95a93d05cd 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,5 +1,6 @@ // const QuickBooks = require('node-quickbooks') const axios = require('axios') +const {createHmac} = require("crypto"); const WEBHOOK_OPERATIONS = [ 'Create', @@ -59,6 +60,15 @@ module.exports = { default: WEBHOOK_OPERATIONS, optional: true, }, + webhook_verifier_token: { + type: 'string', + label: 'Verifier Token', + description: '[Create an app](https://developer.intuit.com/app/developer/qbo/docs/build-your-first-app) ' + + 'on the Intuit Developer dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). ' + + 'Once you have a [verifier token](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications), ' + + 'fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new app for each different source.', + secret: true, + } }, methods: { async getRecordDetails(endpoint, id){ @@ -73,5 +83,13 @@ module.exports = { const {data} = await axios(config) return data }, + verifyWebhookRequest(token, payload, header){ + const hash = createHmac("sha256", token).update(payload).digest('hex') + const converted_header = Buffer.from(header, 'base64').toString('hex') + // console.log('Payload: ', payload) + // console.log('Header: ', converted_header) + // console.log('Hash: ', hash) + return hash === converted_header + } }, }; diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 2993ccaf864b4..663fb0c8a533d 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -94,10 +94,17 @@ module.exports = { }, async emitEvent(event, entity){ - const record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) - const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` - this.$emit(record_details, {summary}) - return record_details + const token = this.webhook_verifier_token + const payload = event.bodyRaw + const header = event.headers['intuit-signature'] + const isWebhookValid = this.quickbooks.verifyWebhookRequest(token, payload, header) + console.log(isWebhookValid) + if(isWebhookValid){ + const record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) + const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` + this.$emit(record_details, {summary}) + return record_details + } } }, async run(event) { diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index e7cf224e7ead9..5ecf456586f96 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -9,6 +9,9 @@ module.exports = { version: '0.0.1', props: { ...common.props, + webhook_verifier_token: { + propDefinition: [quickbooks, 'webhook_verifier_token'], + }, names_to_emit: { propDefinition: [quickbooks, 'webhook_names'], }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 5b8fcefd00ddc..8d23c690b6906 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -14,6 +14,9 @@ module.exports = { version: '0.0.1', props: { ...common.props, + webhook_verifier_token: { + propDefinition: [quickbooks, 'webhook_verifier_token'], + }, operations_to_emit: { propDefinition: [quickbooks, 'webhook_operations'], //overwrite the default options from the propDefinition to list only the options supported by this source's entity From 4c5abf3f23430c61dda5043362bfadc857cc7a0e Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 07:51:48 -0400 Subject: [PATCH 33/96] Move verifyWebhookRequest() to common.js --- components/quickbooks/quickbooks.app.js | 8 -------- components/quickbooks/sources/common.js | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 42c95a93d05cd..d2594bb25c23d 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -83,13 +83,5 @@ module.exports = { const {data} = await axios(config) return data }, - verifyWebhookRequest(token, payload, header){ - const hash = createHmac("sha256", token).update(payload).digest('hex') - const converted_header = Buffer.from(header, 'base64').toString('hex') - // console.log('Payload: ', payload) - // console.log('Header: ', converted_header) - // console.log('Hash: ', hash) - return hash === converted_header - } }, }; diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 663fb0c8a533d..61a4d0522ace6 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -92,6 +92,11 @@ module.exports = { }, }) }, + + async validateAndEmit(event, entity){ + //individual source modules can redefine this method to specify criteria for which events to emit + return this.emitEvent(event, entity) + }, async emitEvent(event, entity){ const token = this.webhook_verifier_token @@ -105,7 +110,16 @@ module.exports = { this.$emit(record_details, {summary}) return record_details } - } + }, + + verifyWebhookRequest(token, payload, header){ + const hash = createHmac("sha256", token).update(payload).digest('hex') + const converted_header = Buffer.from(header, 'base64').toString('hex') + // console.log('Payload: ', payload) + // console.log('Header: ', converted_header) + // console.log('Hash: ', hash) + return hash === converted_header + }, }, async run(event) { const {entities} = event.body.eventNotifications[0].dataChangeEvent From ae711d9e8e0fbc0acb0fcaebbfd03a07725f5378 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 07:52:16 -0400 Subject: [PATCH 34/96] Clean up comments --- components/quickbooks/sources/common.js | 4 ---- .../sources/custom-webhook-events/custom-webhook-events.js | 4 +++- .../new-or-modified-customer/new-or-modified-customer.js | 4 +++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 61a4d0522ace6..1a6a9c256ab1c 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -42,10 +42,6 @@ module.exports = { }, }, methods: { - // getEntityNames(){ - // return Object.keys(supported_webhook_options) - // }, - getSupportedOperations(entity_name){ return supported_webhook_options[entity_name] }, diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 5ecf456586f96..498ca2e990fe2 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -22,7 +22,9 @@ module.exports = { methods:{ ...common.methods, async validateAndEmit(event, entity){ - //reject any events that don't match the entity name or operation (if those options have been selected) + // only emit events that match the entity names and operations indicated by the user + // but if the props are left empty, emit all events rather than filtering them all out + // (it would a hassle for the user to select every single option if they wanted to emit everything) if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`) } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 8d23c690b6906..7524718cbecd6 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -27,7 +27,9 @@ module.exports = { methods: { ...common.methods, validateAndEmit(event, entity){ - //reject any events that don't match the specified entity name or operation + //only emit events that match the specified entity name and operation + // but if the operations prop is left empty, emit all events rather than filtering them all out + // (it would a hassle for the user to select every single option if they wanted to emit everything) if(entity.name !== source_entity){ console.log(`${entity.name} webhook received and ignored, since it is not a Customer`) } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ From fd3cc0d6dfeea341c23434c2bcceec31ad7a3616 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 07:54:33 -0400 Subject: [PATCH 35/96] Clean up more comments --- components/quickbooks/sources/common.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 1a6a9c256ab1c..8ef18dc26078b 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -99,7 +99,6 @@ module.exports = { const payload = event.bodyRaw const header = event.headers['intuit-signature'] const isWebhookValid = this.quickbooks.verifyWebhookRequest(token, payload, header) - console.log(isWebhookValid) if(isWebhookValid){ const record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` @@ -112,8 +111,8 @@ module.exports = { const hash = createHmac("sha256", token).update(payload).digest('hex') const converted_header = Buffer.from(header, 'base64').toString('hex') // console.log('Payload: ', payload) - // console.log('Header: ', converted_header) // console.log('Hash: ', hash) + // console.log('Header: ', converted_header) return hash === converted_header }, }, From 8baf73cd1787298ee46da94335ec4178940ddff5 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 08:22:54 -0400 Subject: [PATCH 36/96] [Draft] Don't try to get deleted records and check for matching company ids --- components/quickbooks/sources/common.js | 28 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 8ef18dc26078b..d21e8f32ba6cd 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -94,16 +94,30 @@ module.exports = { return this.emitEvent(event, entity) }, - async emitEvent(event, entity){ + async emitEvent(event_received, entity){ const token = this.webhook_verifier_token - const payload = event.bodyRaw - const header = event.headers['intuit-signature'] - const isWebhookValid = this.quickbooks.verifyWebhookRequest(token, payload, header) + const payload = event_received.bodyRaw + const header = event_received.headers['intuit-signature'] + const isWebhookValid = this.verifyWebhookRequest(token, payload, header) if(isWebhookValid){ - const record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) + // Unless the record has been deleted, use the id received in the webhook to get the full record data + const event_to_emit = { + event_notification: event_received, + record_details: {}, + } + if(event_received.operation !== 'Delete'){ + const webhook_company_id = event_received.body.eventNotifications[0].realmId + const connected_company_id = this.quickbooks.$auth.webhook_company_id + if(webhook_company_id === connected_company_id){ + console.log('Company ID: ', webhook_company_id) + source_event.record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) + } else { + console.log('Error: Ids do not match. ', webhook_company_id, connected_company_id) + } + } const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` - this.$emit(record_details, {summary}) - return record_details + this.$emit(event_to_emit, {summary}) + return source_event.record_details } }, From c5aa467ac2dfce011a161709015412b8d0f3abf5 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 09:15:37 -0400 Subject: [PATCH 37/96] Move crypto to common.js --- components/quickbooks/quickbooks.app.js | 1 - components/quickbooks/sources/common.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index d2594bb25c23d..4698aebf7783e 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,6 +1,5 @@ // const QuickBooks = require('node-quickbooks') const axios = require('axios') -const {createHmac} = require("crypto"); const WEBHOOK_OPERATIONS = [ 'Create', diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index d21e8f32ba6cd..6b2a3efb2adf4 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -1,4 +1,5 @@ const quickbooks = require('../quickbooks.app'); +const {createHmac} = require("crypto"); //https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/entities-and-operations-supported const supported_webhook_options = { From f60b3e9e977e6a09cd9da1a54b652e6a5f3ad00f Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 09:17:38 -0400 Subject: [PATCH 38/96] Add error handling to verifyWebhookRequest() --- components/quickbooks/sources/common.js | 40 ++++++++++++++----------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 6b2a3efb2adf4..2ab644307a505 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -100,25 +100,31 @@ module.exports = { const payload = event_received.bodyRaw const header = event_received.headers['intuit-signature'] const isWebhookValid = this.verifyWebhookRequest(token, payload, header) - if(isWebhookValid){ - // Unless the record has been deleted, use the id received in the webhook to get the full record data - const event_to_emit = { - event_notification: event_received, - record_details: {}, - } - if(event_received.operation !== 'Delete'){ - const webhook_company_id = event_received.body.eventNotifications[0].realmId - const connected_company_id = this.quickbooks.$auth.webhook_company_id - if(webhook_company_id === connected_company_id){ - console.log('Company ID: ', webhook_company_id) - source_event.record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) - } else { - console.log('Error: Ids do not match. ', webhook_company_id, connected_company_id) + if(!isWebhookValid){ + const message = `Error: Webhook did not pass verification. Try reentering the verify token, making sure it's from the correct webhook on the Intuit Developer Dashboard.` + console.log(message) + throw new Error(message) + } else { + const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` + const webhook_company_id = event_received.body.eventNotifications[0].realmId + const connected_company_id = this.quickbooks.$auth.company_id + if(webhook_company_id !== connected_company_id){ + const message = `Error: Cannot retrieve record details for ${summary}. The QuickBooks company id + of the incoming event (${webhook_company_id}) does not match the company id of the account + currently connected to this source in Pipedream (${connected_company_id}).` + console.log(message) + throw new Error(message) + } else{ + const event_to_emit = { + event_notification: event_received, + record_details: {}, } + // Unless the record has been deleted, use the id received in the webhook to get the full record data + if(event_received.operation !== 'Delete'){ + event_to_emit.record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) + } + this.$emit(event_to_emit, {summary}) } - const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` - this.$emit(event_to_emit, {summary}) - return source_event.record_details } }, From f969c4de7f2e35417b74365954f85e0b780abfa3 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Tue, 20 Jul 2021 09:18:09 -0400 Subject: [PATCH 39/96] Remove unnecessary return values and asyncs --- components/quickbooks/sources/common.js | 2 +- .../sources/custom-webhook-events/custom-webhook-events.js | 6 +++--- .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 2ab644307a505..8b59124e3734f 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -92,7 +92,7 @@ module.exports = { async validateAndEmit(event, entity){ //individual source modules can redefine this method to specify criteria for which events to emit - return this.emitEvent(event, entity) + await this.emitEvent(event, entity) }, async emitEvent(event_received, entity){ diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 498ca2e990fe2..f0b057367054c 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -21,7 +21,7 @@ module.exports = { }, methods:{ ...common.methods, - async validateAndEmit(event, entity){ + validateAndEmit(event, entity){ // only emit events that match the entity names and operations indicated by the user // but if the props are left empty, emit all events rather than filtering them all out // (it would a hassle for the user to select every single option if they wanted to emit everything) @@ -30,8 +30,8 @@ module.exports = { } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - return this.emitEvent(event, entity) + this.emitEvent(event, entity) } - } + } }, }; diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 7524718cbecd6..e311e62b8cac1 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -35,7 +35,7 @@ module.exports = { } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - return this.emitEvent(event, entity) + this.emitEvent(event, entity) } }, }, From 5303b4d9fd48405efafb3db540d8adb909236945 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 23 Jul 2021 21:22:35 -0400 Subject: [PATCH 40/96] Tweak error message and prop description --- components/quickbooks/quickbooks.app.js | 4 ++-- components/quickbooks/sources/common.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 4698aebf7783e..80c57f358583a 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -63,9 +63,9 @@ module.exports = { type: 'string', label: 'Verifier Token', description: '[Create an app](https://developer.intuit.com/app/developer/qbo/docs/build-your-first-app) ' + - 'on the Intuit Developer dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). ' + + 'on the Intuit Developer Dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). ' + 'Once you have a [verifier token](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications), ' + - 'fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new app for each different source.', + 'fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new Dashboard app for each different source.', secret: true, } }, diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 8b59124e3734f..ff142547541e6 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -101,7 +101,8 @@ module.exports = { const header = event_received.headers['intuit-signature'] const isWebhookValid = this.verifyWebhookRequest(token, payload, header) if(!isWebhookValid){ - const message = `Error: Webhook did not pass verification. Try reentering the verify token, making sure it's from the correct webhook on the Intuit Developer Dashboard.` + const message = `Error: Webhook did not pass verification. Try reentering the verify token, + making sure it's from the correct section on the Intuit Developer Dashboard.` console.log(message) throw new Error(message) } else { From 2e7c48cbf235d6ce9ab8ee802cb1696f711aa71c Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 23 Jul 2021 21:45:03 -0400 Subject: [PATCH 41/96] await this.emitEvent() --- .../sources/custom-webhook-events/custom-webhook-events.js | 4 ++-- .../new-or-modified-customer/new-or-modified-customer.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index f0b057367054c..be037e8bff8eb 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -21,7 +21,7 @@ module.exports = { }, methods:{ ...common.methods, - validateAndEmit(event, entity){ + async validateAndEmit(event, entity){ // only emit events that match the entity names and operations indicated by the user // but if the props are left empty, emit all events rather than filtering them all out // (it would a hassle for the user to select every single option if they wanted to emit everything) @@ -30,7 +30,7 @@ module.exports = { } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, entity) + await this.emitEvent(event, entity) } } }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index e311e62b8cac1..0db66149021a8 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -26,7 +26,7 @@ module.exports = { }, methods: { ...common.methods, - validateAndEmit(event, entity){ + async validateAndEmit(event, entity){ //only emit events that match the specified entity name and operation // but if the operations prop is left empty, emit all events rather than filtering them all out // (it would a hassle for the user to select every single option if they wanted to emit everything) @@ -35,7 +35,7 @@ module.exports = { } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ console.log(`Operation '${entity.operation}' not found in list of selected Operations`) } else { - this.emitEvent(event, entity) + await this.emitEvent(event, entity) } }, }, From b36d12efe8b143b4edc6e9c402057da18802a259 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 23 Jul 2021 22:11:21 -0400 Subject: [PATCH 42/96] Turn off secret for webhook_verifier_token for testing --- components/quickbooks/quickbooks.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 80c57f358583a..4435c791cd8fb 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -66,7 +66,7 @@ module.exports = { 'on the Intuit Developer Dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). ' + 'Once you have a [verifier token](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications), ' + 'fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new Dashboard app for each different source.', - secret: true, + // secret: true, } }, methods: { From 6f3d9a9571fbe9b22cae59c402cab5a0ee94d87f Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 23 Jul 2021 22:19:24 -0400 Subject: [PATCH 43/96] Set webhook_verifier_token back to secret --- components/quickbooks/quickbooks.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 4435c791cd8fb..80c57f358583a 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -66,7 +66,7 @@ module.exports = { 'on the Intuit Developer Dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). ' + 'Once you have a [verifier token](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications), ' + 'fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new Dashboard app for each different source.', - // secret: true, + secret: true, } }, methods: { From aeb31be5b8411b34b4813bd24d1ee0665121f777 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Fri, 23 Jul 2021 22:19:52 -0400 Subject: [PATCH 44/96] Fix wrong reference in checking for deleted entity --- components/quickbooks/sources/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index ff142547541e6..74213f293b5ac 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -121,7 +121,7 @@ module.exports = { record_details: {}, } // Unless the record has been deleted, use the id received in the webhook to get the full record data - if(event_received.operation !== 'Delete'){ + if(entity.operation !== 'Delete'){ event_to_emit.record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) } this.$emit(event_to_emit, {summary}) From 9e17d3f47b5a8de40beca13dc1aa07f00508716d Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 25 Jul 2021 10:43:33 -0400 Subject: [PATCH 45/96] Change verify to verifier on error message --- components/quickbooks/sources/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 74213f293b5ac..2cfdb9f1b225c 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -101,7 +101,7 @@ module.exports = { const header = event_received.headers['intuit-signature'] const isWebhookValid = this.verifyWebhookRequest(token, payload, header) if(!isWebhookValid){ - const message = `Error: Webhook did not pass verification. Try reentering the verify token, + const message = `Error: Webhook did not pass verification. Try reentering the verifier token, making sure it's from the correct section on the Intuit Developer Dashboard.` console.log(message) throw new Error(message) From 04cdc8cbe39c9d1abf590d499df6e84143ca37b8 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 7 Aug 2021 22:05:26 -0400 Subject: [PATCH 46/96] Emit just the entity (plus realmID) instead of the full HTTP request --- components/quickbooks/sources/common.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 2cfdb9f1b225c..ccdc796044f95 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -116,8 +116,9 @@ module.exports = { console.log(message) throw new Error(message) } else{ + entity.realmId = webhook_company_id const event_to_emit = { - event_notification: event_received, + event_notification: entity, record_details: {}, } // Unless the record has been deleted, use the id received in the webhook to get the full record data From 50ac040d9b897009b0892f71dd4114e18aa8cddf Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 19:21:49 -0400 Subject: [PATCH 47/96] Fix ESLint errors --- components/quickbooks/quickbooks.app.js | 114 +++--- components/quickbooks/sources/common.js | 331 +++++++++++++----- .../custom-webhook-events.js | 46 ++- .../new-or-modified-customer.js | 58 +-- 4 files changed, 353 insertions(+), 196 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 80c57f358583a..a872a798c09a8 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,86 +1,86 @@ // const QuickBooks = require('node-quickbooks') -const axios = require('axios') +const axios = require("axios"); const WEBHOOK_OPERATIONS = [ - 'Create', - 'Update', - 'Merge', - 'Delete', - 'Void', - 'Emailed', -] + "Create", + "Update", + "Merge", + "Delete", + "Void", + "Emailed", +]; module.exports = { type: "app", app: "quickbooks", propDefinitions: { webhook_names: { - type: 'string[]', - label: 'Entities', - description: 'Select which QuickBooks entities to emit or just leave it blank to emit them all.', + type: "string[]", + label: "Entities", + description: "Select which QuickBooks entities to emit or just leave it blank to emit them all.", options: [ - 'Account', - 'Bill', - 'BillPayment', - 'Budget', - 'Class', - 'CreditMemo', - 'Currency', - 'Customer', - 'Department', - 'Deposit', - 'Employee', - 'Estimate', - 'Invoice', - 'Item', - 'JournalCode', - 'JournalEntry', - 'Payment', - 'PaymentMethod', - 'Preferences', - 'Purchase', - 'PurchaseOrder', - 'RefundReceipt', - 'SalesReceipt', - 'TaxAgency', - 'Term', - 'TimeActivity', - 'Transfer', - 'Vendor', - 'VendorCredit', + "Account", + "Bill", + "BillPayment", + "Budget", + "Class", + "CreditMemo", + "Currency", + "Customer", + "Department", + "Deposit", + "Employee", + "Estimate", + "Invoice", + "Item", + "JournalCode", + "JournalEntry", + "Payment", + "PaymentMethod", + "Preferences", + "Purchase", + "PurchaseOrder", + "RefundReceipt", + "SalesReceipt", + "TaxAgency", + "Term", + "TimeActivity", + "Transfer", + "Vendor", + "VendorCredit", ], optional: true, }, webhook_operations: { - type: 'string[]', - label: 'Operations', - description: 'Select which operations to emit or just leave it blank to emit them all.', + type: "string[]", + label: "Operations", + description: "Select which operations to emit or just leave it blank to emit them all.", options: WEBHOOK_OPERATIONS, default: WEBHOOK_OPERATIONS, optional: true, }, webhook_verifier_token: { - type: 'string', - label: 'Verifier Token', - description: '[Create an app](https://developer.intuit.com/app/developer/qbo/docs/build-your-first-app) ' + - 'on the Intuit Developer Dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). ' + - 'Once you have a [verifier token](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications), ' + - 'fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new Dashboard app for each different source.', + type: "string", + label: "Verifier Token", + description: "[Create an app](https://developer.intuit.com/app/developer/qbo/docs/build-your-first-app) " + + "on the Intuit Developer Dashboard and [set up a webhook](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks). " + + "Once you have a [verifier token](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications), " + + "fill it in below. Note that if you want to send webhooks to more than one Pipedream source, you will have to create a new Dashboard app for each different source.", secret: true, - } + }, }, methods: { - async getRecordDetails(endpoint, id){ + async getRecordDetails(endpoint, id) { const config = { url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint.toLowerCase()}/${id}`, headers: { - Authorization: `Bearer ${this.$auth.oauth_access_token}`, - "accept": `application/json`, - "content-type": `application/json`, + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + "accept": "application/json", + "content-type": "application/json", }, - } - const {data} = await axios(config) - return data + }; + const { data } = await axios(config); + return data; }, }, }; diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index ccdc796044f95..1667c8ad47923 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -1,147 +1,284 @@ -const quickbooks = require('../quickbooks.app'); -const {createHmac} = require("crypto"); +const quickbooks = require("../quickbooks.app"); +const { createHmac } = require("crypto"); //https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/entities-and-operations-supported -const supported_webhook_options = { - Account: ['Create', 'Update', 'Merge', 'Delete'], - Bill: ['Create', 'Update', 'Delete'], - BillPayment: ['Create', 'Update', 'Delete', 'Void'], - Budget: ['Create', 'Update'], - Class: ['Create', 'Update', 'Merge', 'Delete'], - CreditMemo: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], - Currency: ['Create', 'Update'], - Customer: ['Create', 'Update', 'Merge', 'Delete'], - Department: ['Create', 'Update', 'Merge'], - Deposit: ['Create', 'Update', 'Delete'], - Employee: ['Create', 'Update', 'Merge', 'Delete'], - Estimate: ['Create', 'Update', 'Delete', 'Emailed'], - Invoice: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], - Item: ['Create', 'Update', 'Merge', 'Delete'], - JournalCode: ['Create', 'Update'], - JournalEntry: ['Create', 'Update', 'Delete'], - Payment: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], - PaymentMethod: ['Create', 'Update', 'Merge'], - Preferences: ['Update'], - Purchase: ['Create', 'Update', 'Delete', 'Void'], - PurchaseOrder: ['Create', 'Update', 'Delete', 'Emailed'], - RefundReceipt: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], - SalesReceipt: ['Create', 'Update', 'Delete', 'Void', 'Emailed'], - TaxAgency: ['Create', 'Update'], - Term: ['Create', 'Update'], - TimeActivity: ['Create', 'Update', 'Delete'], - Transfer: ['Create', 'Update', 'Delete', 'Void'], - Vendor: ['Create', 'Update', 'Merge', 'Delete'], - VendorCredit: ['Create', 'Update', 'Delete'], -} +const supportedWebhookOptions = { + Account: [ + "Create", + "Update", + "Merge", + "Delete", + ], + Bill: [ + "Create", + "Update", + "Delete", + ], + BillPayment: [ + "Create", + "Update", + "Delete", + "Void", + ], + Budget: [ + "Create", + "Update", + ], + Class: [ + "Create", + "Update", + "Merge", + "Delete", + ], + CreditMemo: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + Currency: [ + "Create", + "Update", + ], + Customer: [ + "Create", + "Update", + "Merge", + "Delete", + ], + Department: [ + "Create", + "Update", + "Merge", + ], + Deposit: [ + "Create", + "Update", + "Delete", + ], + Employee: [ + "Create", + "Update", + "Merge", + "Delete", + ], + Estimate: [ + "Create", + "Update", + "Delete", + "Emailed", + ], + Invoice: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + Item: [ + "Create", + "Update", + "Merge", + "Delete", + ], + JournalCode: [ + "Create", + "Update", + ], + JournalEntry: [ + "Create", + "Update", + "Delete", + ], + Payment: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + PaymentMethod: [ + "Create", + "Update", + "Merge", + ], + Preferences: [ + "Update", + ], + Purchase: [ + "Create", + "Update", + "Delete", + "Void", + ], + PurchaseOrder: [ + "Create", + "Update", + "Delete", + "Emailed", + ], + RefundReceipt: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + SalesReceipt: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + TaxAgency: [ + "Create", + "Update", + ], + Term: [ + "Create", + "Update", + ], + TimeActivity: [ + "Create", + "Update", + "Delete", + ], + Transfer: [ + "Create", + "Update", + "Delete", + "Void", + ], + Vendor: [ + "Create", + "Update", + "Merge", + "Delete", + ], + VendorCredit: [ + "Create", + "Update", + "Delete", + ], +}; module.exports = { props: { quickbooks, http: { - type: '$.interface.http', + type: "$.interface.http", customResponse: true, }, }, methods: { - getSupportedOperations(entity_name){ - return supported_webhook_options[entity_name] + getSupportedOperations(entityName) { + return supportedWebhookOptions[entityName]; }, - getOperationsDescription(operations){ - return this.toReadableList(this.toPastTense(operations)) + getOperationsDescription(operations) { + return this.toReadableList(this.toPastTense(operations)); }, - toPastTense(operations){ - const past_tense_version = { - Create: 'Created', - Update: 'Updated', - Merge: 'Merged', - Delete: 'Deleted', - Void: 'Voided', - Emailed: 'Emailed', - } - if(Array.isArray(operations)){ - return operations.map(operation => past_tense_version[operation]) + toPastTense(operations) { + const pastTenseVersion = { + Create: "Created", + Update: "Updated", + Merge: "Merged", + Delete: "Deleted", + Void: "Voided", + Emailed: "Emailed", + }; + if (Array.isArray(operations)) { + return operations.map((operation) => pastTenseVersion[operation]); } else { - return past_tense_version[operations] + return pastTenseVersion[operations]; } }, - toReadableList(array){ - // converts an array to a readable list like this: ['Created', 'Updated', 'Merged'] => 'Created, Updated, or Merged' - const list = array.join(', ') - const index_after_last_comma = list.lastIndexOf(',') + 1 - if(index_after_last_comma === 0){ + toReadableList(array) { + // converts an array to a readable list like this: + // ['Created', 'Updated', 'Merged'] => 'Created, Updated, or Merged' + const list = array.join(", "); + const indexAfterLastComma = list.lastIndexOf(",") + 1; + if (indexAfterLastComma === 0) { //no commas were found so just return the list - return list + return list; } else { //add an 'or' after the last comma - return list.slice(0, index_after_last_comma) + ' or' + list.slice(index_after_last_comma) + return list.slice(0, indexAfterLastComma) + " or" + list.slice(indexAfterLastComma); } }, - sendHttpResponse(event, entities){ + sendHttpResponse(event, entities) { this.http.respond({ status: 200, body: entities, headers: { - 'Content-Type': event.headers['Content-Type'], + "Content-Type": event.headers["Content-Type"], }, - }) + }); }, - async validateAndEmit(event, entity){ - //individual source modules can redefine this method to specify criteria for which events to emit - await this.emitEvent(event, entity) + async validateAndEmit(event, entity) { + // individual source modules can redefine this method to specify criteria + // for which events to emit + await this.emitEvent(event, entity); }, - - async emitEvent(event_received, entity){ - const token = this.webhook_verifier_token - const payload = event_received.bodyRaw - const header = event_received.headers['intuit-signature'] - const isWebhookValid = this.verifyWebhookRequest(token, payload, header) - if(!isWebhookValid){ + + async emitEvent(eventReceived, entity) { + const token = this.webhook_verifier_token; + const payload = eventReceived.bodyRaw; + const header = eventReceived.headers["intuit-signature"]; + const isWebhookValid = this.verifyWebhookRequest(token, payload, header); + if (!isWebhookValid) { const message = `Error: Webhook did not pass verification. Try reentering the verifier token, - making sure it's from the correct section on the Intuit Developer Dashboard.` - console.log(message) - throw new Error(message) + making sure it's from the correct section on the Intuit Developer Dashboard.`; + console.log(message); + throw new Error(message); } else { - const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}` - const webhook_company_id = event_received.body.eventNotifications[0].realmId - const connected_company_id = this.quickbooks.$auth.company_id - if(webhook_company_id !== connected_company_id){ + const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}`; + const webhookCompanyId = eventReceived.body.eventNotifications[0].realmId; + const connectedCompanyId = this.quickbooks.$auth.company_id; + if (webhookCompanyId !== connectedCompanyId) { const message = `Error: Cannot retrieve record details for ${summary}. The QuickBooks company id - of the incoming event (${webhook_company_id}) does not match the company id of the account - currently connected to this source in Pipedream (${connected_company_id}).` - console.log(message) - throw new Error(message) - } else{ - entity.realmId = webhook_company_id - const event_to_emit = { + of the incoming event (${webhookCompanyId}) does not match the company id of the account + currently connected to this source in Pipedream (${connectedCompanyId}).`; + console.log(message); + throw new Error(message); + } else { + entity.realmId = webhookCompanyId; + const eventToEmit = { event_notification: entity, record_details: {}, + }; + // Unless the record has been deleted, use the id received in the webhook + // to get the full record data + if (entity.operation !== "Delete") { + eventToEmit.record_details = await this.quickbooks + .getRecordDetails(entity.name, entity.id); } - // Unless the record has been deleted, use the id received in the webhook to get the full record data - if(entity.operation !== 'Delete'){ - event_to_emit.record_details = await this.quickbooks.getRecordDetails(entity.name, entity.id) - } - this.$emit(event_to_emit, {summary}) + this.$emit(eventToEmit, { + summary, + }); } } }, - verifyWebhookRequest(token, payload, header){ - const hash = createHmac("sha256", token).update(payload).digest('hex') - const converted_header = Buffer.from(header, 'base64').toString('hex') + verifyWebhookRequest(token, payload, header) { + const hash = createHmac("sha256", token).update(payload) + .digest("hex"); + const convertedHeader = Buffer.from(header, "base64").toString("hex"); // console.log('Payload: ', payload) // console.log('Hash: ', hash) // console.log('Header: ', converted_header) - return hash === converted_header + return hash === convertedHeader; }, }, async run(event) { - const {entities} = event.body.eventNotifications[0].dataChangeEvent - this.sendHttpResponse(event, entities) - await Promise.all(entities.map(entity => this.validateAndEmit(event, entity))) + const { entities } = event.body.eventNotifications[0].dataChangeEvent; + this.sendHttpResponse(event, entities); + await Promise.all(entities.map((entity) => this.validateAndEmit(event, entity))); }, -} +}; diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index be037e8bff8eb..865099fc8262a 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -1,37 +1,47 @@ -const quickbooks = require('../../quickbooks.app'); -const common = require('../common') +const quickbooks = require("../../quickbooks.app"); +const common = require("../common"); module.exports = { ...common, - key: 'quickbooks-custom-webhook-events', - name: 'Custom Set of Webhook Entities (Created, Updated, Merged, Deleted, Voided or Emailed)', - description: 'Emit events for more than one type of entity (e.g. "Emailed Invoices and Purchase Orders" or "New and Modified Customers and Vendors"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks', - version: '0.0.1', + key: "quickbooks-custom-webhook-events", + name: "Custom Set of Webhook Entities (Created, Updated, Merged, Deleted, Voided or Emailed)", + description: "Emit events for more than one type of entity (e.g. \"Emailed Invoices and Purchase Orders\" or \"New and Modified Customers and Vendors\"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", + version: "0.0.1", props: { ...common.props, webhook_verifier_token: { - propDefinition: [quickbooks, 'webhook_verifier_token'], + propDefinition: [ + quickbooks, + "webhook_verifier_token", + ], }, names_to_emit: { - propDefinition: [quickbooks, 'webhook_names'], + propDefinition: [ + quickbooks, + "webhook_names", + ], }, operations_to_emit: { - propDefinition: [quickbooks, 'webhook_operations'], + propDefinition: [ + quickbooks, + "webhook_operations", + ], }, }, - methods:{ + methods: { ...common.methods, - async validateAndEmit(event, entity){ + async validateAndEmit(event, entity) { // only emit events that match the entity names and operations indicated by the user // but if the props are left empty, emit all events rather than filtering them all out - // (it would a hassle for the user to select every single option if they wanted to emit everything) - if(this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)){ - console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ - console.log(`Operation '${entity.operation}' not found in list of selected Operations`) + // (it would a hassle for the user to select every option if they wanted to emit everything) + if (this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)) { + console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`); + } else if (this.operations_to_emit.length > 0 + && !this.operations_to_emit.includes(entity.operation)) { + console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { - await this.emitEvent(event, entity) + await this.emitEvent(event, entity); } - } + }, }, }; diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 0db66149021a8..2951a877018e5 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -1,41 +1,51 @@ -const quickbooks = require('../../quickbooks.app'); -const common = require('../common') +const quickbooks = require("../../quickbooks.app"); +const common = require("../common"); -const source_entity = 'Customer' +const sourceEntity = "Customer"; -const supported_operations = common.methods.getSupportedOperations(source_entity) -const supported_operations_list = common.methods.getOperationsDescription(supported_operations) +const supportedOperations = common.methods.getSupportedOperations(sourceEntity); +const supportedOperationsList = common.methods.getOperationsDescription(supportedOperations); module.exports = { ...common, - key: `quickbooks-new-or-modified-customer`, - name: `New or Modified Customer (${supported_operations_list})`, - description: `Emit Customers that are ${supported_operations_list.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, - version: '0.0.1', + key: "quickbooks-new-or-modified-customer", + name: `New or Modified Customer (${supportedOperationsList})`, + description: `Emit Customers that are ${supportedOperationsList.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, + version: "0.0.1", props: { ...common.props, webhook_verifier_token: { - propDefinition: [quickbooks, 'webhook_verifier_token'], + propDefinition: [ + quickbooks, + "webhook_verifier_token", + ], }, operations_to_emit: { - propDefinition: [quickbooks, 'webhook_operations'], - //overwrite the default options from the propDefinition to list only the options supported by this source's entity - options: supported_operations, - default: supported_operations, + propDefinition: [ + quickbooks, + "webhook_operations", + ], + // overwrite the default options from the propDefinition to list only the options supported + // by this source's entity + options: supportedOperations, + default: supportedOperations, }, }, methods: { - ...common.methods, - async validateAndEmit(event, entity){ - //only emit events that match the specified entity name and operation - // but if the operations prop is left empty, emit all events rather than filtering them all out - // (it would a hassle for the user to select every single option if they wanted to emit everything) - if(entity.name !== source_entity){ - console.log(`${entity.name} webhook received and ignored, since it is not a Customer`) - } else if(this.operations_to_emit.length > 0 && !this.operations_to_emit.includes(entity.operation)){ - console.log(`Operation '${entity.operation}' not found in list of selected Operations`) + ...common.methods, + async validateAndEmit(event, entity) { + // only emit events that match the specified entity name and operation + // but if the operations prop is left empty, emit all events rather + // than filtering them all out + // (it would a hassle for the user to select every single option + // if they wanted to emit everything) + if (entity.name !== sourceEntity) { + console.log(`${entity.name} webhook received and ignored, since it is not a Customer`); + } else if (this.operations_to_emit.length > 0 + && !this.operations_to_emit.includes(entity.operation)) { + console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { - await this.emitEvent(event, entity) + await this.emitEvent(event, entity); } }, }, From c730c06e9e9a49a81cf929ef5e2b420566d6792b Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 21:44:22 -0400 Subject: [PATCH 48/96] Begin drafting Download PDF action --- components/quickbooks/actions/download-pdf.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 components/quickbooks/actions/download-pdf.js diff --git a/components/quickbooks/actions/download-pdf.js b/components/quickbooks/actions/download-pdf.js new file mode 100644 index 0000000000000..9bda19b8d4876 --- /dev/null +++ b/components/quickbooks/actions/download-pdf.js @@ -0,0 +1,13 @@ +module.exports = { + name: 'Download PDF', + description: 'Download an invoice, bill, purchase order, etc. as a PDF', + key: 'download_pdf', + version: '0.0.1', + type: 'action', + props: { + }, + methods: {}, + async run(){ + + } +} \ No newline at end of file From de37b8a9997cfef5852cff14523e40dbfcd1f102 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 21:47:26 -0400 Subject: [PATCH 49/96] Move download-pdf to folder --- .../quickbooks/actions/{ => download-pdf}/download-pdf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename components/quickbooks/actions/{ => download-pdf}/download-pdf.js (72%) diff --git a/components/quickbooks/actions/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js similarity index 72% rename from components/quickbooks/actions/download-pdf.js rename to components/quickbooks/actions/download-pdf/download-pdf.js index 9bda19b8d4876..9868c2db07d47 100644 --- a/components/quickbooks/actions/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -1,6 +1,6 @@ module.exports = { name: 'Download PDF', - description: 'Download an invoice, bill, purchase order, etc. as a PDF', + description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', key: 'download_pdf', version: '0.0.1', type: 'action', @@ -8,6 +8,6 @@ module.exports = { }, methods: {}, async run(){ - + } } \ No newline at end of file From 551986bee16a7e51e5fa06b89552ba727838d136 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 21:51:41 -0400 Subject: [PATCH 50/96] Add quickbooks app --- .../actions/download-pdf/download-pdf.js | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 9868c2db07d47..f3eeb287cd8ca 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -1,3 +1,5 @@ +const quickbooks = require("../quickbooks.app"); + module.exports = { name: 'Download PDF', description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', @@ -5,9 +7,39 @@ module.exports = { version: '0.0.1', type: 'action', props: { + quickbooks }, methods: {}, async run(){ - + // const fs = require('fs') + + // const {name, operation, id} = steps.trigger.event.event_notification.body.eventNotifications[0].dataChangeEvent.entities[0] + // const po = steps.trigger.event.record_details.PurchaseOrder + + // const po_number = po.DocNumber.replace('/', ' ') + // const vendor_name = po.VendorRef.name.replace(' [V]', '') + // const file_name = `${po.TxnDate} PO ${po_number} for ${vendor_name.replace('&', ' and ')}.pdf` + // const file_path = await downloadPDF(id, file_name) + + // this.qb_id = id + // this.po = po + // this.file_name = file_name + // this.file_path = file_path + + // async function downloadPDF(id, file_name){ + // const file = await require("@pipedreamhq/platform").axios(this, { + // url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/purchaseorder/${id}/pdf`, + // headers: { + // Authorization: `Bearer ${this.quickbooks.$auth.oauth_access_token}`, + // 'accept': 'application/pdf', + // }, + // responseType: 'arraybuffer', + // }) + + // const file_path = '/tmp/' + file_name + // fs.writeFileSync(file_path, file) + + // return file_path + // } } } \ No newline at end of file From a0a854fe7c6a83163ce88261183173269b428c50 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 22:16:44 -0400 Subject: [PATCH 51/96] Add single entity prop to quickbooks app file --- components/quickbooks/quickbooks.app.js | 69 ++++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index a872a798c09a8..14f62e25ea5a1 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,6 +1,38 @@ // const QuickBooks = require('node-quickbooks') const axios = require("axios"); +const ENTITIES = [ + "Account", + "Bill", + "BillPayment", + "Budget", + "Class", + "CreditMemo", + "Currency", + "Customer", + "Department", + "Deposit", + "Employee", + "Estimate", + "Invoice", + "Item", + "JournalCode", + "JournalEntry", + "Payment", + "PaymentMethod", + "Preferences", + "Purchase", + "PurchaseOrder", + "RefundReceipt", + "SalesReceipt", + "TaxAgency", + "Term", + "TimeActivity", + "Transfer", + "Vendor", + "VendorCredit", +] + const WEBHOOK_OPERATIONS = [ "Create", "Update", @@ -14,41 +46,16 @@ module.exports = { type: "app", app: "quickbooks", propDefinitions: { + entity: { + type: "string", + label: "Entity", + options: ENTITIES, + }, webhook_names: { type: "string[]", label: "Entities", description: "Select which QuickBooks entities to emit or just leave it blank to emit them all.", - options: [ - "Account", - "Bill", - "BillPayment", - "Budget", - "Class", - "CreditMemo", - "Currency", - "Customer", - "Department", - "Deposit", - "Employee", - "Estimate", - "Invoice", - "Item", - "JournalCode", - "JournalEntry", - "Payment", - "PaymentMethod", - "Preferences", - "Purchase", - "PurchaseOrder", - "RefundReceipt", - "SalesReceipt", - "TaxAgency", - "Term", - "TimeActivity", - "Transfer", - "Vendor", - "VendorCredit", - ], + options: ENTITIES, optional: true, }, webhook_operations: { From 4c80a3803037299c76f49ad561998d1cab59b91f Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 22:17:28 -0400 Subject: [PATCH 52/96] Get basic version working with props to choose an entity, record id and file name --- .../actions/download-pdf/download-pdf.js | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index f3eeb287cd8ca..b3a1076a4ad7f 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -1,45 +1,48 @@ -const quickbooks = require("../quickbooks.app"); +const quickbooks = require("../../quickbooks.app"); +const fs = require('fs') module.exports = { name: 'Download PDF', description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', key: 'download_pdf', - version: '0.0.1', + version: '0.0.8', type: 'action', props: { - quickbooks + quickbooks, + entity: { + propDefinition: [ + quickbooks, + 'entity', + ] + }, + id: { + type: 'string', + label: 'Record Id', + }, + file_name: { + type: 'string', + label: 'File Name', + }, }, - methods: {}, - async run(){ - // const fs = require('fs') + methods: { + async downloadPDF(entity, id, file_name){ + const file = await require("@pipedreamhq/platform").axios(this, { + url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, + headers: { + Authorization: `Bearer ${this.quickbooks.$auth.oauth_access_token}`, + 'accept': 'application/pdf', + }, + responseType: 'arraybuffer', + }) - // const {name, operation, id} = steps.trigger.event.event_notification.body.eventNotifications[0].dataChangeEvent.entities[0] - // const po = steps.trigger.event.record_details.PurchaseOrder + const file_path = '/tmp/' + file_name + fs.writeFileSync(file_path, file) - // const po_number = po.DocNumber.replace('/', ' ') - // const vendor_name = po.VendorRef.name.replace(' [V]', '') - // const file_name = `${po.TxnDate} PO ${po_number} for ${vendor_name.replace('&', ' and ')}.pdf` - // const file_path = await downloadPDF(id, file_name) - - // this.qb_id = id - // this.po = po - // this.file_name = file_name - // this.file_path = file_path - - // async function downloadPDF(id, file_name){ - // const file = await require("@pipedreamhq/platform").axios(this, { - // url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/purchaseorder/${id}/pdf`, - // headers: { - // Authorization: `Bearer ${this.quickbooks.$auth.oauth_access_token}`, - // 'accept': 'application/pdf', - // }, - // responseType: 'arraybuffer', - // }) - - // const file_path = '/tmp/' + file_name - // fs.writeFileSync(file_path, file) - - // return file_path - // } + return file_path + } + }, + async run({ $ }){ + const file_path = await this.downloadPDF(this.entity, this.id, this.file_name) + $.export('file_path', file_path) } } \ No newline at end of file From 519de0983695ccc8461e355b7b67d30211c2989a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 22:27:52 -0400 Subject: [PATCH 53/96] Replace Entity prop with Document Type prop limited to the downloadable entities --- .../actions/download-pdf/download-pdf.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index b3a1076a4ad7f..639175612618e 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -5,15 +5,22 @@ module.exports = { name: 'Download PDF', description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', key: 'download_pdf', - version: '0.0.8', + version: '0.0.9', type: 'action', props: { quickbooks, entity: { - propDefinition: [ - quickbooks, - 'entity', - ] + type: 'string', + label: 'Document Type', + options: [ + "CreditMemo", + "Estimate", + "Invoice", + "Payment", + "PurchaseOrder", + "RefundReceipt", + "SalesReceipt", + ], }, id: { type: 'string', From 4bfe358e1ee6bdc465fffb1a36e0723a41b8d26b Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 15 Aug 2021 22:28:28 -0400 Subject: [PATCH 54/96] Remove single entity prop from quickbooks app for now --- components/quickbooks/quickbooks.app.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 14f62e25ea5a1..d4a80c6915276 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,7 +1,7 @@ // const QuickBooks = require('node-quickbooks') const axios = require("axios"); -const ENTITIES = [ +const WEBHOOK_ENTITIES = [ "Account", "Bill", "BillPayment", @@ -46,16 +46,11 @@ module.exports = { type: "app", app: "quickbooks", propDefinitions: { - entity: { - type: "string", - label: "Entity", - options: ENTITIES, - }, webhook_names: { type: "string[]", label: "Entities", description: "Select which QuickBooks entities to emit or just leave it blank to emit them all.", - options: ENTITIES, + options: WEBHOOK_ENTITIES, optional: true, }, webhook_operations: { From ef67ada93268b9f695d53ca300b4116b5efdf1e5 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Mon, 16 Aug 2021 19:08:42 -0400 Subject: [PATCH 55/96] Make File Name prop optional --- components/quickbooks/actions/download-pdf/download-pdf.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 639175612618e..519a7af7ef9e9 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -5,7 +5,7 @@ module.exports = { name: 'Download PDF', description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', key: 'download_pdf', - version: '0.0.9', + version: '0.1.1', type: 'action', props: { quickbooks, @@ -28,7 +28,8 @@ module.exports = { }, file_name: { type: 'string', - label: 'File Name', + label: 'File Name (Optional)', + optional: true, }, }, methods: { @@ -42,7 +43,7 @@ module.exports = { responseType: 'arraybuffer', }) - const file_path = '/tmp/' + file_name + const file_path = '/tmp/' + (file_name || id) fs.writeFileSync(file_path, file) return file_path From 20264e5f337536d042b3aeff9b4fd630c4dc669d Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Mon, 16 Aug 2021 20:32:38 -0400 Subject: [PATCH 56/96] Add .pdf extension if it's not already included in the file name --- .../quickbooks/actions/download-pdf/download-pdf.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 519a7af7ef9e9..9dee67cec7bd7 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -5,7 +5,7 @@ module.exports = { name: 'Download PDF', description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', key: 'download_pdf', - version: '0.1.1', + version: '0.1.3', type: 'action', props: { quickbooks, @@ -43,14 +43,18 @@ module.exports = { responseType: 'arraybuffer', }) - const file_path = '/tmp/' + (file_name || id) + const file_path = '/tmp/' + file_name fs.writeFileSync(file_path, file) return file_path } }, async run({ $ }){ - const file_path = await this.downloadPDF(this.entity, this.id, this.file_name) + const file_name = this.file_name || this.id + const file_name_with_extension = file_name.endsWith('.pdf') ? file_name : file_name + '.pdf' + + const file_path = await this.downloadPDF(this.entity, this.id, file_name_with_extension) $.export('file_path', file_path) + $.export('file_name', file_name_with_extension) } } \ No newline at end of file From fa90d153a656cc7ca836ca2a1f7ad3a0aacf1411 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 26 Sep 2021 14:07:56 -0400 Subject: [PATCH 57/96] ES Lint Fixes for Download PDF action --- .../actions/download-pdf/download-pdf.js | 114 +++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 9dee67cec7bd7..dd22924c4d753 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -1,60 +1,64 @@ const quickbooks = require("../../quickbooks.app"); -const fs = require('fs') +const fs = require("fs"); module.exports = { - name: 'Download PDF', - description: 'Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.', - key: 'download_pdf', - version: '0.1.3', - type: 'action', - props: { - quickbooks, - entity: { - type: 'string', - label: 'Document Type', - options: [ - "CreditMemo", - "Estimate", - "Invoice", - "Payment", - "PurchaseOrder", - "RefundReceipt", - "SalesReceipt", - ], - }, - id: { - type: 'string', - label: 'Record Id', - }, - file_name: { - type: 'string', - label: 'File Name (Optional)', - optional: true, - }, - }, - methods: { - async downloadPDF(entity, id, file_name){ - const file = await require("@pipedreamhq/platform").axios(this, { - url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, - headers: { - Authorization: `Bearer ${this.quickbooks.$auth.oauth_access_token}`, - 'accept': 'application/pdf', - }, - responseType: 'arraybuffer', - }) + name: "Download PDF", + description: "Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.", + key: "download_pdf", + version: "0.2.3", + type: "action", + props: { + quickbooks, + entity: { + type: "string", + label: "Document Type", + description: null, + options: [ + "CreditMemo", + "Estimate", + "Invoice", + "Payment", + "PurchaseOrder", + "RefundReceipt", + "SalesReceipt", + ], + }, + id: { + type: "string", + label: "Record ID", + description: null, + }, + fileName: { + type: "string", + label: "File Name (Optional)", + description: "If no file name is provided, the PDF will be named using the record ID, e.g. '22743.pdf'", + optional: true, + }, + }, + methods: { + async downloadPDF(entity, id, fileName) { + const file = await require("@pipedreamhq/platform").axios(this, { + url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, + headers: { + "Authorization": `Bearer ${this.quickbooks.$auth.oauth_access_token}`, + "accept": "application/pdf", + }, + responseType: "arraybuffer", + }); - const file_path = '/tmp/' + file_name - fs.writeFileSync(file_path, file) + const filePath = "/tmp/" + fileName; + fs.writeFileSync(filePath, file); + return filePath; + }, + }, + async run({ $ }) { + const fileName = this.fileName || this.id; + const fileNameWithExtension = fileName.endsWith(".pdf") + ? fileName + : fileName + ".pdf"; - return file_path - } - }, - async run({ $ }){ - const file_name = this.file_name || this.id - const file_name_with_extension = file_name.endsWith('.pdf') ? file_name : file_name + '.pdf' - - const file_path = await this.downloadPDF(this.entity, this.id, file_name_with_extension) - $.export('file_path', file_path) - $.export('file_name', file_name_with_extension) - } -} \ No newline at end of file + const filePath = await this.downloadPDF(this.entity, this.id, fileNameWithExtension); + $.export("file_path", filePath); + $.export("file_name", fileNameWithExtension); + }, +}; From ce53addc1f31e337e050728e6cfd853ac5befa0a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 26 Sep 2021 14:19:41 -0400 Subject: [PATCH 58/96] ES Lint fixes for source files --- components/quickbooks/quickbooks.app.js | 2 +- components/quickbooks/sources/common.js | 2 ++ .../sources/custom-webhook-events/custom-webhook-events.js | 1 + .../new-or-modified-customer/new-or-modified-customer.js | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index d4a80c6915276..d6625b3d6d16e 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -31,7 +31,7 @@ const WEBHOOK_ENTITIES = [ "Transfer", "Vendor", "VendorCredit", -] +]; const WEBHOOK_OPERATIONS = [ "Create", diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 1667c8ad47923..b2ec4262261ce 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -170,6 +170,8 @@ module.exports = { http: { type: "$.interface.http", customResponse: true, + label: "HTTP", + description: "", }, }, methods: { diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 865099fc8262a..81fb72cace241 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -7,6 +7,7 @@ module.exports = { name: "Custom Set of Webhook Entities (Created, Updated, Merged, Deleted, Voided or Emailed)", description: "Emit events for more than one type of entity (e.g. \"Emailed Invoices and Purchase Orders\" or \"New and Modified Customers and Vendors\"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", version: "0.0.1", + type: "source", props: { ...common.props, webhook_verifier_token: { diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 2951a877018e5..fccce52ff6c99 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -12,6 +12,7 @@ module.exports = { name: `New or Modified Customer (${supportedOperationsList})`, description: `Emit Customers that are ${supportedOperationsList.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, version: "0.0.1", + type: "source", props: { ...common.props, webhook_verifier_token: { From b6004808d7ead0489099c4323ede8af28fc7e787 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 26 Sep 2021 14:38:58 -0400 Subject: [PATCH 59/96] Change name and description from dynamic to static --- components/quickbooks/sources/common.js | 18 ------------------ .../new-or-modified-customer.js | 5 ++--- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index b2ec4262261ce..a0fb3c5826926 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -179,10 +179,6 @@ module.exports = { return supportedWebhookOptions[entityName]; }, - getOperationsDescription(operations) { - return this.toReadableList(this.toPastTense(operations)); - }, - toPastTense(operations) { const pastTenseVersion = { Create: "Created", @@ -199,20 +195,6 @@ module.exports = { } }, - toReadableList(array) { - // converts an array to a readable list like this: - // ['Created', 'Updated', 'Merged'] => 'Created, Updated, or Merged' - const list = array.join(", "); - const indexAfterLastComma = list.lastIndexOf(",") + 1; - if (indexAfterLastComma === 0) { - //no commas were found so just return the list - return list; - } else { - //add an 'or' after the last comma - return list.slice(0, indexAfterLastComma) + " or" + list.slice(indexAfterLastComma); - } - }, - sendHttpResponse(event, entities) { this.http.respond({ status: 200, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index fccce52ff6c99..02a8d3ea59edd 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -4,13 +4,12 @@ const common = require("../common"); const sourceEntity = "Customer"; const supportedOperations = common.methods.getSupportedOperations(sourceEntity); -const supportedOperationsList = common.methods.getOperationsDescription(supportedOperations); module.exports = { ...common, key: "quickbooks-new-or-modified-customer", - name: `New or Modified Customer (${supportedOperationsList})`, - description: `Emit Customers that are ${supportedOperationsList.toLowerCase()}. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks`, + name: "New or Modified Customer (Created, Updated, Merged or Deleted)", + description: "Emit new or modified customers. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", version: "0.0.1", type: "source", props: { From 9afe2b5dc61acd1e3a05d802462b375b457a7500 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 13 Nov 2021 18:23:28 -0500 Subject: [PATCH 60/96] Change component key to include quickbooks --- components/quickbooks/actions/download-pdf/download-pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index dd22924c4d753..422ffff5c521a 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -4,7 +4,7 @@ const fs = require("fs"); module.exports = { name: "Download PDF", description: "Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.", - key: "download_pdf", + key: "quickbooks-download-pdf", version: "0.2.3", type: "action", props: { From 337a9af6ccaff04def711439111eb4a9fe7c78e9 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 13 Nov 2021 18:27:14 -0500 Subject: [PATCH 61/96] Use $ instead of this for axios request --- components/quickbooks/actions/download-pdf/download-pdf.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 422ffff5c521a..3441a8960b56f 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -36,8 +36,8 @@ module.exports = { }, }, methods: { - async downloadPDF(entity, id, fileName) { - const file = await require("@pipedreamhq/platform").axios(this, { + async downloadPDF($, entity, id, fileName) { + const file = await require("@pipedreamhq/platform").axios($, { url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, headers: { "Authorization": `Bearer ${this.quickbooks.$auth.oauth_access_token}`, @@ -57,7 +57,7 @@ module.exports = { ? fileName : fileName + ".pdf"; - const filePath = await this.downloadPDF(this.entity, this.id, fileNameWithExtension); + const filePath = await this.downloadPDF($, this.entity, this.id, fileNameWithExtension); $.export("file_path", filePath); $.export("file_name", fileNameWithExtension); }, From b4df4fba007279616564a683a9ffcb7b36673e3f Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 13 Nov 2021 18:37:58 -0500 Subject: [PATCH 62/96] Add _makeRequest() method --- .../actions/download-pdf/download-pdf.js | 30 +++++--- components/quickbooks/quickbooks.app.js | 74 ++++++++++++++++--- 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 3441a8960b56f..47435cd32adc8 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -1,5 +1,7 @@ const quickbooks = require("../../quickbooks.app"); const fs = require("fs"); +const { promisify } = require("util"); +const stream = require("stream"); module.exports = { name: "Download PDF", @@ -37,18 +39,28 @@ module.exports = { }, methods: { async downloadPDF($, entity, id, fileName) { - const file = await require("@pipedreamhq/platform").axios($, { - url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, - headers: { - "Authorization": `Bearer ${this.quickbooks.$auth.oauth_access_token}`, - "accept": "application/pdf", - }, - responseType: "arraybuffer", - }); + const file = await this.quickbooks.getPDF($, entity, id); const filePath = "/tmp/" + fileName; - fs.writeFileSync(filePath, file); + const pipeline = promisify(stream.pipeline); + await pipeline( + file, + fs.createWriteStream(filePath), + ); return filePath; + + // const file = await require("@pipedreamhq/platform").axios($, { + // url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, + // headers: { + // "Authorization": `Bearer ${this.quickbooks.$auth.oauth_access_token}`, + // "accept": "application/pdf", + // }, + // responseType: "arraybuffer", + // }); + + // const filePath = "/tmp/" + fileName; + // fs.writeFileSync(filePath, file); + // return filePath; }, }, async run({ $ }) { diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index d6625b3d6d16e..f80d29156a54d 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,5 +1,5 @@ // const QuickBooks = require('node-quickbooks') -const axios = require("axios"); +const { axios } = require("@pipedream/platform"); const WEBHOOK_ENTITIES = [ "Account", @@ -72,17 +72,73 @@ module.exports = { }, }, methods: { - async getRecordDetails(endpoint, id) { - const config = { - url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint.toLowerCase()}/${id}`, + _apiUrl(){ + return "https://quickbooks.api.intuit.com/v3"; + }, + + _authToken() { + return this.$auth.oauth_access_token; + }, + + _companyId() { + return this.$auth.company_id; + }, + + _makeRequestConfig(config = {}) { + const { + headers, + path = "", + ...extraConfig + } = config; + const authToken = this._authToken(); + const baseUrl = this._apiUrl(); + const url = `${baseUrl}/${path[0] === "/" + ? "" + : "/"}${path}`; + return { headers: { - "Authorization": `Bearer ${this.$auth.oauth_access_token}`, - "accept": "application/json", - "content-type": "application/json", + "Authorization": `Bearer ${authToken}`, + "Accept": "application/json", + "Content-Type": "application/json", + ...headers, }, + url, + ...extraConfig, }; - const { data } = await axios(config); - return data; + }, + + async _makeRequest($ = this, config) { + const requestConfig = this._makeRequestConfig(config); + return await axios($, requestConfig); + }, + + async getPDF($, entity, id) { + const companyId = this._companyId(); + return await this._makeRequest($, { + path: `company/${companyId}/${entity.toLowerCase()}/${id}/pdf`, + headers: { + "Accept": "application/pdf", + }, + responseType: "stream", + }); + }, + + async getRecordDetails(endpoint, id) { + const companyId = this._companyId(); + return await this._makeRequest(this, { + path: `company/${companyId}/${endpoint.toLowerCase()}/${id}`, + }); + + // const config = { + // url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint.toLowerCase()}/${id}`, + // headers: { + // "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + // "accept": "application/json", + // "content-type": "application/json", + // }, + // }; + // const { data } = await axios(config); + // return data; }, }, }; From 042f2ef1c8242a9b8c29d66f63b8a433d7dec1b1 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 13 Nov 2021 18:38:20 -0500 Subject: [PATCH 63/96] Reset version number from 0.2.3 to 0.0.1 --- components/quickbooks/actions/download-pdf/download-pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 47435cd32adc8..7b904059dc374 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -7,7 +7,7 @@ module.exports = { name: "Download PDF", description: "Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.", key: "quickbooks-download-pdf", - version: "0.2.3", + version: "0.0.1", type: "action", props: { quickbooks, From 2caf1ebb3713aee34dd84bf40b8fcfb16d2b244e Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 13 Nov 2021 18:54:05 -0500 Subject: [PATCH 64/96] ES Lint Fixes --- components/quickbooks/quickbooks.app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index f80d29156a54d..d99d6ab970754 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -72,7 +72,7 @@ module.exports = { }, }, methods: { - _apiUrl(){ + _apiUrl() { return "https://quickbooks.api.intuit.com/v3"; }, @@ -122,7 +122,7 @@ module.exports = { responseType: "stream", }); }, - + async getRecordDetails(endpoint, id) { const companyId = this._companyId(); return await this._makeRequest(this, { From cc71cbe00af5f5808c57dffc9e806b6573940754 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 13:55:54 -0500 Subject: [PATCH 65/96] Revert key and version of download-pdf.js for testing --- components/quickbooks/actions/download-pdf/download-pdf.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 7b904059dc374..6bd1003c85d6f 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -6,8 +6,10 @@ const stream = require("stream"); module.exports = { name: "Download PDF", description: "Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.", - key: "quickbooks-download-pdf", - version: "0.0.1", + key: "download-pdf", + version: "0.2.6", + // key: "quickbooks-download-pdf", + // version: "0.0.1", type: "action", props: { quickbooks, From f05e631cb8c1f9298f4526cad92fac9753f1ef56 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:11:37 -0500 Subject: [PATCH 66/96] Revise action description and prop description for Download PDF --- components/quickbooks/actions/download-pdf/download-pdf.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 6bd1003c85d6f..52f327d0accd2 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -5,9 +5,9 @@ const stream = require("stream"); module.exports = { name: "Download PDF", - description: "Download an invoice, bill, purchase order, etc. as a PDF and save it in the temporary file system for use in a later step.", + description: "Download an invoice, bill, purchase order or other QuickBooks entity as a PDF and save it in [Pipedream's temporary file system](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory) for use in a later step.", key: "download-pdf", - version: "0.2.6", + version: "0.2.8", // key: "quickbooks-download-pdf", // version: "0.0.1", type: "action", @@ -35,7 +35,7 @@ module.exports = { fileName: { type: "string", label: "File Name (Optional)", - description: "If no file name is provided, the PDF will be named using the record ID, e.g. '22743.pdf'", + description: "The name to give the PDF when it is stored in the /tmp directory, e.g. `myFile.pdf`. If no file name is provided, the PDF will be named using the record ID, e.g. '22743.pdf'", optional: true, }, }, From c958d5f81df60da24cfd716b726e0f13007ff1d2 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:13:47 -0500 Subject: [PATCH 67/96] Remove unused code --- .../quickbooks/actions/download-pdf/download-pdf.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 52f327d0accd2..9b1d62f4dd7cc 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -50,19 +50,6 @@ module.exports = { fs.createWriteStream(filePath), ); return filePath; - - // const file = await require("@pipedreamhq/platform").axios($, { - // url: `https://quickbooks.api.intuit.com/v3/company/${this.quickbooks.$auth.company_id}/${entity.toLowerCase()}/${id}/pdf`, - // headers: { - // "Authorization": `Bearer ${this.quickbooks.$auth.oauth_access_token}`, - // "accept": "application/pdf", - // }, - // responseType: "arraybuffer", - // }); - - // const filePath = "/tmp/" + fileName; - // fs.writeFileSync(filePath, file); - // return filePath; }, }, async run({ $ }) { From 42dd4b55693bcdf5057af505ed0d89ce18bb606c Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:14:08 -0500 Subject: [PATCH 68/96] Add summary for Download PDF --- components/quickbooks/actions/download-pdf/download-pdf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 9b1d62f4dd7cc..f4b61b0b8e17d 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -61,5 +61,6 @@ module.exports = { const filePath = await this.downloadPDF($, this.entity, this.id, fileNameWithExtension); $.export("file_path", filePath); $.export("file_name", fileNameWithExtension); + $.export("$summary", `Successfully downloaded file: ${fileNameWithExtension}`); }, }; From 084b77fb102780dec5e209332379b80bc3560e25 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:14:51 -0500 Subject: [PATCH 69/96] Move constants to a separate file. --- components/quickbooks/constants.js | 45 ++++++++++++++++++++++++ components/quickbooks/quickbooks.app.js | 46 +++---------------------- 2 files changed, 49 insertions(+), 42 deletions(-) create mode 100644 components/quickbooks/constants.js diff --git a/components/quickbooks/constants.js b/components/quickbooks/constants.js new file mode 100644 index 0000000000000..7edb270923cfb --- /dev/null +++ b/components/quickbooks/constants.js @@ -0,0 +1,45 @@ +const WEBHOOK_ENTITIES = [ + "Account", + "Bill", + "BillPayment", + "Budget", + "Class", + "CreditMemo", + "Currency", + "Customer", + "Department", + "Deposit", + "Employee", + "Estimate", + "Invoice", + "Item", + "JournalCode", + "JournalEntry", + "Payment", + "PaymentMethod", + "Preferences", + "Purchase", + "PurchaseOrder", + "RefundReceipt", + "SalesReceipt", + "TaxAgency", + "Term", + "TimeActivity", + "Transfer", + "Vendor", + "VendorCredit", +]; + +const WEBHOOK_OPERATIONS = [ + "Create", + "Update", + "Merge", + "Delete", + "Void", + "Emailed", +]; + +module.exports = { + WEBHOOK_ENTITIES, + WEBHOOK_OPERATIONS, +}; diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index d99d6ab970754..a7a0f6ca4e16c 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -1,46 +1,8 @@ -// const QuickBooks = require('node-quickbooks') const { axios } = require("@pipedream/platform"); - -const WEBHOOK_ENTITIES = [ - "Account", - "Bill", - "BillPayment", - "Budget", - "Class", - "CreditMemo", - "Currency", - "Customer", - "Department", - "Deposit", - "Employee", - "Estimate", - "Invoice", - "Item", - "JournalCode", - "JournalEntry", - "Payment", - "PaymentMethod", - "Preferences", - "Purchase", - "PurchaseOrder", - "RefundReceipt", - "SalesReceipt", - "TaxAgency", - "Term", - "TimeActivity", - "Transfer", - "Vendor", - "VendorCredit", -]; - -const WEBHOOK_OPERATIONS = [ - "Create", - "Update", - "Merge", - "Delete", - "Void", - "Emailed", -]; +const { + WEBHOOK_ENTITIES, + WEBHOOK_OPERATIONS, +} = require("./constants"); module.exports = { type: "app", From 3a525c56e8b7a37a76fb64a79063e04449af29e9 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:15:39 -0500 Subject: [PATCH 70/96] Remove extra slash from url string --- components/quickbooks/quickbooks.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index a7a0f6ca4e16c..c92979a3ce206 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -54,7 +54,7 @@ module.exports = { } = config; const authToken = this._authToken(); const baseUrl = this._apiUrl(); - const url = `${baseUrl}/${path[0] === "/" + const url = `${baseUrl}${path[0] === "/" ? "" : "/"}${path}`; return { From f6491389cf49acfe6f0ceb77d1b2b361eaf26336 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:22:15 -0500 Subject: [PATCH 71/96] Change prop names to camel case --- components/quickbooks/quickbooks.app.js | 6 +++--- components/quickbooks/sources/common.js | 2 +- .../custom-webhook-events.js | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index c92979a3ce206..faa72856e94d8 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -8,14 +8,14 @@ module.exports = { type: "app", app: "quickbooks", propDefinitions: { - webhook_names: { + webhookNames: { type: "string[]", label: "Entities", description: "Select which QuickBooks entities to emit or just leave it blank to emit them all.", options: WEBHOOK_ENTITIES, optional: true, }, - webhook_operations: { + webhookOperations: { type: "string[]", label: "Operations", description: "Select which operations to emit or just leave it blank to emit them all.", @@ -23,7 +23,7 @@ module.exports = { default: WEBHOOK_OPERATIONS, optional: true, }, - webhook_verifier_token: { + webhookVerifierToken: { type: "string", label: "Verifier Token", description: "[Create an app](https://developer.intuit.com/app/developer/qbo/docs/build-your-first-app) " + diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index a0fb3c5826926..39fce34d7281b 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -212,7 +212,7 @@ module.exports = { }, async emitEvent(eventReceived, entity) { - const token = this.webhook_verifier_token; + const token = this.webhookVerifierToken; const payload = eventReceived.bodyRaw; const header = eventReceived.headers["intuit-signature"]; const isWebhookValid = this.verifyWebhookRequest(token, payload, header); diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 81fb72cace241..6521696be3d7f 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -10,22 +10,22 @@ module.exports = { type: "source", props: { ...common.props, - webhook_verifier_token: { + webhookVerifierToken: { propDefinition: [ quickbooks, - "webhook_verifier_token", + "webhookVerifierToken", ], }, - names_to_emit: { + namesToEmit: { propDefinition: [ quickbooks, - "webhook_names", + "webhookNames", ], }, - operations_to_emit: { + operationsToEmit: { propDefinition: [ quickbooks, - "webhook_operations", + "webhookOperations", ], }, }, @@ -35,10 +35,10 @@ module.exports = { // only emit events that match the entity names and operations indicated by the user // but if the props are left empty, emit all events rather than filtering them all out // (it would a hassle for the user to select every option if they wanted to emit everything) - if (this.names_to_emit.length > 0 && !this.names_to_emit.includes(entity.name)) { + if (this.namesToEmit.length > 0 && !this.namesToEmit.includes(entity.name)) { console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`); - } else if (this.operations_to_emit.length > 0 - && !this.operations_to_emit.includes(entity.operation)) { + } else if (this.operationsToEmit.length > 0 + && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { await this.emitEvent(event, entity); From 2c0ed0d283d8c340c7dd65412d41fad538e9c9f5 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:23:52 -0500 Subject: [PATCH 72/96] Remove empty lines between method definitions --- components/quickbooks/quickbooks.app.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index faa72856e94d8..fed4f5f8d767a 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -37,15 +37,12 @@ module.exports = { _apiUrl() { return "https://quickbooks.api.intuit.com/v3"; }, - _authToken() { return this.$auth.oauth_access_token; }, - _companyId() { return this.$auth.company_id; }, - _makeRequestConfig(config = {}) { const { headers, @@ -68,12 +65,10 @@ module.exports = { ...extraConfig, }; }, - async _makeRequest($ = this, config) { const requestConfig = this._makeRequestConfig(config); return await axios($, requestConfig); }, - async getPDF($, entity, id) { const companyId = this._companyId(); return await this._makeRequest($, { @@ -84,7 +79,6 @@ module.exports = { responseType: "stream", }); }, - async getRecordDetails(endpoint, id) { const companyId = this._companyId(); return await this._makeRequest(this, { From 79b0f8da80e9a321479521ad78e6b7cd0f5ea40a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:25:11 -0500 Subject: [PATCH 73/96] Change endpoint to entityName --- components/quickbooks/quickbooks.app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index fed4f5f8d767a..74e3d179a8242 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -79,10 +79,10 @@ module.exports = { responseType: "stream", }); }, - async getRecordDetails(endpoint, id) { + async getRecordDetails(entityName, id) { const companyId = this._companyId(); return await this._makeRequest(this, { - path: `company/${companyId}/${endpoint.toLowerCase()}/${id}`, + path: `company/${companyId}/${entityName.toLowerCase()}/${id}`, }); // const config = { From d76f8d4bd530357d0b6370100d8adb205a6a7100 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:25:29 -0500 Subject: [PATCH 74/96] Remove unused code --- components/quickbooks/quickbooks.app.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index 74e3d179a8242..eea443c41a400 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -84,17 +84,6 @@ module.exports = { return await this._makeRequest(this, { path: `company/${companyId}/${entityName.toLowerCase()}/${id}`, }); - - // const config = { - // url: `https://quickbooks.api.intuit.com/v3/company/${this.$auth.company_id}/${endpoint.toLowerCase()}/${id}`, - // headers: { - // "Authorization": `Bearer ${this.$auth.oauth_access_token}`, - // "accept": "application/json", - // "content-type": "application/json", - // }, - // }; - // const { data } = await axios(config); - // return data; }, }, }; From 91e0f0db89fe05213a5ab4b3788d0e96de0ff24d Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:35:43 -0500 Subject: [PATCH 75/96] Move list of supported webhook operations to constants file --- components/quickbooks/constants.js | 165 +++++++++++++++++++++++ components/quickbooks/sources/common.js | 166 +----------------------- 2 files changed, 167 insertions(+), 164 deletions(-) diff --git a/components/quickbooks/constants.js b/components/quickbooks/constants.js index 7edb270923cfb..301a6d7e1af15 100644 --- a/components/quickbooks/constants.js +++ b/components/quickbooks/constants.js @@ -39,7 +39,172 @@ const WEBHOOK_OPERATIONS = [ "Emailed", ]; +// The below list is based on the table shown in Step 3 of the Intuit webhooks documentation +// https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks#set-up-oauth +const SUPPORTED_WEBHOOK_OPERATIONS = { + Account: [ + "Create", + "Update", + "Merge", + "Delete", + ], + Bill: [ + "Create", + "Update", + "Delete", + ], + BillPayment: [ + "Create", + "Update", + "Delete", + "Void", + ], + Budget: [ + "Create", + "Update", + ], + Class: [ + "Create", + "Update", + "Merge", + "Delete", + ], + CreditMemo: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + Currency: [ + "Create", + "Update", + ], + Customer: [ + "Create", + "Update", + "Merge", + "Delete", + ], + Department: [ + "Create", + "Update", + "Merge", + ], + Deposit: [ + "Create", + "Update", + "Delete", + ], + Employee: [ + "Create", + "Update", + "Merge", + "Delete", + ], + Estimate: [ + "Create", + "Update", + "Delete", + "Emailed", + ], + Invoice: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + Item: [ + "Create", + "Update", + "Merge", + "Delete", + ], + JournalCode: [ + "Create", + "Update", + ], + JournalEntry: [ + "Create", + "Update", + "Delete", + ], + Payment: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + PaymentMethod: [ + "Create", + "Update", + "Merge", + ], + Preferences: [ + "Update", + ], + Purchase: [ + "Create", + "Update", + "Delete", + "Void", + ], + PurchaseOrder: [ + "Create", + "Update", + "Delete", + "Emailed", + ], + RefundReceipt: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + SalesReceipt: [ + "Create", + "Update", + "Delete", + "Void", + "Emailed", + ], + TaxAgency: [ + "Create", + "Update", + ], + Term: [ + "Create", + "Update", + ], + TimeActivity: [ + "Create", + "Update", + "Delete", + ], + Transfer: [ + "Create", + "Update", + "Delete", + "Void", + ], + Vendor: [ + "Create", + "Update", + "Merge", + "Delete", + ], + VendorCredit: [ + "Create", + "Update", + "Delete", + ], +}; + module.exports = { WEBHOOK_ENTITIES, WEBHOOK_OPERATIONS, + SUPPORTED_WEBHOOK_OPERATIONS, }; diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 39fce34d7281b..06a752023dc5f 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -1,168 +1,6 @@ const quickbooks = require("../quickbooks.app"); const { createHmac } = require("crypto"); - -//https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/entities-and-operations-supported -const supportedWebhookOptions = { - Account: [ - "Create", - "Update", - "Merge", - "Delete", - ], - Bill: [ - "Create", - "Update", - "Delete", - ], - BillPayment: [ - "Create", - "Update", - "Delete", - "Void", - ], - Budget: [ - "Create", - "Update", - ], - Class: [ - "Create", - "Update", - "Merge", - "Delete", - ], - CreditMemo: [ - "Create", - "Update", - "Delete", - "Void", - "Emailed", - ], - Currency: [ - "Create", - "Update", - ], - Customer: [ - "Create", - "Update", - "Merge", - "Delete", - ], - Department: [ - "Create", - "Update", - "Merge", - ], - Deposit: [ - "Create", - "Update", - "Delete", - ], - Employee: [ - "Create", - "Update", - "Merge", - "Delete", - ], - Estimate: [ - "Create", - "Update", - "Delete", - "Emailed", - ], - Invoice: [ - "Create", - "Update", - "Delete", - "Void", - "Emailed", - ], - Item: [ - "Create", - "Update", - "Merge", - "Delete", - ], - JournalCode: [ - "Create", - "Update", - ], - JournalEntry: [ - "Create", - "Update", - "Delete", - ], - Payment: [ - "Create", - "Update", - "Delete", - "Void", - "Emailed", - ], - PaymentMethod: [ - "Create", - "Update", - "Merge", - ], - Preferences: [ - "Update", - ], - Purchase: [ - "Create", - "Update", - "Delete", - "Void", - ], - PurchaseOrder: [ - "Create", - "Update", - "Delete", - "Emailed", - ], - RefundReceipt: [ - "Create", - "Update", - "Delete", - "Void", - "Emailed", - ], - SalesReceipt: [ - "Create", - "Update", - "Delete", - "Void", - "Emailed", - ], - TaxAgency: [ - "Create", - "Update", - ], - Term: [ - "Create", - "Update", - ], - TimeActivity: [ - "Create", - "Update", - "Delete", - ], - Transfer: [ - "Create", - "Update", - "Delete", - "Void", - ], - Vendor: [ - "Create", - "Update", - "Merge", - "Delete", - ], - VendorCredit: [ - "Create", - "Update", - "Delete", - ], -}; +const { SUPPORTED_WEBHOOK_OPERATIONS } = require("../constants"); module.exports = { props: { @@ -176,7 +14,7 @@ module.exports = { }, methods: { getSupportedOperations(entityName) { - return supportedWebhookOptions[entityName]; + return SUPPORTED_WEBHOOK_OPERATIONS[entityName]; }, toPastTense(operations) { From 4c71d74735534bbb9a56960dedf7905f70a23be6 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:43:49 -0500 Subject: [PATCH 76/96] Move webhookVerifierToken prop to common file --- components/quickbooks/sources/common.js | 6 ++++++ .../sources/custom-webhook-events/custom-webhook-events.js | 6 ------ .../new-or-modified-customer/new-or-modified-customer.js | 6 ------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 06a752023dc5f..6652fac5d411a 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -11,6 +11,12 @@ module.exports = { label: "HTTP", description: "", }, + webhookVerifierToken: { + propDefinition: [ + quickbooks, + "webhookVerifierToken", + ], + }, }, methods: { getSupportedOperations(entityName) { diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 6521696be3d7f..c9ce9b61c1f12 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -10,12 +10,6 @@ module.exports = { type: "source", props: { ...common.props, - webhookVerifierToken: { - propDefinition: [ - quickbooks, - "webhookVerifierToken", - ], - }, namesToEmit: { propDefinition: [ quickbooks, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 02a8d3ea59edd..e85f1b6b9781d 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -14,12 +14,6 @@ module.exports = { type: "source", props: { ...common.props, - webhook_verifier_token: { - propDefinition: [ - quickbooks, - "webhook_verifier_token", - ], - }, operations_to_emit: { propDefinition: [ quickbooks, From 7607d0d502d7cebac7069bcdf23e7542d68a419a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:49:36 -0500 Subject: [PATCH 77/96] Change prop names to camel case --- .../new-or-modified-customer/new-or-modified-customer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index e85f1b6b9781d..ad580d74795fd 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -14,10 +14,10 @@ module.exports = { type: "source", props: { ...common.props, - operations_to_emit: { + operationsToEmit: { propDefinition: [ quickbooks, - "webhook_operations", + "webhookOperations", ], // overwrite the default options from the propDefinition to list only the options supported // by this source's entity @@ -35,8 +35,8 @@ module.exports = { // if they wanted to emit everything) if (entity.name !== sourceEntity) { console.log(`${entity.name} webhook received and ignored, since it is not a Customer`); - } else if (this.operations_to_emit.length > 0 - && !this.operations_to_emit.includes(entity.operation)) { + } else if (this.operationsToEmit.length > 0 + && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { await this.emitEvent(event, entity); From 7023986ce443e4bd71e3885bfa682642e26a6137 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 14:54:11 -0500 Subject: [PATCH 78/96] Rename emitEvent() to processEvent() --- components/quickbooks/sources/common.js | 4 ++-- .../sources/custom-webhook-events/custom-webhook-events.js | 2 +- .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 6652fac5d411a..f7b62f432cd12 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -52,10 +52,10 @@ module.exports = { async validateAndEmit(event, entity) { // individual source modules can redefine this method to specify criteria // for which events to emit - await this.emitEvent(event, entity); + await this.processEvent(event, entity); }, - async emitEvent(eventReceived, entity) { + async processEvent(eventReceived, entity) { const token = this.webhookVerifierToken; const payload = eventReceived.bodyRaw; const header = eventReceived.headers["intuit-signature"]; diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index c9ce9b61c1f12..92a45209ed55a 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -35,7 +35,7 @@ module.exports = { && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { - await this.emitEvent(event, entity); + await this.processEvent(event, entity); } }, }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index ad580d74795fd..8f47df60ec757 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -39,7 +39,7 @@ module.exports = { && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { - await this.emitEvent(event, entity); + await this.processEvent(event, entity); } }, }, From 7f3a2abc6a77c2f49df3d46fc913d3533605c364 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 15:46:44 -0500 Subject: [PATCH 79/96] Refactor webhook validation to happen once per event instead of once per entity. --- components/quickbooks/sources/common.js | 95 ++++++++++--------- .../custom-webhook-events.js | 4 +- .../new-or-modified-customer.js | 4 +- 3 files changed, 56 insertions(+), 47 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index f7b62f432cd12..311d1b71ff8b0 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -49,52 +49,28 @@ module.exports = { }); }, - async validateAndEmit(event, entity) { - // individual source modules can redefine this method to specify criteria - // for which events to emit - await this.processEvent(event, entity); - }, - - async processEvent(eventReceived, entity) { - const token = this.webhookVerifierToken; - const payload = eventReceived.bodyRaw; - const header = eventReceived.headers["intuit-signature"]; - const isWebhookValid = this.verifyWebhookRequest(token, payload, header); + isValidSource(event, webhookCompanyId) { + const isWebhookValid = this.verifyWebhookRequest(event); if (!isWebhookValid) { - const message = `Error: Webhook did not pass verification. Try reentering the verifier token, - making sure it's from the correct section on the Intuit Developer Dashboard.`; - console.log(message); - throw new Error(message); - } else { - const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}`; - const webhookCompanyId = eventReceived.body.eventNotifications[0].realmId; - const connectedCompanyId = this.quickbooks.$auth.company_id; - if (webhookCompanyId !== connectedCompanyId) { - const message = `Error: Cannot retrieve record details for ${summary}. The QuickBooks company id - of the incoming event (${webhookCompanyId}) does not match the company id of the account - currently connected to this source in Pipedream (${connectedCompanyId}).`; - console.log(message); - throw new Error(message); - } else { - entity.realmId = webhookCompanyId; - const eventToEmit = { - event_notification: entity, - record_details: {}, - }; - // Unless the record has been deleted, use the id received in the webhook - // to get the full record data - if (entity.operation !== "Delete") { - eventToEmit.record_details = await this.quickbooks - .getRecordDetails(entity.name, entity.id); - } - this.$emit(eventToEmit, { - summary, - }); - } + console.log(`Error: Webhook did not pass verification. Try reentering the verifier token, + making sure it's from the correct section on the Intuit Developer Dashboard.`); + return false; + } + + const connectedCompanyId = this.quickbooks.$auth.company_id; + if (webhookCompanyId !== connectedCompanyId) { + console.log(`Error: Cannot retrieve record details for incoming webhook. The QuickBooks company id + of the incoming event (${webhookCompanyId}) does not match the company id of the account + currently connected to this source in Pipedream (${connectedCompanyId}).`); + return false; } + return true; }, - verifyWebhookRequest(token, payload, header) { + verifyWebhookRequest(event) { + const token = this.webhookVerifierToken; + const payload = event.bodyRaw; + const header = event.headers["intuit-signature"]; const hash = createHmac("sha256", token).update(payload) .digest("hex"); const convertedHeader = Buffer.from(header, "base64").toString("hex"); @@ -103,10 +79,43 @@ module.exports = { // console.log('Header: ', converted_header) return hash === convertedHeader; }, + + async validateAndEmit(entity) { + // individual source modules can redefine this method to specify criteria + // for which events to emit + await this.processEvent(entity); + }, + + async processEvent(entity) { + const eventToEmit = { + event_notification: entity, + record_details: {}, + }; + // Unless the record has been deleted, use the id received in the webhook + // to get the full record data + if (entity.operation !== "Delete") { + eventToEmit.record_details = await this.quickbooks + .getRecordDetails(entity.name, entity.id); + } + + const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}`; + this.$emit(eventToEmit, { + summary, + }); + }, }, async run(event) { const { entities } = event.body.eventNotifications[0].dataChangeEvent; this.sendHttpResponse(event, entities); - await Promise.all(entities.map((entity) => this.validateAndEmit(event, entity))); + + const webhookCompanyId = event.body.eventNotifications[0].realmId; + if (!this.isValidSource(event, webhookCompanyId)) { + console.log("Skipping event from unrecognized source."); + } else { + await Promise.all(entities.map((entity) => { + entity.realmId = webhookCompanyId; + return this.validateAndEmit(entity); + })); + } }, }; diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 92a45209ed55a..2ad0f3d95687a 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -25,7 +25,7 @@ module.exports = { }, methods: { ...common.methods, - async validateAndEmit(event, entity) { + async validateAndEmit(entity) { // only emit events that match the entity names and operations indicated by the user // but if the props are left empty, emit all events rather than filtering them all out // (it would a hassle for the user to select every option if they wanted to emit everything) @@ -35,7 +35,7 @@ module.exports = { && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { - await this.processEvent(event, entity); + await this.processEvent(entity); } }, }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 8f47df60ec757..4d146069feb16 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -27,7 +27,7 @@ module.exports = { }, methods: { ...common.methods, - async validateAndEmit(event, entity) { + async validateAndEmit(entity) { // only emit events that match the specified entity name and operation // but if the operations prop is left empty, emit all events rather // than filtering them all out @@ -39,7 +39,7 @@ module.exports = { && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); } else { - await this.processEvent(event, entity); + await this.processEvent(entity); } }, }, From 6ce4036fa02e0ce44a35c31486f40e6d738b1be6 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 15:47:34 -0500 Subject: [PATCH 80/96] Remove empty lines between methods in common.js --- components/quickbooks/sources/common.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 311d1b71ff8b0..065e33b50d83f 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -22,7 +22,6 @@ module.exports = { getSupportedOperations(entityName) { return SUPPORTED_WEBHOOK_OPERATIONS[entityName]; }, - toPastTense(operations) { const pastTenseVersion = { Create: "Created", @@ -38,7 +37,6 @@ module.exports = { return pastTenseVersion[operations]; } }, - sendHttpResponse(event, entities) { this.http.respond({ status: 200, @@ -48,7 +46,6 @@ module.exports = { }, }); }, - isValidSource(event, webhookCompanyId) { const isWebhookValid = this.verifyWebhookRequest(event); if (!isWebhookValid) { @@ -66,7 +63,6 @@ module.exports = { } return true; }, - verifyWebhookRequest(event) { const token = this.webhookVerifierToken; const payload = event.bodyRaw; @@ -79,13 +75,11 @@ module.exports = { // console.log('Header: ', converted_header) return hash === convertedHeader; }, - async validateAndEmit(entity) { // individual source modules can redefine this method to specify criteria // for which events to emit await this.processEvent(entity); }, - async processEvent(entity) { const eventToEmit = { event_notification: entity, From 9d84607622beffee3be09c57b87b3dcbd0f91234 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 15:56:31 -0500 Subject: [PATCH 81/96] Convert companyId() to public method and use in common.js --- components/quickbooks/quickbooks.app.js | 6 +++--- components/quickbooks/sources/common.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index eea443c41a400..bca6ad345655b 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -40,7 +40,7 @@ module.exports = { _authToken() { return this.$auth.oauth_access_token; }, - _companyId() { + companyId() { return this.$auth.company_id; }, _makeRequestConfig(config = {}) { @@ -70,7 +70,7 @@ module.exports = { return await axios($, requestConfig); }, async getPDF($, entity, id) { - const companyId = this._companyId(); + const companyId = this.companyId(); return await this._makeRequest($, { path: `company/${companyId}/${entity.toLowerCase()}/${id}/pdf`, headers: { @@ -80,7 +80,7 @@ module.exports = { }); }, async getRecordDetails(entityName, id) { - const companyId = this._companyId(); + const companyId = this.companyId(); return await this._makeRequest(this, { path: `company/${companyId}/${entityName.toLowerCase()}/${id}`, }); diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 065e33b50d83f..d903c0bb542ee 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -54,7 +54,7 @@ module.exports = { return false; } - const connectedCompanyId = this.quickbooks.$auth.company_id; + const connectedCompanyId = this.quickbooks.companyId(); if (webhookCompanyId !== connectedCompanyId) { console.log(`Error: Cannot retrieve record details for incoming webhook. The QuickBooks company id of the incoming event (${webhookCompanyId}) does not match the company id of the account From dc15a1ebe10179666559b78cd27a7abbd38b24a2 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 16:01:52 -0500 Subject: [PATCH 82/96] Include timestamp when emitting event. --- components/quickbooks/sources/common.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index d903c0bb542ee..70f536db6b745 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -93,8 +93,12 @@ module.exports = { } const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}`; + const ts = entity?.lastUpdated + ? Date.parse(entity.lastUpdated) + : Date.now(); this.$emit(eventToEmit, { summary, + ts, }); }, }, From 5cc57d4f5bfd85f262826cce2f06f71f80ace3b3 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 16:07:43 -0500 Subject: [PATCH 83/96] Include id when emitting event. --- components/quickbooks/sources/common.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 70f536db6b745..88326555507b4 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -81,6 +81,7 @@ module.exports = { await this.processEvent(entity); }, async processEvent(entity) { + const {name, id, operation} = entity const eventToEmit = { event_notification: entity, record_details: {}, @@ -89,14 +90,21 @@ module.exports = { // to get the full record data if (entity.operation !== "Delete") { eventToEmit.record_details = await this.quickbooks - .getRecordDetails(entity.name, entity.id); + .getRecordDetails(name, id); } - - const summary = `${entity.name} ${entity.id} ${this.toPastTense(entity.operation)}`; + const summary = `${name} ${id} ${this.toPastTense(operation)}`; const ts = entity?.lastUpdated ? Date.parse(entity.lastUpdated) : Date.now(); + const event_id = [ + name, + id, + operation, + ts, + ].join("-"); + console.log(event_id) this.$emit(eventToEmit, { + id: event_id, summary, ts, }); From d752bda2832ac1be58cfce60251f688ba42eb871 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 16:13:22 -0500 Subject: [PATCH 84/96] Minor refactoring --- components/quickbooks/sources/common.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 88326555507b4..4888a35ec852a 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -81,20 +81,19 @@ module.exports = { await this.processEvent(entity); }, async processEvent(entity) { - const {name, id, operation} = entity + const {name, id, operation, lastUpdated} = entity + // Unless the record has been deleted, use the id received in the webhook + // to get the full record data const eventToEmit = { event_notification: entity, - record_details: {}, + record_details: operation === "Delete" + ? {} + : await this.quickbooks.getRecordDetails(name, id), }; - // Unless the record has been deleted, use the id received in the webhook - // to get the full record data - if (entity.operation !== "Delete") { - eventToEmit.record_details = await this.quickbooks - .getRecordDetails(name, id); - } + const summary = `${name} ${id} ${this.toPastTense(operation)}`; - const ts = entity?.lastUpdated - ? Date.parse(entity.lastUpdated) + const ts = lastUpdated + ? Date.parse(lastUpdated) : Date.now(); const event_id = [ name, @@ -102,7 +101,6 @@ module.exports = { operation, ts, ].join("-"); - console.log(event_id) this.$emit(eventToEmit, { id: event_id, summary, From 60856e1b7a50213c0a7eeee837cda2eb315497c5 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 16:13:47 -0500 Subject: [PATCH 85/96] Remove unused code --- components/quickbooks/sources/common.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 4888a35ec852a..0cb85a717d712 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -70,9 +70,6 @@ module.exports = { const hash = createHmac("sha256", token).update(payload) .digest("hex"); const convertedHeader = Buffer.from(header, "base64").toString("hex"); - // console.log('Payload: ', payload) - // console.log('Hash: ', hash) - // console.log('Header: ', converted_header) return hash === convertedHeader; }, async validateAndEmit(entity) { From 6e70342fe845233a1c291e2afbfe865d58414800 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 16:15:36 -0500 Subject: [PATCH 86/96] Change name to Custom Webhook Events --- .../sources/custom-webhook-events/custom-webhook-events.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 2ad0f3d95687a..dd9fb81c53875 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -4,8 +4,8 @@ const common = require("../common"); module.exports = { ...common, key: "quickbooks-custom-webhook-events", - name: "Custom Set of Webhook Entities (Created, Updated, Merged, Deleted, Voided or Emailed)", - description: "Emit events for more than one type of entity (e.g. \"Emailed Invoices and Purchase Orders\" or \"New and Modified Customers and Vendors\"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", + name: "Custom Webhook Events (Created, Updated, Merged, Deleted, Voided or Emailed)", // eslint-disable-line + description: "Emit new events for more than one type of entity (e.g. \"Emailed Invoices and Purchase Orders\" or \"New and Modified Customers and Vendors\"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", version: "0.0.1", type: "source", props: { From 0caf494cfe37999dc92130e9548ec0268aeaa8ba Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 16:16:56 -0500 Subject: [PATCH 87/96] Add (Instant) to webhook source names --- .../sources/custom-webhook-events/custom-webhook-events.js | 2 +- .../new-or-modified-customer/new-or-modified-customer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index dd9fb81c53875..ab3ef69077e75 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -4,7 +4,7 @@ const common = require("../common"); module.exports = { ...common, key: "quickbooks-custom-webhook-events", - name: "Custom Webhook Events (Created, Updated, Merged, Deleted, Voided or Emailed)", // eslint-disable-line + name: "Custom Webhook Events: Created, Updated, Merged, Deleted, Voided or Emailed (Instant)", // eslint-disable-line description: "Emit new events for more than one type of entity (e.g. \"Emailed Invoices and Purchase Orders\" or \"New and Modified Customers and Vendors\"). Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", version: "0.0.1", type: "source", diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 4d146069feb16..285f9b535797e 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -8,7 +8,7 @@ const supportedOperations = common.methods.getSupportedOperations(sourceEntity); module.exports = { ...common, key: "quickbooks-new-or-modified-customer", - name: "New or Modified Customer (Created, Updated, Merged or Deleted)", + name: "New or Modified Customer: Created, Updated, Merged or Deleted (Instant)", description: "Emit new or modified customers. Visit the documentation page to learn how to configure webhooks for your QuickBooks company: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks", version: "0.0.1", type: "source", From 2225f9c3f980a51065ec4f430ced79f6f21838f3 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 18:55:25 -0500 Subject: [PATCH 88/96] Convert if else statements to if() statements that return --- components/quickbooks/sources/common.js | 12 +++++++----- .../custom-webhook-events/custom-webhook-events.js | 10 ++++++---- .../new-or-modified-customer.js | 8 +++++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 0cb85a717d712..109b0cf4c6c9e 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -112,11 +112,13 @@ module.exports = { const webhookCompanyId = event.body.eventNotifications[0].realmId; if (!this.isValidSource(event, webhookCompanyId)) { console.log("Skipping event from unrecognized source."); - } else { - await Promise.all(entities.map((entity) => { - entity.realmId = webhookCompanyId; - return this.validateAndEmit(entity); - })); + return; + } + + await Promise.all(entities.map((entity) => { + entity.realmId = webhookCompanyId; + return this.validateAndEmit(entity); + })); } }, }; diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index ab3ef69077e75..3b20fd7ee0877 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -31,12 +31,14 @@ module.exports = { // (it would a hassle for the user to select every option if they wanted to emit everything) if (this.namesToEmit.length > 0 && !this.namesToEmit.includes(entity.name)) { console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`); - } else if (this.operationsToEmit.length > 0 + return; + } + if (this.operationsToEmit.length > 0 && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); - } else { - await this.processEvent(entity); - } + return; + } + await this.processEvent(entity); }, }, }; diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 285f9b535797e..3897ee69e1c5b 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -35,12 +35,14 @@ module.exports = { // if they wanted to emit everything) if (entity.name !== sourceEntity) { console.log(`${entity.name} webhook received and ignored, since it is not a Customer`); - } else if (this.operationsToEmit.length > 0 + return; + } + if (this.operationsToEmit.length > 0 && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); - } else { - await this.processEvent(entity); + return; } + await this.processEvent(entity); }, }, }; From 245ea128adf8b1bf3f1bf84be9e3d4aa3e1d92e9 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 4 Dec 2021 18:57:22 -0500 Subject: [PATCH 89/96] ESLint Fixes --- components/quickbooks/sources/common.js | 16 ++++++++++------ .../custom-webhook-events.js | 2 +- .../new-or-modified-customer.js | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 109b0cf4c6c9e..ee788e56e2ff8 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -78,12 +78,17 @@ module.exports = { await this.processEvent(entity); }, async processEvent(entity) { - const {name, id, operation, lastUpdated} = entity + const { + name, + id, + operation, + lastUpdated, + } = entity; // Unless the record has been deleted, use the id received in the webhook // to get the full record data const eventToEmit = { event_notification: entity, - record_details: operation === "Delete" + record_details: operation === "Delete" ? {} : await this.quickbooks.getRecordDetails(name, id), }; @@ -92,14 +97,14 @@ module.exports = { const ts = lastUpdated ? Date.parse(lastUpdated) : Date.now(); - const event_id = [ + const eventId = [ name, id, operation, ts, ].join("-"); this.$emit(eventToEmit, { - id: event_id, + id: eventId, summary, ts, }); @@ -113,12 +118,11 @@ module.exports = { if (!this.isValidSource(event, webhookCompanyId)) { console.log("Skipping event from unrecognized source."); return; - } + } await Promise.all(entities.map((entity) => { entity.realmId = webhookCompanyId; return this.validateAndEmit(entity); })); - } }, }; diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 3b20fd7ee0877..49f4aec95203f 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -37,7 +37,7 @@ module.exports = { && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); return; - } + } await this.processEvent(entity); }, }, diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 3897ee69e1c5b..ddb7fde49dbae 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -36,11 +36,11 @@ module.exports = { if (entity.name !== sourceEntity) { console.log(`${entity.name} webhook received and ignored, since it is not a Customer`); return; - } + } if (this.operationsToEmit.length > 0 && !this.operationsToEmit.includes(entity.operation)) { console.log(`Operation '${entity.operation}' not found in list of selected Operations`); - return; + return; } await this.processEvent(entity); }, From 27b61b802ef111db9af0227d47d2d6527f14f87c Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 11 Dec 2021 18:54:54 -0500 Subject: [PATCH 90/96] Refactor to move validateAndEmit() logic to common.js --- components/quickbooks/sources/common.js | 119 +++++++++++------- .../custom-webhook-events.js | 21 +--- .../new-or-modified-customer.js | 21 +--- 3 files changed, 88 insertions(+), 73 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index ee788e56e2ff8..3f8bbec1e7049 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -1,6 +1,10 @@ const quickbooks = require("../quickbooks.app"); const { createHmac } = require("crypto"); -const { SUPPORTED_WEBHOOK_OPERATIONS } = require("../constants"); +const { + WEBHOOK_ENTITIES, + WEBHOOK_OPERATIONS, + SUPPORTED_WEBHOOK_OPERATIONS, +} = require("../constants"); module.exports = { props: { @@ -19,6 +23,9 @@ module.exports = { }, }, methods: { + companyId(event) { + return event.body.eventNotifications[0].realmId; + }, getSupportedOperations(entityName) { return SUPPORTED_WEBHOOK_OPERATIONS[entityName]; }, @@ -37,16 +44,16 @@ module.exports = { return pastTenseVersion[operations]; } }, - sendHttpResponse(event, entities) { - this.http.respond({ - status: 200, - body: entities, - headers: { - "Content-Type": event.headers["Content-Type"], - }, - }); + verifyWebhookRequest(event) { + const token = this.webhookVerifierToken; + const payload = event.bodyRaw; + const header = event.headers["intuit-signature"]; + const hash = createHmac("sha256", token).update(payload) + .digest("hex"); + const convertedHeader = Buffer.from(header, "base64").toString("hex"); + return hash === convertedHeader; }, - isValidSource(event, webhookCompanyId) { + isValidSource(event) { const isWebhookValid = this.verifyWebhookRequest(event); if (!isWebhookValid) { console.log(`Error: Webhook did not pass verification. Try reentering the verifier token, @@ -54,6 +61,7 @@ module.exports = { return false; } + const webhookCompanyId = this.companyId(event); const connectedCompanyId = this.quickbooks.companyId(); if (webhookCompanyId !== connectedCompanyId) { console.log(`Error: Cannot retrieve record details for incoming webhook. The QuickBooks company id @@ -63,36 +71,54 @@ module.exports = { } return true; }, - verifyWebhookRequest(event) { - const token = this.webhookVerifierToken; - const payload = event.bodyRaw; - const header = event.headers["intuit-signature"]; - const hash = createHmac("sha256", token).update(payload) - .digest("hex"); - const convertedHeader = Buffer.from(header, "base64").toString("hex"); - return hash === convertedHeader; + getEntities() { + return WEBHOOK_ENTITIES; }, - async validateAndEmit(entity) { - // individual source modules can redefine this method to specify criteria - // for which events to emit - await this.processEvent(entity); + getOperations() { + return WEBHOOK_OPERATIONS; }, - async processEvent(entity) { + isEntityRelevant(entity) { const { name, - id, operation, - lastUpdated, } = entity; - // Unless the record has been deleted, use the id received in the webhook - // to get the full record data - const eventToEmit = { - event_notification: entity, - record_details: operation === "Delete" - ? {} - : await this.quickbooks.getRecordDetails(name, id), + const relevantEntities = this.getEntities(); + const relevantOperations = this.getOperations(); + + if (!relevantEntities.includes(name)) { + console.log(`Skipping '${operation} ${name}' event. (Accepted entities: ${relevantEntities.join(", ")})`); + return false; + } + if (!relevantOperations.includes(operation)) { + console.log(`Skipping '${operation} ${name}' event. (Accepted operations: ${relevantOperations.join(", ")})`); + return false; + } + return true; + }, + async generateEvent(entity, event) { + const eventDetails = { + ...entity, + companyId: this.companyId(event), }; + // Unless the record has been deleted, use the id received in the webhook + // to get the full record data from QuickBooks + const recordDetails = entity.operation === "Delete" + ? {} + : await this.quickbooks.getRecordDetails(entity.name, entity.id); + + return { + eventDetails, + recordDetails, + }; + }, + generateMeta(event) { + const { + name, + id, + operation, + lastUpdated, + } = event.eventDetails; const summary = `${name} ${id} ${this.toPastTense(operation)}`; const ts = lastUpdated ? Date.parse(lastUpdated) @@ -103,26 +129,35 @@ module.exports = { operation, ts, ].join("-"); - this.$emit(eventToEmit, { + return { id: eventId, summary, ts, + }; + }, + async processEvent(event) { + const { entities } = event.body.eventNotifications[0].dataChangeEvent; + + const events = await Promise.all(entities + .filter(this.isEntityRelevant) + .map(async (entity) => { + // Generate events asynchronously to fetch multiple records from the API at the same time + return await this.generateEvent(entity, event); + })); + + events.forEach((event) => { + const meta = this.generateMeta(event); + this.$emit(event, meta); }); }, + }, async run(event) { - const { entities } = event.body.eventNotifications[0].dataChangeEvent; - this.sendHttpResponse(event, entities); - - const webhookCompanyId = event.body.eventNotifications[0].realmId; - if (!this.isValidSource(event, webhookCompanyId)) { + if (!this.isValidSource(event)) { console.log("Skipping event from unrecognized source."); return; } - await Promise.all(entities.map((entity) => { - entity.realmId = webhookCompanyId; - return this.validateAndEmit(entity); - })); + return this.processEvent(event); }, }; diff --git a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js index 49f4aec95203f..af19f56e1c1bb 100644 --- a/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js +++ b/components/quickbooks/sources/custom-webhook-events/custom-webhook-events.js @@ -10,7 +10,7 @@ module.exports = { type: "source", props: { ...common.props, - namesToEmit: { + entitiesToEmit: { propDefinition: [ quickbooks, "webhookNames", @@ -25,20 +25,11 @@ module.exports = { }, methods: { ...common.methods, - async validateAndEmit(entity) { - // only emit events that match the entity names and operations indicated by the user - // but if the props are left empty, emit all events rather than filtering them all out - // (it would a hassle for the user to select every option if they wanted to emit everything) - if (this.namesToEmit.length > 0 && !this.namesToEmit.includes(entity.name)) { - console.log(`Entity Type '${entity.name}' not found in list of selected Entity Types`); - return; - } - if (this.operationsToEmit.length > 0 - && !this.operationsToEmit.includes(entity.operation)) { - console.log(`Operation '${entity.operation}' not found in list of selected Operations`); - return; - } - await this.processEvent(entity); + getEntities() { + return this.entitiesToEmit; + }, + getOperations() { + return this.operationsToEmit; }, }, }; diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index ddb7fde49dbae..391939e394be0 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -27,22 +27,11 @@ module.exports = { }, methods: { ...common.methods, - async validateAndEmit(entity) { - // only emit events that match the specified entity name and operation - // but if the operations prop is left empty, emit all events rather - // than filtering them all out - // (it would a hassle for the user to select every single option - // if they wanted to emit everything) - if (entity.name !== sourceEntity) { - console.log(`${entity.name} webhook received and ignored, since it is not a Customer`); - return; - } - if (this.operationsToEmit.length > 0 - && !this.operationsToEmit.includes(entity.operation)) { - console.log(`Operation '${entity.operation}' not found in list of selected Operations`); - return; - } - await this.processEvent(entity); + getEntities() { + return [sourceEntity]; + }, + getOperations() { + return this.operationsToEmit; }, }, }; From a59b99baed337bd8b037f93d12e33c24b1b3f8a3 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 11 Dec 2021 19:23:39 -0500 Subject: [PATCH 91/96] Add an error message to getPDF() prompting the user to double-check the record ID --- components/quickbooks/quickbooks.app.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/components/quickbooks/quickbooks.app.js b/components/quickbooks/quickbooks.app.js index bca6ad345655b..de984d7de231f 100644 --- a/components/quickbooks/quickbooks.app.js +++ b/components/quickbooks/quickbooks.app.js @@ -70,14 +70,22 @@ module.exports = { return await axios($, requestConfig); }, async getPDF($, entity, id) { - const companyId = this.companyId(); - return await this._makeRequest($, { - path: `company/${companyId}/${entity.toLowerCase()}/${id}/pdf`, - headers: { - "Accept": "application/pdf", - }, - responseType: "stream", - }); + try { + const companyId = this.companyId(); + return await this._makeRequest($, { + path: `company/${companyId}/${entity.toLowerCase()}/${id}/pdf`, + headers: { + "Accept": "application/pdf", + }, + responseType: "stream", + }); + } catch (ex) { + if (ex.response.data.statusCode === 400) { + throw new Error(`Request failed with status code 400. Double-check that '${id}' is a valid ${entity} record ID.`); + } else { + throw ex; + } + } }, async getRecordDetails(entityName, id) { const companyId = this.companyId(); From 7c3c6891dffe75c6eda20bd9133b3d11a141411c Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 11 Dec 2021 19:24:55 -0500 Subject: [PATCH 92/96] Revert key and version of download-pdf.js now that testing is complete. --- components/quickbooks/actions/download-pdf/download-pdf.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index f4b61b0b8e17d..9bdd61efb7a18 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -6,10 +6,8 @@ const stream = require("stream"); module.exports = { name: "Download PDF", description: "Download an invoice, bill, purchase order or other QuickBooks entity as a PDF and save it in [Pipedream's temporary file system](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory) for use in a later step.", - key: "download-pdf", - version: "0.2.8", - // key: "quickbooks-download-pdf", - // version: "0.0.1", + key: "quickbooks-download-pdf", + version: "0.0.1", type: "action", props: { quickbooks, From 7931dd881ebd55b5557105f2fe680dae0cb0992b Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sat, 11 Dec 2021 19:40:26 -0500 Subject: [PATCH 93/96] ESLint fix --- .../new-or-modified-customer/new-or-modified-customer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js index 391939e394be0..ad9ff6ef4f8fe 100644 --- a/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js +++ b/components/quickbooks/sources/new-or-modified-customer/new-or-modified-customer.js @@ -28,7 +28,9 @@ module.exports = { methods: { ...common.methods, getEntities() { - return [sourceEntity]; + return [ + sourceEntity, + ]; }, getOperations() { return this.operationsToEmit; From 953959364fb763f056c9b730f420d35f7ba2158a Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 20 Mar 2022 20:16:51 -0400 Subject: [PATCH 94/96] Fix logic in isEntityRelevant() to include all entities and operations if the user left the props empty --- components/quickbooks/sources/common.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 3f8bbec1e7049..2fca21e9cf960 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -84,12 +84,15 @@ module.exports = { } = entity; const relevantEntities = this.getEntities(); const relevantOperations = this.getOperations(); - - if (!relevantEntities.includes(name)) { + + // only emit events that match the entity names and operations indicated by the user + // but if the props are left empty, emit all events rather than filtering them all out + // (it would a hassle for the user to select every option if they wanted to emit everything) + if (relevantEntities?.length > 0 && !relevantEntities.includes(name)) { console.log(`Skipping '${operation} ${name}' event. (Accepted entities: ${relevantEntities.join(", ")})`); return false; } - if (!relevantOperations.includes(operation)) { + if (!relevantOperations?.length > 0 && !relevantOperations.includes(operation)) { console.log(`Skipping '${operation} ${name}' event. (Accepted operations: ${relevantOperations.join(", ")})`); return false; } From 238ea2843fb01e4bceef2fbefedcbbda820bb8e7 Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Sun, 20 Mar 2022 20:59:43 -0400 Subject: [PATCH 95/96] Add http response to webhook --- components/quickbooks/sources/common.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/quickbooks/sources/common.js b/components/quickbooks/sources/common.js index 2fca21e9cf960..234b1f83e486a 100644 --- a/components/quickbooks/sources/common.js +++ b/components/quickbooks/sources/common.js @@ -84,7 +84,7 @@ module.exports = { } = entity; const relevantEntities = this.getEntities(); const relevantOperations = this.getOperations(); - + // only emit events that match the entity names and operations indicated by the user // but if the props are left empty, emit all events rather than filtering them all out // (it would a hassle for the user to select every option if they wanted to emit everything) @@ -161,6 +161,10 @@ module.exports = { return; } + this.http.respond({ + status: 200, + }); + return this.processEvent(event); }, }; From e63bb3796578595efca1ca41fd1dc8aa7fc686bf Mon Sep 17 00:00:00 2001 From: Celeste Bancos Date: Mon, 21 Mar 2022 21:42:56 -0400 Subject: [PATCH 96/96] Add a description to the Record ID prop explaining how to find the record ID --- components/quickbooks/actions/download-pdf/download-pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/quickbooks/actions/download-pdf/download-pdf.js b/components/quickbooks/actions/download-pdf/download-pdf.js index 9bdd61efb7a18..0c9a826f652e0 100644 --- a/components/quickbooks/actions/download-pdf/download-pdf.js +++ b/components/quickbooks/actions/download-pdf/download-pdf.js @@ -28,7 +28,7 @@ module.exports = { id: { type: "string", label: "Record ID", - description: null, + description: "Use the 'Id' property of the record's JSON object or open the record for editing in Quickbooks Online to find its ID in the URL after 'txnId=', e.g. 'https://app.qbo.intuit.com/app/invoice?txnId=22743'", }, fileName: { type: "string",