11const quickbooks = require ( "../quickbooks.app" ) ;
22const { createHmac } = require ( "crypto" ) ;
3- const { SUPPORTED_WEBHOOK_OPERATIONS } = require ( "../constants" ) ;
3+ const {
4+ WEBHOOK_ENTITIES ,
5+ WEBHOOK_OPERATIONS ,
6+ SUPPORTED_WEBHOOK_OPERATIONS ,
7+ } = require ( "../constants" ) ;
48
59module . exports = {
610 props : {
@@ -19,6 +23,9 @@ module.exports = {
1923 } ,
2024 } ,
2125 methods : {
26+ companyId ( event ) {
27+ return event . body . eventNotifications [ 0 ] . realmId ;
28+ } ,
2229 getSupportedOperations ( entityName ) {
2330 return SUPPORTED_WEBHOOK_OPERATIONS [ entityName ] ;
2431 } ,
@@ -37,23 +44,24 @@ module.exports = {
3744 return pastTenseVersion [ operations ] ;
3845 }
3946 } ,
40- sendHttpResponse ( event , entities ) {
41- this . http . respond ( {
42- status : 200 ,
43- body : entities ,
44- headers : {
45- "Content-Type" : event . headers [ "Content-Type" ] ,
46- } ,
47- } ) ;
47+ verifyWebhookRequest ( event ) {
48+ const token = this . webhookVerifierToken ;
49+ const payload = event . bodyRaw ;
50+ const header = event . headers [ "intuit-signature" ] ;
51+ const hash = createHmac ( "sha256" , token ) . update ( payload )
52+ . digest ( "hex" ) ;
53+ const convertedHeader = Buffer . from ( header , "base64" ) . toString ( "hex" ) ;
54+ return hash === convertedHeader ;
4855 } ,
49- isValidSource ( event , webhookCompanyId ) {
56+ isValidSource ( event ) {
5057 const isWebhookValid = this . verifyWebhookRequest ( event ) ;
5158 if ( ! isWebhookValid ) {
5259 console . log ( `Error: Webhook did not pass verification. Try reentering the verifier token,
5360 making sure it's from the correct section on the Intuit Developer Dashboard.` ) ;
5461 return false ;
5562 }
5663
64+ const webhookCompanyId = this . companyId ( event ) ;
5765 const connectedCompanyId = this . quickbooks . companyId ( ) ;
5866 if ( webhookCompanyId !== connectedCompanyId ) {
5967 console . log ( `Error: Cannot retrieve record details for incoming webhook. The QuickBooks company id
@@ -63,36 +71,54 @@ module.exports = {
6371 }
6472 return true ;
6573 } ,
66- verifyWebhookRequest ( event ) {
67- const token = this . webhookVerifierToken ;
68- const payload = event . bodyRaw ;
69- const header = event . headers [ "intuit-signature" ] ;
70- const hash = createHmac ( "sha256" , token ) . update ( payload )
71- . digest ( "hex" ) ;
72- const convertedHeader = Buffer . from ( header , "base64" ) . toString ( "hex" ) ;
73- return hash === convertedHeader ;
74+ getEntities ( ) {
75+ return WEBHOOK_ENTITIES ;
7476 } ,
75- async validateAndEmit ( entity ) {
76- // individual source modules can redefine this method to specify criteria
77- // for which events to emit
78- await this . processEvent ( entity ) ;
77+ getOperations ( ) {
78+ return WEBHOOK_OPERATIONS ;
7979 } ,
80- async processEvent ( entity ) {
80+ isEntityRelevant ( entity ) {
8181 const {
8282 name,
83- id,
8483 operation,
85- lastUpdated,
8684 } = entity ;
87- // Unless the record has been deleted, use the id received in the webhook
88- // to get the full record data
89- const eventToEmit = {
90- event_notification : entity ,
91- record_details : operation === "Delete"
92- ? { }
93- : await this . quickbooks . getRecordDetails ( name , id ) ,
85+ const relevantEntities = this . getEntities ( ) ;
86+ const relevantOperations = this . getOperations ( ) ;
87+
88+ if ( ! relevantEntities . includes ( name ) ) {
89+ console . log ( `Skipping '${ operation } ${ name } ' event. (Accepted entities: ${ relevantEntities . join ( ", " ) } )` ) ;
90+ return false ;
91+ }
92+ if ( ! relevantOperations . includes ( operation ) ) {
93+ console . log ( `Skipping '${ operation } ${ name } ' event. (Accepted operations: ${ relevantOperations . join ( ", " ) } )` ) ;
94+ return false ;
95+ }
96+ return true ;
97+ } ,
98+ async generateEvent ( entity , event ) {
99+ const eventDetails = {
100+ ...entity ,
101+ companyId : this . companyId ( event ) ,
94102 } ;
95103
104+ // Unless the record has been deleted, use the id received in the webhook
105+ // to get the full record data from QuickBooks
106+ const recordDetails = entity . operation === "Delete"
107+ ? { }
108+ : await this . quickbooks . getRecordDetails ( entity . name , entity . id ) ;
109+
110+ return {
111+ eventDetails,
112+ recordDetails,
113+ } ;
114+ } ,
115+ generateMeta ( event ) {
116+ const {
117+ name,
118+ id,
119+ operation,
120+ lastUpdated,
121+ } = event . eventDetails ;
96122 const summary = `${ name } ${ id } ${ this . toPastTense ( operation ) } ` ;
97123 const ts = lastUpdated
98124 ? Date . parse ( lastUpdated )
@@ -103,26 +129,35 @@ module.exports = {
103129 operation ,
104130 ts ,
105131 ] . join ( "-" ) ;
106- this . $emit ( eventToEmit , {
132+ return {
107133 id : eventId ,
108134 summary,
109135 ts,
136+ } ;
137+ } ,
138+ async processEvent ( event ) {
139+ const { entities } = event . body . eventNotifications [ 0 ] . dataChangeEvent ;
140+
141+ const events = await Promise . all ( entities
142+ . filter ( this . isEntityRelevant )
143+ . map ( async ( entity ) => {
144+ // Generate events asynchronously to fetch multiple records from the API at the same time
145+ return await this . generateEvent ( entity , event ) ;
146+ } ) ) ;
147+
148+ events . forEach ( ( event ) => {
149+ const meta = this . generateMeta ( event ) ;
150+ this . $emit ( event , meta ) ;
110151 } ) ;
111152 } ,
153+
112154 } ,
113155 async run ( event ) {
114- const { entities } = event . body . eventNotifications [ 0 ] . dataChangeEvent ;
115- this . sendHttpResponse ( event , entities ) ;
116-
117- const webhookCompanyId = event . body . eventNotifications [ 0 ] . realmId ;
118- if ( ! this . isValidSource ( event , webhookCompanyId ) ) {
156+ if ( ! this . isValidSource ( event ) ) {
119157 console . log ( "Skipping event from unrecognized source." ) ;
120158 return ;
121159 }
122160
123- await Promise . all ( entities . map ( ( entity ) => {
124- entity . realmId = webhookCompanyId ;
125- return this . validateAndEmit ( entity ) ;
126- } ) ) ;
161+ return this . processEvent ( event ) ;
127162 } ,
128163} ;
0 commit comments