Rails and QuickBooks integration - Part 4
Make sure you read part 1, part 2, and part 3 of this series if you haven't already.
The Full Monty
In part 3 we tested the simple case of having no pending requests for the QuickBooks Web Connector (QBWC) which results in the exercise of only authenticate() and closeConnection(). But I know you were craving more...
To test the full send/receive stack we need to return a qbXML request from sendRequestXML(). QBWC will forward this qbXML request to QuickBooks and then return the result by calling our receiveResponseXML(). Let's put together a sample request that returns all customer names.
quickbooks_controller.rb
class QuickbooksController < ApplicationController
ssl_required :api, :qwc
before_filter :set_soap_header, :except => :qwc
...
def authenticate(username, password)
['85B41BEE-5CD9-427a-A61B-83964F1EB426', '']
end
...
def sendRequestXML(ticket, hpc_response, company_file_name, country, qbxml_major_version, qbxml_minor_version)
qbxml = <<-REQUEST
<?xml version="1.0"?>
<?qbxml version="4.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
<CustomerQueryRq requestID="1">
<IncludeRetElement>FullName</IncludeRetElement>
</CustomerQueryRq>
</QBXMLMsgsRq>
</QBXML>
REQUEST
clean_qbxml(qbxml)
end
def receiveResponseXML(ticket, response, hresult, message)
xml = REXML::Document.new(response)
statusCode = REXML::XPath.first(xml, '//CustomerQueryRs[1]' ).attributes['statusCode']
if statusCode == 0.to_s
REXML::XPath.each(xml, '//FullName' ){ |e| logger.info "Customer: #{e.text}" }
else
logger.info "Unable to receive the customer list...status code: #{statusCode}"
end
100
end
...
def closeConnection(ticket)
'OK'
end
...
private
def set_soap_header
if request.env['HTTP_SOAPACTION'].blank? || request.env['HTTP_SOAPACTION'] == %Q("")
xml = REXML::Document.new(request.raw_post)
element = REXML::XPath.first(xml, '/soap:Envelope/soap:Body/*')
request.env['HTTP_SOAPACTION'] = element.name if element
end
end
def clean_qbxml(qbxml)
qbxml.gsub(/>\n\s+</, '><').strip
end
end
First, authenticate returns an empty string. This instructs QBWC to use the current company file. You can check the current company file name by checking the strHCPResponse xml passed to the sendRequestXML() call.
Secondly, we issue a simple qbXML request to QBWC. I like to use here documents (<<- notation) to keep xml readable. But QBWC will barf on the newlines and whitespace so we have to throw in a quick helper (clean_qbxml) to do our dirty work.
Finally, our receiveResponseXML() simply logs the customer names and returns 100 which signals that we have no future pending requests. QWBC then calls closeConnection() and exits.
Next steps
This has been a wild ride but now we're home free. The basics of the QBWC communication cycle are understood. QBWC 1.5 should come out soon to improve on the rough edges. Now all that remains is to sit down and determine exactly how your app should sync with QuickBooks. That's what I'm doing now. If I discover any helpful tidbits as I work through my integration I'll be sure to post another article. Otherwise I hope these posts were helpful and I wish you luck in your integration quest!


2 Comments
I like your pace of writing these articles :-)
Did you consider using rxml templates for the qbxml? That is what I was planning to do. Keeping the files in view/quickbooks, one per message request template, something like customer_list.rxml for the example you used.
I have noticed from dealing with qbxml before, that many of the qbxml messages are combinations of smaller qbxml messages. I was hoping that by creating qbrxml templates as I need them I would be able to take advantage of render :partial sometimes.
I'm curious, how'd you implement testing for this, if you did?
Michahel,
Rendering rxml files may work fine although I haven't tried it. The qbXML messages that my app will pass back are very small (just a few lines) so I am planning on simply making them constants in my app. To me this is the simplest way.
As far as testing I'm doing functional testing using invoke (see AWDR chapter on web services) and may even use integration testing depending on how complete the coverage is I get from the functional side. Maybe this will be the topic of another post.
Commenting is closed for this article.