Skip to content

Commit 65d2a13

Browse files
committed
initial checkin after exporting from svn
0 parents  commit 65d2a13

9 files changed

+413
-0
lines changed

MIT-LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2006 Zack Chandler <zackchandler@depixelate.com>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
### Introduction ###
3+
4+
This plugin provides a webservice controller as a starting point for learning and implementing Rails/QuickBooks integration via the [QuickBooks Web Connector](http://marketplace.intuit.com/webconnector) (QBWC).
5+
6+
If you are just learning about QuickBooks integration you need to read and understand the [QBWC Programmers Guide](http://developer.intuit.com/qbSDK-current/doc/html/wwhelp/wwhimpl/js/html/wwhelp.htm).
7+
8+
### What this plugin provides ###
9+
10+
On installation this plugin creates the following files:
11+
12+
* app/controllers/qbwc_controller.rb
13+
* app/apis/qbwc_api.rb
14+
* app/helpers/qbwc_helper.rb
15+
* test/functional/qbwc_controller_test.rb
16+
* config/qbwc.qwc
17+
18+
The QbwcController includes a very simple example of querying QuickBooks and displaying a customer list.
19+
20+
To implement your own QuickBooks integration, implement the functions outlined in the QbwcController according to your integration needs.
21+
22+
### Requirements ###
23+
24+
1) Install actionwebservice gem (if it's not already installed)
25+
26+
sudo gem install actionwebservice
27+
28+
2) Add a route for the qbwc controller to config/routes.rb
29+
30+
# example Quickbooks Web Connector api route
31+
map.quickbooks_api 'apis/quickbooks/api', :controller => 'qbwc', :action => 'api'
32+
33+
3) The QBWC will only communicate over SSL so make sure your app has a valid and trusted SSL cert installed. For testing the QBWC will speak to localhost over http.
34+
35+
### Installation ###
36+
37+
$ ./script/plugin install http://svn.depixelate.com/plugins/quickbooks_integration
38+
39+
Upon installation, the template files will be copied over to your app.
40+
41+
If you want to copy over the template files again, use the following rake task:
42+
43+
$ rake quickbooks_integration:setup
44+
45+
Note: The rake task will not overwrite any existing files.
46+
47+
### Development Strategies ###
48+
49+
I develop on a Mac and run QuickBooks via [Parallels Desktop for Mac](http://www.parallels.com/en/products/desktop). To setup an development environment using a Mac + Parallels do the following:
50+
51+
1. Open Windows instance in Parallels
52+
2. Install QuickBooks
53+
3. Install the [QBWC](http://marketplace.intuit.com/webconnector)
54+
4. Modify hosts file (c:\WINDOWS\system32\drivers\etc\hosts) to point localhost1 to Mac's IP:
55+
56+
# replace IP address below with your Mac's IP address
57+
192.168.0.3 localhost1
58+
59+
5. Open a QuickBooks company file (you can use one of the QuickBooks example company files)
60+
6. Copy qbwc.qwc file to Windows instance and double-click to install apps configuration in the QBWC
61+
7. Start up Rails app with ./script/server
62+
8. Tail your log/development.log file to see communication cycle
63+
9. Check box on QBWC next to app's listing and click 'Update selected' button
64+
10. Watch qbxml messages sent back and forth...
65+
66+
### Help ###
67+
68+
Getting up to speed on QuickBooks can be challenging. I would highly recommend posting questions to the excellent [IDN forums](http://idnforums.intuit.com) if you get stuck.
69+
70+
### References ###
71+
72+
[QBWC Programmers Guide](http://developer.intuit.com/qbSDK-current/doc/html/wwhelp/wwhimpl/js/html/wwhelp.htm)
73+
[QBXML Messages](http://developer.intuit.com/qbSDK-current/OSR/OnscreenRef/index-QBD.html)
74+
[QuickBooks SDK Manuals](http://developer.intuit.com/QuickBooksSDK/chart.asp?id=94)
75+
[IDN Forums](http://idnforums.intuit.com)
76+
[QuickBooks Web Connector](http://marketplace.intuit.com/webconnector)

install.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'fileutils'
2+
3+
def safe_copy(src, dest)
4+
if File.exists? dest
5+
puts "#{dest} exists... not overwriting..."
6+
else
7+
FileUtils.cp src, dest, :verbose => true
8+
end
9+
end
10+
11+
plugin_root = File.dirname(__FILE__)
12+
app_root = File.join(plugin_root, '../../../')
13+
14+
FileUtils.mkdir_p File.join(app_root, 'app/apis'), :verbose => true
15+
safe_copy File.join(plugin_root, 'templates/example_qbwc_controller.rb'), File.join(app_root, 'app/controllers/qbwc_controller.rb')
16+
safe_copy File.join(plugin_root, 'templates/example_qbwc_helper.rb'), File.join(app_root, 'app/helpers/qbwc_helper.rb')
17+
safe_copy File.join(plugin_root, 'templates/example_qbwc_api.rb'), File.join(app_root, 'app/apis/qbwc_api.rb')
18+
safe_copy File.join(plugin_root, 'templates/example_qbwc_controller_test.rb'), File.join(app_root, 'test/functional/qbwc_controller_test.rb')
19+
safe_copy File.join(plugin_root, 'templates/example_qbwc.qwc'), File.join(app_root, 'config/qbwc.qwc')
20+
21+
puts IO.read(File.join(File.dirname(__FILE__), 'README'))

tasks/quickbooks_integration.rake

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace :quickbooks_integration do
2+
3+
desc 'Install template files for QuickBooks integration'
4+
task :setup do
5+
`ruby #{File.join(File.dirname(__FILE__), '../install.rb')}`
6+
end
7+
8+
end

templates/example_qbwc.qwc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<QBWCXML>
2+
<AppName>QuickBooks Plugin Example</AppName>
3+
<AppID></AppID>
4+
<AppURL>http://localhost1:3000/apis/quickbooks/api</AppURL>
5+
<AppDescription>QuickBooks Plugin Example</AppDescription>
6+
<AppSupport>http://idnforums.intuit.com/</AppSupport>
7+
<UserName>CHANGME</UserName>
8+
<OwnerID></OwnerID>
9+
<FileID></FileID>
10+
<QBType>QBFS</QBType>
11+
<Style>RPC</Style>
12+
</QBWCXML>

templates/example_qbwc_api.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
class QbwcApi < ActionWebService::API::Base
2+
inflect_names false
3+
4+
# --- [ QBWC version control ] ---
5+
# Expects:
6+
# * string strVersion = QBWC version number
7+
# Returns string:
8+
# * NULL or <emptyString> = QBWC will let the web service update
9+
# * "E:<any text>" = popup ERROR dialog with <any text>, abort update and force download of new QBWC.
10+
# * "W:<any text>" = popup WARNING dialog with <any text>, and give user choice to update or not.
11+
api_method :clientVersion,
12+
:expects => [{:strVersion => :string}],
13+
:returns => [[:string]]
14+
15+
# --- [ Authenticate web connector ] ---
16+
# Expects:
17+
# * string strUserName = username from QWC file
18+
# * string strPassword = password
19+
# Returns string[2]:
20+
# * string[0] = ticket (guid)
21+
# * string[1] =
22+
# - empty string = use current company file
23+
# - "none" = no further request/no further action required
24+
# - "nvu" = not valid user
25+
# - any other string value = use this company file
26+
api_method :authenticate,
27+
:expects => [{:strUserName => :string}, {:strPassword => :string}],
28+
:returns => [[:string]]
29+
30+
# --- [ To facilitate capturing of QuickBooks error and notifying it to web services ] ---
31+
# Expects:
32+
# * string ticket = A GUID based ticket string to maintain identity of QBWebConnector
33+
# * string hresult = An HRESULT value thrown by QuickBooks when trying to make connection
34+
# * string message = An error message corresponding to the HRESULT
35+
# Returns string:
36+
# * "done" = no further action required from QBWebConnector
37+
# * any other string value = use this name for company file
38+
api_method :connectionError,
39+
:expects => [{:ticket => :string}, {:hresult => :string}, {:message => :string}],
40+
:returns => [:string]
41+
42+
# --- [ Facilitates web service to send request XML to QuickBooks via QBWC ] ---
43+
# Expects:
44+
# * int qbXMLMajorVers
45+
# * int qbXMLMinorVers
46+
# * string ticket
47+
# * string strHCPResponse
48+
# * string strCompanyFileName
49+
# * string Country
50+
# * int qbXMLMajorVers
51+
# * int qbXMLMinorVers
52+
# Returns string:
53+
# * "any_string" = Request XML for QBWebConnector to process
54+
# * "" = No more request XML
55+
api_method :sendRequestXML,
56+
:expects => [{:ticket => :string}, {:strHCPResponse => :string},
57+
{:strCompanyFileName => :string}, {:Country => :string},
58+
{:qbXMLMajorVers => :int}, {:qbXMLMinorVers => :int}],
59+
:returns => [:string]
60+
61+
# --- [ Facilitates web service to receive response XML from QuickBooks via QBWC ] ---
62+
# Expects:
63+
# * string ticket
64+
# * string response
65+
# * string hresult
66+
# * string message
67+
# Returns int:
68+
# * Greater than zero = There are more request to send
69+
# * 100 = Done. no more request to send
70+
# * Less than zero = Custom Error codes
71+
api_method :receiveResponseXML,
72+
:expects => [{:ticket => :string}, {:response => :string},
73+
{:hresult => :string}, {:message => :string}],
74+
:returns => [:int]
75+
76+
# --- [ Facilitates QBWC to receive last web service error ] ---
77+
# Expects:
78+
# * string ticket
79+
# Returns string:
80+
# * error message describing last web service error
81+
api_method :getLastError,
82+
:expects => [{:ticket => :string}],
83+
:returns => [:string]
84+
85+
# --- [ QBWC will call this method at the end of a successful update session ] ---
86+
# Expects:
87+
# * string ticket
88+
# Returns string:
89+
# * closeConnection result. Ex: "OK"
90+
api_method :closeConnection,
91+
:expects => [{:ticket => :string}],
92+
:returns => [:string]
93+
94+
end

templates/example_qbwc_controller.rb

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# This controller implements the seven web callback methods for QBWC
2+
# Check qbwc_api.rb file for descriptions of parameters and return values
3+
class QbwcController < ApplicationController
4+
before_filter :set_soap_header
5+
6+
# --- [ QBWC version control ] ---
7+
# Expects:
8+
# * string strVersion = QBWC version number
9+
# Returns string:
10+
# * NULL or <emptyString> = QBWC will let the web service update
11+
# * "E:<any text>" = popup ERROR dialog with <any text>, abort update and force download of new QBWC.
12+
# * "W:<any text>" = popup WARNING dialog with <any text>, and give user choice to update or not.
13+
def clientVersion(version)
14+
nil # support any version
15+
end
16+
17+
# --- [ Authenticate web connector ] ---
18+
# Expects:
19+
# * string strUserName = username from QWC file
20+
# * string strPassword = password
21+
# Returns string[2]:
22+
# * string[0] = ticket (guid)
23+
# * string[1] =
24+
# - empty string = use current company file
25+
# - "none" = no further request/no further action required
26+
# - "nvu" = not valid user
27+
# - any other string value = use this company file
28+
def authenticate(username, password)
29+
[ 'ABC123', '' ]
30+
end
31+
32+
# --- [ To facilitate capturing of QuickBooks error and notifying it to web services ] ---
33+
# Expects:
34+
# * string ticket = A GUID based ticket string to maintain identity of QBWebConnector
35+
# * string hresult = An HRESULT value thrown by QuickBooks when trying to make connection
36+
# * string message = An error message corresponding to the HRESULT
37+
# Returns string:
38+
# * "done" = no further action required from QBWebConnector
39+
# * any other string value = use this name for company file
40+
def connectionError(ticket, hresult, message)
41+
'done'
42+
end
43+
44+
# --- [ Facilitates web service to send request XML to QuickBooks via QBWC ] ---
45+
# Expects:
46+
# * int qbXMLMajorVers
47+
# * int qbXMLMinorVers
48+
# * string ticket
49+
# * string strHCPResponse
50+
# * string strCompanyFileName
51+
# * string Country
52+
# * int qbXMLMajorVers
53+
# * int qbXMLMinorVers
54+
# Returns string:
55+
# * "any_string" = Request XML for QBWebConnector to process
56+
# * "" = No more request XML
57+
def sendRequestXML(ticket, hpc_response, company_file_name, country, qbxml_major_version, qbxml_minor_version)
58+
# Sample qbxml request to query customers and only return 'Name' attribute
59+
xml = <<-REQUEST
60+
<CustomerQueryRq requestID="1">
61+
<IncludeRetElement>Name</IncludeRetElement>
62+
</CustomerQueryRq>
63+
REQUEST
64+
wrap_qbxml_request(xml)
65+
end
66+
67+
# --- [ Facilitates web service to receive response XML from QuickBooks via QBWC ] ---
68+
# Expects:
69+
# * string ticket
70+
# * string response
71+
# * string hresult
72+
# * string message
73+
# Returns int:
74+
# * Greater than zero = There are more request to send
75+
# * 100 = Done. no more request to send
76+
# * Less than zero = Custom Error codes
77+
def receiveResponseXML(ticket, response, hresult, message)
78+
# Simply prints QuickBooks customers to log file
79+
xml = XmlSimple.xml_in(response, { 'ForceArray' => false })
80+
logger.info "QuickBooks has the following customers:"
81+
xml['QBXMLMsgsRs']['CustomerQueryRs']['CustomerRet'].each do |customer|
82+
logger.info customer['Name']
83+
end
84+
100 # Signal done - no more requests are needed.
85+
end
86+
87+
# --- [ Facilitates QBWC to receive last web service error ] ---
88+
# Expects:
89+
# * string ticket
90+
# Returns string:
91+
# * error message describing last web service error
92+
def getLastError(ticket)
93+
'An error occurred'
94+
end
95+
96+
# --- [ QBWC will call this method at the end of a successful update session ] ---
97+
# Expects:
98+
# * string ticket
99+
# Returns string:
100+
# * closeConnection result. Ex: "OK"
101+
def closeConnection(ticket)
102+
'OK'
103+
end
104+
105+
private
106+
107+
# The W3C SOAP docs state (http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528):
108+
# "The SOAPAction HTTP request header field can be used to indicate the intent of
109+
# the SOAP HTTP request. The value is a URI identifying the intent. SOAP places
110+
# no restrictions on the format or specificity of the URI or that it is resolvable.
111+
# An HTTP client MUST use this header field when issuing a SOAP HTTP Request."
112+
# Unfortunately the QBWC does not set this header and ActionWebService needs
113+
# HTTP_SOAPACTION set correctly in order to route the incoming SOAP request.
114+
# So we set the header in this before filter.
115+
def set_soap_header
116+
if request.env['HTTP_SOAPACTION'].blank? || request.env['HTTP_SOAPACTION'] == %Q("")
117+
xml = REXML::Document.new(request.raw_post)
118+
element = REXML::XPath.first(xml, '/soap:Envelope/soap:Body/*')
119+
request.env['HTTP_SOAPACTION'] = element.name if element
120+
end
121+
end
122+
123+
# Simple wrapping helper
124+
def wrap_qbxml_request(body)
125+
r_start = <<-XML
126+
<?xml version="1.0" ?>
127+
<?qbxml version="5.0" ?>
128+
<QBXML>
129+
<QBXMLMsgsRq onError="continueOnError">
130+
XML
131+
r_end = <<-XML
132+
</QBXMLMsgsRq>
133+
</QBXML>
134+
XML
135+
r_start + body + r_end
136+
end
137+
138+
end

0 commit comments

Comments
 (0)