UNPKG

qbws

Version:

a web server for QuickBooks Web Connector integration

594 lines (510 loc) 18.2 kB
var http = require('http'); var soap = require('soap'); var chalk = require('chalk'); var fs = require('fs'); var builder = require('xmlbuilder'); var uuid = require('node-uuid'); var qbws, server, counter = null, connectionErrCounter = null, username = 'username', password = 'password', // Change companyFile to an empty string to use the company file // currently open in Quickbooks companyFile = 'C:\\Users\\Public\\Documents\\Intuit\\QuickBooks\\Sample Company Files\\QuickBooks 2014\\sample_wholesale-distribution business.qbw', req = []; /** * Returns an array of sample QBXML requests * @returns {String|Array} QBXML requests: CustomerQuery, InvoiceQuery and BillQuery */ function buildRequest() { // TODO: is 'pretty' false by default? Probably. var strRequestXML, inputXMLDoc, request = []; // CustomerQuery inputXMLDoc = builder.create('QBXML', { version: '1.0' }) .instruction('qbxml', 'version="4.0"') .ele('QBXMLMsgsRq', { 'onError': 'stopOnError' }) .ele('CustomerQueryRq', { 'requestID': '1' }) .ele('MaxReturned') .text('1'); strRequestXML = inputXMLDoc.end({ 'pretty': false }); request.push(strRequestXML); // clean up strRequestXML = ''; inputXMLDoc = null; // InvoiceQuery inputXMLDoc = builder.create('QBXML', { version: '1.0' }) .instruction('qbxml', 'version="4.0"') .ele('QBXMLMsgsRq', { 'onError': 'stopOnError' }) .ele('InvoiceQueryRq', { 'requestID': '2' }) .ele('MaxReturned') .text('1'); strRequestXML = inputXMLDoc.end({ 'pretty': false }); request.push(strRequestXML); // clean up strRequestXML = ''; inputXMLDoc = null; // BillQuery inputXMLDoc = builder.create('QBXML', { version: '1.0' }) .instruction('qbxml', 'version="4.0"') .ele('QBXMLMsgsRq', { 'onError': 'stopOnError' }) .ele('BillQueryRq', { 'requestID': '3' }) .ele('MaxReturned') .text('1'); strRequestXML = inputXMLDoc.end({ 'pretty': false }); request.push(strRequestXML); return request; } /** * Parses the first two version components out of the standard four component * version number: `<Major>.<Minor>.<Release>.<Build>` * @example * // returns 2.0 * parseForVersion("2.0.1.30"); * @param input {String} A version number * @returns {String} First two version components (i.e. &lt;Major>.&lt;Minor>) * or the original input parameter if it does not match the regular expression */ function parseForVersion(input) { // As long as you get the version in right format, you could use // any algorithm here. var major = '', minor = '', version = /^(\d+)\.(\d+)(\.\w+){0,2}$/, versionMatch; versionMatch = version.exec(input.toString()); if (versionMatch !== null) { major = versionMatch[1]; minor = versionMatch[2]; return major + '.' + minor; } else { return input; } } /** * Writes a string to the console and log file * @param input {String} String to be logged */ function serviceLog(data) { // TODO: Put the log file somewhere else var consoleLogging = true; if (consoleLogging) { console.log(data); } fs.appendFile('log.log', chalk.stripColor(data) + '\n', function callback(err) { if (err) { console.log(err); } }); } /** * Logs qbws method calls and their parameters * @param name {String} The name of the method * @param params {Object} The parameters sent with the method's function call */ function announceMethod(name, params) { var arg, argType; serviceLog(chalk.green('WebMethod: ' + name + '() has been called by QBWebConnector')); // TODO: dedicated method for Object...length if (Object.getOwnPropertyNames(params).length) { serviceLog(' Parameters received:'); for (arg in params) { if (params.hasOwnProperty(arg)) { // TODO: Truncate long value argType = typeof params[arg]; // TODO: DRY this up if (argType === 'object') { serviceLog(' ' + argType + ' ' + arg + ' = ' + JSON.stringify(params[arg], null, 2)); } else { serviceLog(' ' + argType + ' ' + arg + ' = ' + params[arg]); } } } } else { serviceLog(' No parameters received.'); } } qbws = { QBWebConnectorSvc: { QBWebConnectorSvcSoap: {} } }; /** * To enable web service with its version number returned back to QBWC * @returns {String} qbws's version number */ qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.serverVersion = function (args) { var retVal = '0.2.1'; announceMethod('serverVersion', args); serviceLog(' No parameters required.'); serviceLog(' Returned: ' + retVal); return { serverVersionResult: { string: retVal } }; }; /** * @function clientVersion * @summary An optional callback that allows the web service to evaluate the * current web connector version and react to it. * * @description An optional callback that allows the web service to evaluate * the current web connector version and react to it. Not currently required to * support backward compatibility but strongly recommended. * * Supply one of the following return strings: * * - "NULL" or "" (empty string) if you want the Web * Connector to proceed with the update * - "W:<any text>" if you want the web Connector to display * a WARNING dialog prompting the user to continue with the * update or cancel it. The text string after the "W:" will * be displayed in the warning dialog. * - "E:<any text>" if you want to cancel the update and * display an ERROR dialog. The text string after "E:" will * be displayed in the error dialog. The user will have to * download a new version of the Web Connector to continue * with the update. * - "O:<version number>" to tell the user that the server * expects a newer version of QBWC than the user currently * has but also tells the user which version is needed * @param strVersion {String} The version of the QB web connector supplied in the * web connector's call to clientVersion * @returns {String} A string telling the Web Connector what to do next. * */ qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.clientVersion = function (args) { var strVersion = args.strVersion, recommendedVersion = '2.0.1.30', supportedMinVersion = '1.0', suppliedVersion, retVal = ''; suppliedVersion = parseForVersion(strVersion); announceMethod('clientVersion', args); serviceLog(' QBWebConnector Version = ' + strVersion); serviceLog(' Recommended Version = ' + recommendedVersion); serviceLog(' Supported Minimum Version = ' + supportedMinVersion); serviceLog(' Supplied Version = ' + suppliedVersion); if (strVersion < recommendedVersion) { retVal = 'W:We recommend that you upgrade your QBWebConnector'; } else if (strVersion < supportedMinVersion) { retVal = 'E:You need to upgrade your QBWebConnector'; } serviceLog(' Return values:'); serviceLog(' string retVal = ' + retVal); return { clientVersionResult: { string: retVal } }; }; // WebMethod - authenticate() // To verify username and password for the web connector that is trying to connect // // IN: // string strUserName // string strPassword // // OUT: // string[] authReturn // Possible values: // string[0] = ticket // string[1] // - empty string = use current company file // - "none" = no further request/no further action required // - "nvu" = not valid user // - any other string value = use this company file 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) { req = buildRequest(); 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]] } }; }; // WebMethod - sendRequestXML() // Description: This web method facilitates web service to send request XML to QuickBooks via QBWebConnector // // IN: // int qbXMLMajorVers // int qbXMLMinorVers // string ticket // string strHCPResponse // string strCompanyFileName // string Country // int qbXMLMajorVers // int qbXMLMinorVers // // OUT: // string request // Possible values: // - any_string = Request XML for QBWebConnector to process // - "" = No more request XML qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.sendRequestXML = function (args) { var total, request = ''; if (counter === null) { counter = 0; } announceMethod('sendRequestXML', args); total = req.length; if (counter < total) { request = req[counter]; serviceLog(' Sending request no = ' + (counter + 1)); counter = counter + 1; } else { counter = 0; request = ''; } return { sendRequestXMLResult: { string: request } }; }; // WebMethod - closeConnection() // At the end of a successful update session, QBWebConnector will call this web method. // // IN: // string ticket // // OUT: // string closeConnection result qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.closeConnection = function (args) { var retVal = null; announceMethod('closeConnection', args); retVal = 'OK'; serviceLog(' Return values:'); serviceLog(' string retVal = ' + retVal); return { closeConnectionResult: { string: retVal } }; }; // WebMethod - connectionError() // To facilitate capturing of QuickBooks error and notifying it to web services // Description: This web method facilitates web service to handle connection error between QuickBooks and QBWebConnector // // IN: // string ticket = A GUID based ticket string to maintain identity of QBWebConnector // string hresult = An HRESULT value thrown by QuickBooks when trying to make connection // string message = An error message corresponding to the HRESULT // // OUT: // string retVal // Possible values: // - done = no further action required from QBWebConnector // - any other string value = use this name for company file qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.connectionError = function (args) { var hresult = args.hresult, message = args.message, retVal = null, // 0x80040400 - QuickBooks found an error when parsing the // provided XML text stream. QB_ERROR_WHEN_PARSING = '0x80040400', // 0x80040401 - Could not access QuickBooks. QB_COULDNT_ACCESS_QB = '0x80040401', // 0x80040402 - Unexpected error. Check the qbsdklog.txt file for // possible additional information. QB_UNEXPECTED_ERROR = '0x80040402'; // Add more as you need... if (connectionErrCounter === null) { connectionErrCounter = 0; } announceMethod('connectionError', args); // TODO: Why is the same code repeated thrice? Switch statement instead? if (hresult.trim() === QB_ERROR_WHEN_PARSING) { serviceLog(' HRESULT = ' + hresult); serviceLog(' Message = ' + message); retVal = 'DONE'; } else if (hresult.trim() === QB_COULDNT_ACCESS_QB) { serviceLog(' HRESULT = ' + hresult); serviceLog(' Message = ' + message); retVal = 'DONE'; } else if (hresult.trim() === QB_UNEXPECTED_ERROR) { serviceLog(' HRESULT = ' + hresult); serviceLog(' Message = ' + message); retVal = 'DONE'; } else { // Depending on various hresults return different value if (connectionErrCounter === 0) { // Try again with this company file serviceLog(' HRESULT = ' + hresult); serviceLog(' Message = ' + message); serviceLog(' Sending empty company file to try again.'); retVal = ''; } else { serviceLog(' HRESULT = ' + hresult); serviceLog(' Message = ' + message); serviceLog(' Sending DONE to stop.'); retVal = 'DONE'; } } serviceLog(' Return values:'); serviceLog(' string retVal = ' + retVal); connectionErrCounter = connectionErrCounter + 1; return { connectionErrorResult: { string: retVal } }; }; // WebMethod - getInteractiveURL() // // IN: // string wcTicket // string sessionID // // OUT: // URL string // Possible values: // URL to a website qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.getInteractiveURL = function (args) { var retVal = ''; announceMethod('getInteractiveURL', args); return { getInteractiveURLResult: { string: retVal } }; }; // WebMethod - getLastError() // // IN: // string ticket // // OUT: // string retVal // Possible Values: // Error message describing last web service error qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.getLastError = function (args) { var errorCode = 0, retVal = ''; announceMethod('getLastError', args); if (errorCode === -101) { // This is just an example of custom user errors retVal = 'QuickBooks was not running!'; } else { retVal = 'Error!'; } serviceLog(' Return values:'); serviceLog(' string retVal = ' + retVal); return { getLastErrorResult: { string: retVal } }; }; // WebMethod - interactiveDone() // // IN: // string wcTicket // // OUT: // string qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.interactiveDone = function (args) { var retVal = ''; announceMethod('interactiveDone', args); return { interactiveDoneResult: { string: retVal } }; }; // WebMethod - interactiveRejected() // // IN: // string wcTicket // string reason // // OUT: // string qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.interactiveRejected = function (args) { var retVal = ''; announceMethod('interactiveRejected', args); return { interactiveRejectedResult: { string: retVal } }; }; // WebMethod - receiveResponseXML() // Description: This web method facilitates web service to receive response XML from QuickBooks via QBWebConnector // // IN: // string ticket // string response // string hresult // string message // // OUT: // int retVal // Greater than zero = There are more request to send // 100 = Done. no more request to send // Less than zero = Custom Error codes qbws.QBWebConnectorSvc.QBWebConnectorSvcSoap.receiveResponseXML = function (args) { var response = args.response, hresult = args.hresult, message = args.message, retVal = 0, percentage; announceMethod('receiveResponseXML', args); // TODO: Create method for Object testing and property names length if (typeof hresult === 'object' && Object.getOwnPropertyNames(hresult).length !== 0) { // If there is an error with response received, // web service could also return a -ve int serviceLog(' HRESULT = ' + hresult); serviceLog(' Message = ' + message); retVal = -101; } else { serviceLog(' Length of response received = ' + response.length); percentage = counter * 100 / req.length; if (percentage >= 100) { counter = 0; } // QVWC throws an error if if the return value contains a decimal retVal = percentage.toFixed(); } serviceLog(' Return values: '); serviceLog(' Number retVal = ' + retVal); return { receiveResponseXMLResult: { string: retVal } }; }; server = http.createServer(function requestListener(request,response) { response.end('404: Not Found: ' + request.url); }); module.exports.run = function runQBWS() { var soapServer, xml = fs.readFileSync(__dirname + '/qbws.wsdl', 'utf8'); server.listen(8000); soapServer = soap.listen(server, '/wsdl', qbws, xml); soapServer.log = function soapServerLog(type, data) { serviceLog(type + ': ' + data); }; };