Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
6b21e25
Create app file for quickbooks
celestebancos Jul 12, 2021
831711f
Add basic skeleton to quickbooks app file
celestebancos Jul 12, 2021
a425096
Add custom webhook events source with basic common file
celestebancos Jul 15, 2021
e5d06a2
Add propDefinition for operations_to_emit
celestebancos Jul 16, 2021
d0b29bf
Move webhook options list to common.js
celestebancos Jul 16, 2021
ea99e93
Create specific source for customers
celestebancos Jul 16, 2021
8fb858b
Rename variables for readablility
celestebancos Jul 16, 2021
91228bd
Rename variables and change source names.
celestebancos Jul 16, 2021
11b6af2
Add documenation link to specific entity source.
celestebancos Jul 16, 2021
5f89600
Add quickbooks app to custom-webhook-events.js
celestebancos Jul 17, 2021
004c390
Revise names and descriptions to make them more readable
celestebancos Jul 17, 2021
64de7a9
Add the rest of the changed names and descriptions.
celestebancos Jul 17, 2021
c6ed1cd
Hard-code entity name into descriptive text to increase code readability
celestebancos Jul 17, 2021
70cad06
Change common.methods.function() to this.function()
celestebancos Jul 17, 2021
21dbb22
Change describeOperations() to toPastTense()
celestebancos Jul 17, 2021
653ec09
Change this.function() back to common.methods.function()
celestebancos Jul 17, 2021
1029b6f
Add spacing, improve comments
celestebancos Jul 17, 2021
3e8506d
Add common.js methods to custom source file
celestebancos Jul 17, 2021
101fc1b
Add function to create lists with an 'or' at the end
celestebancos Jul 17, 2021
9939441
Revise descriptions to start with 'Emit'
celestebancos Jul 17, 2021
7e67c78
Add default values for operations and convert entity prop to a propDe…
celestebancos Jul 17, 2021
6f0d455
Add oxford comma to readable lists
celestebancos Jul 17, 2021
d49208e
Fix outdated variable name
celestebancos Jul 17, 2021
d0e8509
Change webhook_entities to webhook_names
celestebancos Jul 17, 2021
c776699
Move run function to common.js and create validateAndEmit()
celestebancos Jul 17, 2021
36d30db
Add handling for multiple entities per webhook request
celestebancos Jul 17, 2021
215cfaa
Rename webhook-entity to entity
celestebancos Jul 17, 2021
af5eb91
Minor code clean-up
celestebancos Jul 18, 2021
15bde79
Draft getRecordDetails() - need to figure out how to access auth details
celestebancos Jul 18, 2021
74ab122
Remove unnecessary methods property from method call
celestebancos Jul 18, 2021
70469e5
Emit full record data instead of just the webhook payload
celestebancos Jul 18, 2021
6693df1
Add webhook verification
celestebancos Jul 19, 2021
4c5abf3
Move verifyWebhookRequest() to common.js
celestebancos Jul 20, 2021
ae711d9
Clean up comments
celestebancos Jul 20, 2021
fd3cc0d
Clean up more comments
celestebancos Jul 20, 2021
8baf73c
[Draft] Don't try to get deleted records and check for matching compa…
celestebancos Jul 20, 2021
c5aa467
Move crypto to common.js
celestebancos Jul 20, 2021
f60b3e9
Add error handling to verifyWebhookRequest()
celestebancos Jul 20, 2021
f969c4d
Remove unnecessary return values and asyncs
celestebancos Jul 20, 2021
5303b4d
Tweak error message and prop description
celestebancos Jul 24, 2021
2e7c48c
await this.emitEvent()
celestebancos Jul 24, 2021
b36d12e
Turn off secret for webhook_verifier_token for testing
celestebancos Jul 24, 2021
6f3d9a9
Set webhook_verifier_token back to secret
celestebancos Jul 24, 2021
aeb31be
Fix wrong reference in checking for deleted entity
celestebancos Jul 24, 2021
9e17d3f
Change verify to verifier on error message
celestebancos Jul 25, 2021
04cdc8c
Emit just the entity (plus realmID) instead of the full HTTP request
celestebancos Aug 8, 2021
1f7a854
Merge branch 'master' into quickbooks
celestebancos Aug 8, 2021
50ac040
Fix ESLint errors
celestebancos Aug 15, 2021
aa4b865
Merge branch 'master' into quickbooks
celestebancos Aug 15, 2021
c730c06
Begin drafting Download PDF action
celestebancos Aug 16, 2021
de37b8a
Move download-pdf to folder
celestebancos Aug 16, 2021
551986b
Add quickbooks app
celestebancos Aug 16, 2021
a0a854f
Add single entity prop to quickbooks app file
celestebancos Aug 16, 2021
4c80a38
Get basic version working with props to choose an entity, record id a…
celestebancos Aug 16, 2021
519de09
Replace Entity prop with Document Type prop limited to the downloadab…
celestebancos Aug 16, 2021
4bfe358
Remove single entity prop from quickbooks app for now
celestebancos Aug 16, 2021
ef67ada
Make File Name prop optional
celestebancos Aug 16, 2021
20264e5
Add .pdf extension if it's not already included in the file name
celestebancos Aug 17, 2021
b87879d
Merge branch 'master' into quickbooks
celestebancos Sep 26, 2021
fa90d15
ES Lint Fixes for Download PDF action
celestebancos Sep 26, 2021
ce53add
ES Lint fixes for source files
celestebancos Sep 26, 2021
b600480
Change name and description from dynamic to static
celestebancos Sep 26, 2021
9afe2b5
Change component key to include quickbooks
celestebancos Nov 13, 2021
337a9af
Use $ instead of this for axios request
celestebancos Nov 13, 2021
b4df4fb
Add _makeRequest() method
celestebancos Nov 13, 2021
042f2ef
Reset version number from 0.2.3 to 0.0.1
celestebancos Nov 13, 2021
b446a51
Merge branch 'master' into quickbooks
celestebancos Nov 13, 2021
2caf1eb
ES Lint Fixes
celestebancos Nov 13, 2021
cc71cbe
Revert key and version of download-pdf.js for testing
celestebancos Dec 4, 2021
f05e631
Revise action description and prop description for Download PDF
celestebancos Dec 4, 2021
c958d5f
Remove unused code
celestebancos Dec 4, 2021
42dd4b5
Add summary for Download PDF
celestebancos Dec 4, 2021
084b77f
Move constants to a separate file.
celestebancos Dec 4, 2021
3a525c5
Remove extra slash from url string
celestebancos Dec 4, 2021
f649138
Change prop names to camel case
celestebancos Dec 4, 2021
2c0ed0d
Remove empty lines between method definitions
celestebancos Dec 4, 2021
79b0f8d
Change endpoint to entityName
celestebancos Dec 4, 2021
d76f8d4
Remove unused code
celestebancos Dec 4, 2021
91e0f0d
Move list of supported webhook operations to constants file
celestebancos Dec 4, 2021
4c71d74
Move webhookVerifierToken prop to common file
celestebancos Dec 4, 2021
7607d0d
Change prop names to camel case
celestebancos Dec 4, 2021
7023986
Rename emitEvent() to processEvent()
celestebancos Dec 4, 2021
7f3a2ab
Refactor webhook validation to happen once per event instead of once …
celestebancos Dec 4, 2021
6ce4036
Remove empty lines between methods in common.js
celestebancos Dec 4, 2021
9d84607
Convert companyId() to public method and use in common.js
celestebancos Dec 4, 2021
dc15a1e
Include timestamp when emitting event.
celestebancos Dec 4, 2021
5cc57d4
Include id when emitting event.
celestebancos Dec 4, 2021
d752bda
Minor refactoring
celestebancos Dec 4, 2021
60856e1
Remove unused code
celestebancos Dec 4, 2021
6e70342
Change name to Custom Webhook Events
celestebancos Dec 4, 2021
0caf494
Add (Instant) to webhook source names
celestebancos Dec 4, 2021
2225f9c
Convert if else statements to if() statements that return
celestebancos Dec 4, 2021
245ea12
ESLint Fixes
celestebancos Dec 4, 2021
27b61b8
Refactor to move validateAndEmit() logic to common.js
celestebancos Dec 11, 2021
a59b99b
Add an error message to getPDF() prompting the user to double-check t…
celestebancos Dec 12, 2021
7c3c689
Revert key and version of download-pdf.js now that testing is complete.
celestebancos Dec 12, 2021
4a2994c
Merge branch 'master' into quickbooks
celestebancos Dec 12, 2021
7931dd8
ESLint fix
celestebancos Dec 12, 2021
9539593
Fix logic in isEntityRelevant() to include all entities and operation…
celestebancos Mar 21, 2022
238ea28
Add http response to webhook
celestebancos Mar 21, 2022
e63bb37
Add a description to the Record ID prop explaining how to find the re…
celestebancos Mar 22, 2022
5861368
Merge branch 'master' into quickbooks
celestebancos Mar 22, 2022
5c54656
Merge branch 'master' into quickbooks
celestebancos Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions components/quickbooks/actions/download-pdf/download-pdf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const quickbooks = require("../../quickbooks.app");
const fs = require("fs");
const { promisify } = require("util");
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: "quickbooks-download-pdf",
version: "0.0.1",
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: "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",
label: "File Name (Optional)",
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,
},
},
methods: {
async downloadPDF($, entity, id, fileName) {
const file = await this.quickbooks.getPDF($, entity, id);

const filePath = "/tmp/" + fileName;
const pipeline = promisify(stream.pipeline);
await pipeline(
file,
fs.createWriteStream(filePath),
);
return filePath;
},
},
async run({ $ }) {
const fileName = this.fileName || this.id;
const fileNameWithExtension = fileName.endsWith(".pdf")
? fileName
: fileName + ".pdf";

const filePath = await this.downloadPDF($, this.entity, this.id, fileNameWithExtension);
$.export("file_path", filePath);
$.export("file_name", fileNameWithExtension);
$.export("$summary", `Successfully downloaded file: ${fileNameWithExtension}`);
},
};
210 changes: 210 additions & 0 deletions components/quickbooks/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
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",
];

// 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,
};
97 changes: 97 additions & 0 deletions components/quickbooks/quickbooks.app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const { axios } = require("@pipedream/platform");
const {
WEBHOOK_ENTITIES,
WEBHOOK_OPERATIONS,
} = require("./constants");

module.exports = {
type: "app",
app: "quickbooks",
propDefinitions: {
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,
},
webhookOperations: {
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,
},
webhookVerifierToken: {
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: {
_apiUrl() {
return "https://quickbooks.api.intuit.com/v3";
},
_authToken() {
return this.$auth.oauth_access_token;
},
companyId() {
return this.$auth.company_id;
},
Comment on lines +43 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Maintain consistent method naming conventions

Private methods are typically prefixed with an underscore (e.g., _apiUrl(), _authToken()). For consistency, consider renaming companyId() to _companyId().

Apply this diff to rename the method and update its references:

       },
-      companyId() {
+      _companyId() {
         return this.$auth.company_id;
       },

Update references to the method:

         const companyId = this.companyId();
+        const companyId = this._companyId();

This change applies to all instances where companyId() is called:

  • In the getPDF method (lines 74-75)
  • In the getRecordDetails method (lines 91-92)

Also applies to: 74-93

_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 ${authToken}`,
"Accept": "application/json",
"Content-Type": "application/json",
...headers,
},
url,
...extraConfig,
};
},
async _makeRequest($ = this, config) {
const requestConfig = this._makeRequestConfig(config);
return await axios($, requestConfig);
},
async getPDF($, entity, id) {
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;
}
}
Comment on lines +82 to +88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance error handling in the 'getPDF' method

In the catch block of the getPDF method, accessing ex.response.data.statusCode without checking if ex.response and ex.response.data exist could lead to a runtime error if they are undefined. To prevent potential TypeError, please add checks to ensure these properties are defined before accessing statusCode.

Apply this diff to improve error handling:

         } catch (ex) {
-          if (ex.response.data.statusCode === 400) {
+          if (ex.response && ex.response.data && 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;
           }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} 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;
}
}
} catch (ex) {
if (ex.response && ex.response.data && 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();
return await this._makeRequest(this, {
path: `company/${companyId}/${entityName.toLowerCase()}/${id}`,
});
},
Comment on lines +90 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure consistent usage of the context parameter in 'getRecordDetails'

In the getRecordDetails method, you pass this as the first argument to _makeRequest. However, in other methods like getPDF, you pass $ as the context parameter. For consistency and clarity, consider adding $ as a parameter to getRecordDetails and passing it to _makeRequest.

Apply this diff to make the method consistent:

       },
-      async getRecordDetails(entityName, id) {
+      async getRecordDetails($, entityName, id) {
         const companyId = this.companyId();
-        return await this._makeRequest(this, {
+        return await this._makeRequest($, {
           path: `company/${companyId}/${entityName.toLowerCase()}/${id}`,
         });
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRecordDetails(entityName, id) {
const companyId = this.companyId();
return await this._makeRequest(this, {
path: `company/${companyId}/${entityName.toLowerCase()}/${id}`,
});
},
async getRecordDetails($, entityName, id) {
const companyId = this.companyId();
return await this._makeRequest($, {
path: `company/${companyId}/${entityName.toLowerCase()}/${id}`,
});
},

},
};
Loading
Loading