- 
                Notifications
    You must be signed in to change notification settings 
- Fork 5.5k
QuickBooks Webhook Sources #1564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
6b21e25
              831711f
              a425096
              e5d06a2
              d0b29bf
              ea99e93
              8fb858b
              91228bd
              11b6af2
              5f89600
              004c390
              64de7a9
              c6ed1cd
              70cad06
              21dbb22
              653ec09
              1029b6f
              3e8506d
              101fc1b
              9939441
              7e67c78
              6f0d455
              d49208e
              d0e8509
              c776699
              36d30db
              215cfaa
              af5eb91
              15bde79
              74ab122
              70469e5
              6693df1
              4c5abf3
              ae711d9
              fd3cc0d
              8baf73c
              c5aa467
              f60b3e9
              f969c4d
              5303b4d
              2e7c48c
              b36d12e
              6f3d9a9
              aeb31be
              9e17d3f
              04cdc8c
              1f7a854
              50ac040
              aa4b865
              c730c06
              de37b8a
              551986b
              a0a854f
              4c80a38
              519de09
              4bfe358
              ef67ada
              20264e5
              b87879d
              fa90d15
              ce53add
              b600480
              9afe2b5
              337a9af
              b4df4fb
              042f2ef
              b446a51
              2caf1eb
              cc71cbe
              f05e631
              c958d5f
              42dd4b5
              084b77f
              3a525c5
              f649138
              2c0ed0d
              79b0f8d
              d76f8d4
              91e0f0d
              4c71d74
              7607d0d
              7023986
              7f3a2ab
              6ce4036
              9d84607
              dc15a1e
              5cc57d4
              d752bda
              60856e1
              6e70342
              0caf494
              2225f9c
              245ea12
              27b61b8
              a59b99b
              7c3c689
              4a2994c
              7931dd8
              9539593
              238ea28
              e63bb37
              5861368
              5c54656
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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}`); | ||
| }, | ||
| }; | ||
| 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, | ||
| }; | 
| 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
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.,  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  
 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
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enhance error handling in the 'getPDF' method In the  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
 
        Suggested change
       
 | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| 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
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe 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  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
 
        Suggested change
       
 | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.