johnballantyne / qbws

A Node.js web service for QuickBooks Desktop Web Connector
MIT License
38 stars 26 forks source link

Asynchronous Support? #6

Open MattMorgis opened 7 years ago

MattMorgis commented 7 years ago

I first off wanted to thank you for open sourcing this đź‘Ť I have been working a project that required integrating with the Quickbooks Web Connector and I was up and running within an hour with this module and it acted as a fantastic resource to understand the qbwc implementation!

I started to integrate your work with my project. I'm simply running all orders processed by an e-commerce site through Quickbooks and only using two commands - CustomerAdd and InvoiceAdd.

Each time an order comes through, I save the qbXML needed to create the customer and invoice in a MySQL database. Each time the QBWC comes around polling for any commands to run, I want to replace the buildRequest() call with something like externalModule.fetchRequests(callback) and make it asynchronous since it is impossible to have a synchronous call to MySQL.

What are your thoughts on asynchronous support? Is it even possible? It would effectively make the method look like this:

qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.authenticate =
function (args) {
    var authReturn = [];

    announceMethod('authenticate', args);

    // Code below uses a random GUID to use as a session ticket
    // An example of a GUID is {85B41BEE-5CD9-427a-A61B-83964F1EB426}
    authReturn[0] = uuid.v1();

    // For simplicity of sample, a hardcoded username/password is used.
    // In real world, you should handle authentication in using a standard way.
    // For example, you could validate the username/password against an LDAP
    // or a directory server
    // TODO: This shouldn't be hard coded
    serviceLog('    Password locally stored = ' + password);

    if (args.strUserName.trim() === username && args.strPassword.trim() === password) {
        qbwcManager.fetchRequests(function(err, requests) {
            req = requests;
            if (req.length === 0) {
                authReturn[1] = 'NONE';
            } else {
                // An empty string for authReturn[1] means asking QBWebConnector
                // to connect to the company file that is currently opened in QB
                authReturn[1] = companyFile;
            }
         } else {
            authReturn[1] = 'nvu';
        }

        serviceLog('    Return values: ');
        serviceLog('        string[] authReturn[0] = ' + authReturn[0]);
        serviceLog('        string[] authReturn[1] = ' + authReturn[1]);

        return {
            authenticateResult: { 'string': [authReturn[0], authReturn[1]] }
         };
    });
};
johnballantyne commented 7 years ago

Hey Matt, I'm really happy to hear that this has helped with your project!

Asynchronous is definitely possible, I've done it successfully. But I never got around to updating this project to include it, I should probably get on that.

Here's my answer on Stack Overflow for a skeleton idea of how to do it:

The soap module supports asynchronous function calls which makes this easy to do. To use the same template as my other answer, here's how you'd do that:

var soap = require('soap');

var yourService = {
    QBWebConnectorSvc: {
        QBWebConnectorSvcSoap: {
            serverVersion: function (args, callback) {

                // serverVersion code here

                callback({
                    serverVersionResult: { string: retVal }
                });
            },
            clientVersion: function (args, callback) {

                //clientVersion code here

                callback({
                    clientVersionResult: { string: retVal }
                });
            },

            // and all other service functions required by QBWC

        }
    }
};

There are two differences:

  1. Each method signature has an additional callback parameter
  2. There is no return, that's handled by callback() instead.

I don't currently have a suitable environment to test this, but I created a client to imitate QuickBooks Web Connector and it worked fine. Converting the qbws methods to asynchronous allowed it to service multiple clients simultaneously (including one legitimate QBWC client).

That should point you in the right direction, but please do reach out if you need help with implementation. And thanks for the pull request, I'll add that in when I'm in the office next week.

MattMorgis commented 7 years ago

Thanks a lot for getting back to me so quickly! I poked around at the links and resources you gave me - if you don't mind, I'm going to take a stab at the async version and submit a PR since I'll need that for my project.

With the hard-coded requests, it makes it hard to use this package directly from npm. I would like to externalize that somehow (let me know if I should open another issue). I come from an iOS world so, ideally I would like to pass it my own delegate that implements buildRequests(callback) and processRequests(callback). I'm not entirely sure how JavaScript handles interfaces.

var qbXMLHandler = require('./path/to/your/own/qbXMLHanlder');
var qbws = require('qbws');
qbws.qbXMLHandler('qbXMLHandler');
qbws.run();

///////////////////////////
qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.authenticate =
    function (args) {
        // ...
        qbXMLHandler.buildRequests(function(err, requests) {
        if (!err) {
            req = requests;
         }
        // ... 
    });
};

Is this kinda the direction you were going in with your qbXML project? I'd love to build on what you've done and tie this all together if so.

brandonros commented 7 years ago

@MattMorgis What version of QuickBooks + QuickBooks WebConnector are you running? I'm running into issues getting XML parsed.

MattMorgis commented 7 years ago

@brandonros Hey! I have actually been fighting that all week. I find when I run it in different environments I get different results - for instance running locally on my Mac is fine, running on an Ubuntu server I get the invalid XML error.

I am using Quickbooks Desktop Enterprise Edition v15 and Web Connector v2.1.

I found the error is with the xmlBuilder module and have since worked around that issue by moving to another XML module. I saw your comment on the other thread as well - I actually forked this and made a significant amount of changes, including some improved documentation about how to get the SOAP server up and running, removing the hard-coded values in this example, etc. I am AFK this afternoon, but this evening I'll push everything up to GitHub and we can move this conversations over to the fork - I'm confident I can help ya work through your issues as I spent the last month successfully building on this and integrating it into a production web app.

brandonros commented 7 years ago

@mattmorgis I am doing the same. what app?

MattMorgis commented 7 years ago

Just an e-commerce website for a small business here in Philadelphia. It's an express.js app I built for them.

I am using qbXML v8.0 and only using the CustomerAddRq, InvoiceAddRq, and ItemQueryRq commands so far.

brandonros commented 7 years ago

@mattmorgis mine is ecommerce too. i really need to solve the invalid xml thing asap. any insight you can share is beyond helpful

johnballantyne commented 7 years ago

Sorry for ignoring this thread, I've been swamped at work.

@MattMorgis the changes you suggest sound fantastic! The initial goal of the project was to port Intuit's example service from C# to JavaScript, then eventually transform that into more of a utility. Better npm compatibility was what I had in mind for the "version 0.3 milestone". I'll start reviewing your fork to get up to speed on where you're at with this.