Rails and QuickBooks integration - Part 4

December 14th, 2006 QuickbooksRails

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

  1. Comment by Micahel on 12/15/06

    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?

  2. Comment by Zack on 12/15/06

    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.