https://minimul.com Minimul: a QuickBooks integration expert minimul.com 2019-11-08T00:00:00Z https://minimul.com/migrating-a-quickbooks-online-app-from-oauth1-to-oauth2-using-the-qbo_api-gem-part-3.html 2019-11-08T00:00:00Z <p>Migrating a QuickBooks Online App from OAuth1 to OAuth2 using the qbo_api gem - Step 3</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/IT9HNLPUUxQ?wmode=transparent" frameborder="0"></iframe> </p> <h3>Show notes:</h3> <ul> <li>In short this tutorial is about creating OAuth2 access tokens given that you have valid OAuth1 tokens using the <a href="https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/migration" target="_blank">Migration API</a>.</li> <li>To see the <code>Qbo::OAuth2.migrate!</code> code see the <a href="/migrating-a-quickbooks-online-app-from-oauth1-to-oauth2-using-the-qbo_api-gem-part-2.html" target="_blank">Step 2 article</a>.</li> </ul> <h4>lib/tasks/quickbooks.rake</h4> <pre> namespace :quickbooks do task :oauth2_migrate => :environment do qbo_accounts = QboAccount.all qbo_accounts.each do |qbo_account| next unless qbo_account.id == 3 # Remove this line later of course begin Qbo::OAuth2.migrate!(qbo_account) puts "SUCCESS_OAUTH2_MIGRATION_FLAG: qbo_account_id: #{qbo_account.id}" rescue => e puts "FAILED_OAUTH2_MIGRATION_FLAG: qbo_account_id: #{qbo_account.id} error_message: #{e.message}" end end end end </pre> Christian Pelczarski https://minimul.com/migrating-a-quickbooks-online-app-from-oauth1-to-oauth2-using-the-qbo_api-gem-part-2.html 2019-11-06T00:00:00Z <p>Migrating a QuickBooks Online App from OAuth1 to OAuth2 using the qbo_api gem - Step 2</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/BzecFOHyv84?wmode=transparent" frameborder="0"></iframe> </p> <h3>Show notes:</h3> <ul> <li>Add gem 'rack-oauth2' to Gemfile</li> <li>Enable OAuth2 on your app at <a href="https://developer.intuit.com" target="_blank">https://developer.intuit.com</a> and set the redirect URI and grab the client_id and client_secret.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/180/help-dev-oauth2.png" /> <figcaption class="count">Initial OAuth2 Setup</figcaption> </figure> </ul> <h4>config/routes.rb</h4> <pre> get '/oauth2-redirect', to: 'visitors#oauth2_redirect' </pre> <h4>app/models/qbo/oauth2.rb</h4> <script src="https://gist.github.com/minimul/a2c270ae5c5008326aae16c03a3b810b.js"></script> <h3>app/controllers/visitors_controller.rb</h3> <pre> class VisitorsController < ApplicationController skip_before_action :authenticate_user!, :except => [:oauth2_redirect] def oauth2_redirect qbo_account = current_account.qbo_account if params[:state] == session[:oauth2_state] && qbo_account client = Qbo::OAuth2.client client.authorization_code = params[:code] if resp = client.access_token! attrs = { access_token: resp.access_token, refresh_token: resp.refresh_token, companyid: params[:realmId] }.merge(Qbo::OAuth2.expires_in) qbo_account.update!(attrs) msg = "Your QuickBooks account has been successfully linked." flash[:inner_notice] = msg render 'shared/close_and_redirect', layout: 'basic' end end rescue => e @url = account_url(current_account) msg = "There was a problem linking Your QuickBooks account - given an error: #{e.message}" logger.warn msg flash[:alert] = msg render 'shared/close_and_redirect', layout: 'basic' end end </pre> <h3>app/controllers/application_controller.rb</h3> <pre> def set_oauth2_state session[:oauth2_state] = Rails.env.test? ? '3242adf32423kjo' : SecureRandom.uuid end helper_method :set_oauth2_state </pre> <h4>config/initializers/inflectors.rb</h4> <pre> ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'OAuth2' end </pre> <h4>view code</h4> <pre> <% content_for :script do %> intuit.ipp.anywhere.setup({ grantUrl: '<%= Qbo::OAuth2.authorize_url(state: set_oauth2_state).html_safe %>', datasources: { quickbooks : true, payments : false } }); <% end %> </pre> Christian Pelczarski https://minimul.com/migrating-a-quickbooks-online-app-from-oauth1-to-oauth2-using-the-qbo_api-gem.html 2019-10-24T00:00:00Z <p>Migrating a QuickBooks Online App from OAuth1 to OAuth2 using the qbo_api gem - Step 1</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/mcFg4dqQH_M?wmode=transparent" frameborder="0"></iframe> </p> <h3>OAuth2 Renewal Strategy</h3> <p>The QuickBooks Online API OAuth2 access token's are only good for 1 hour. This is a radical departure from OAuth1's 6 month expiry duration. With that short a window <em>probably</em> the best way to ensure uninterrupted usage is to utilize a code pattern for all QBO request that does the following:</p> <ol> <li>Catches "Unauthorized" errors (status code 401 &mdash; i.e. an invalid access token).</li> <li>Next, automatically renews and persists the access token.</li> <li>Lastly, replays the failed 401 request.</li> </ol> <p>In the screencast, I'll be focusing on this approach. See the show notes below for an example code pattern that will make OAuth2 renewal and persistence automatic. Note: The code pattern below requires customization for your particular situation.</p> <h3>Scheduled Renewal Job?</h3> <p>With OAuth1's one month renewal window you could rely on a background scheduler like Cron but with the OAuth2's super short renewal duration, relying solely on Cron may not be best because in the case that your app or the QBO API has an outage when either service returns it may take considerably longer to get QBO requests flowing again. I talk more about going with a background scheduler strategy <a href="https://youtu.be/vhVZ5oq3npw" target="_blank">here</a>.</p> <h3>Show Notes:</h3> <ol> <li>Update the <code>qbo_api</code> gem to the latest.</li> <li>Refactor your QuickBooks Online API requests in to the "request_handler" pattern as seen below. The qbo_account, record, and payload ideas are specific to my app that I'm switching over to OAuth2. Please watch the screencast to get the proper context.</li> </ol> <pre> module Qbo class Base def self.request_handler(qbo_account_id:, payload: false, record: false) retries ||= 0 qbo_account = QboAccount.find(qbo_account_id) api = Qbo.init(qbo_account) yield(api) rescue QboApi::Unauthorized if qbo_account.access_token.present? Qbo::OAuth2.renew(qbo_account) retry if (retries += 1) < 3 else register_error(qbo_account: qbo_account, error: '401 Authentication Error', payload: payload) end rescue QboApi::Error => e if err = e.fault if record fix_duplicate_customer(qbo_account: qbo_account, record: record, error: err, payload: payload, qbo_api: api) else final_err = error_body(err) register_error(qbo_account: qbo_account, error: final_err, payload: payload) end end rescue => e final_err = e.to_s[0..255] qbo_account.account.qbo_errors.create(body: final_err, request_json: payload.to_json, account: qbo_account.account) raise e end def self.entity_handler(qbo_account_id:, entity:, record:, payload:) request_handler(qbo_account_id: qbo_account_id, record: record, payload: payload) do |api| if id = record.qbo_id res = api.update(entity, id: id, payload: payload) else res = api.create(entity, payload: payload) record.update(qbo_id: res['Id']) end res end end def self.fix_duplicate_customer(qbo_account:, record:, error:, payload:, qbo_api: api) if record.class.name == "Customer" && error[:error_body][0][:error_message] == 'Duplicate Name Exists Error' if res = qbo_api.get(:customer, ["DisplayName", record.display_name]) record.update(qbo_id: res['Id']) else register_error(qbo_account: qbo_account, error: error, payload: payload, resource: record) end else register_error(qbo_account: qbo_account, error: error, payload: payload, resource: record) end end def self.register_error(qbo_account:, error:, payload:, resource: false) qbo_account.account.qbo_errors.create(body: error[:error_body], method: error[:method], resource: resource, url: error[:url], status: error[:status], request_json: payload.to_json) end def self.error_body(err) if body = err.try(:[], :error_body) result = body.map{ |b| b.try(:[], :error_detail) || b.try(:[], :error_message) }.join(" -- ") else "" end end end end </pre> <pre> module Qbo def self.init(qbo_account) if qbo_account.access_token.present? attrs = { access_token: qbo_account.access_token, realm_id: qbo_account.companyid } else attrs = { token: qbo_account.token, token_secret: qbo_account.secret, realm_id: qbo_account.companyid, consumer_key: Rails.application.secrets.qbo_app_consumer_key, consumer_secret: Rails.application.secrets.qbo_app_consumer_secret } end QboApi.new(attrs) end end </pre> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-setup-qbwc-verbose-logging.html 2019-10-02T00:00:00Z <p>Lesson 9 - QuickBooks Desktop Integration Setup - QBWC Verbose Logging</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/nrTlRpM5E24?wmode=transparent" frameborder="0"></iframe> </p> <h3>Lesson Notes:</h3> <ul> <li>Protip: If you're dev environment is MacOS or Linux OS and you miss your command line tools when hacking around in Windows 10 you can <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10" target="_blank">install the Ubuntu subsystem</a>.</li> <li>Here is how to turn on <a href="https://help.developer.intuit.com/s/article/QBD-QBSDK-Logging" target="_blank">verbose logging</a>.</li> <li><a href="https://help.developer.intuit.com/s/article/Understanding-QBXML-Verbose-Logs" target="_blank">Understanding QBXML Verbose Logs</a>.</li> </ul> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-using-the-sdk-plus-3-tool.html 2019-09-28T00:00:00Z <p>Lesson 8 - QuickBooks Desktop Integration: Using the SDKTestPlus3 Tool</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/GcJzzhwHaDE?wmode=transparent" frameborder="0"></iframe> </p> <h3>Lesson Notes:</h3> <ul> <li>Here is the <a href="https://help.developer.intuit.com/s/article/SDKTestPlus3" target="_blank">link</a> for the SDKTestPlus3 Tool.</li> <ul> <li>It is great tool for quickly troubleshooting QBXML requests and responses as it gives a "close to metal" experience.</li> <li>I also use the tool from time to time for creating test fixtures.</li> </ul> <li>The tool is automatically installed during <a href="/install-quickbooks-desktop-sdk.html" target="_blank">QBSDK</a> installation.</li> <li>To find it easily type "sdk" in the Window search bar. The name of the SDK tool is "qbXML Test+".</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/177/find-sdk-plus.png" /> <figcaption class="count">Quickly find the SDKTestPlus3 Tool.</figcaption> </figure> </li> <li>To get started, load in a sample QBXML query that was provided when the QBSDK was installed.</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/177/sdk-plus-overview.png" /> <figcaption class="count">Sample QBXML files can be found in <code>C:\Program Files (x86)\Intuit\IDN\QBSDK13.0\samples\xmlfiles</code> directory.</figcaption> </figure> </li> <li>I'm going to use QBXML that adds an Invoice to QBD.</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/177/sdk-plus-use.png" /> <figcaption class="count">If the QBXML request was successful it will say so in the "Status" box.</figcaption> </figure> </li> <li>Here is what QBXML Response looks like:</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/177/sdk-tool-output.png" /> <figcaption class="count">When seleting the 'View Output' button ou get the QBXML response (when the request was successful).</figcaption> </figure> </li> <li>Make sure that when submitting transaction requests, like an add invoice request that you have "real" required references, like the CustomerRef in the request below (meaning the the customer being referenced is a real QBD customer).</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/177/qbxml-hacking.png" /> <figcaption class="count">You're going to be doing a lot of QBXML hacking so have a good editor &mdash; like Vim :)</figcaption> </figure> </li> <li>Using the SDKTestPlus3 Tool I made a real QBD invoice.</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/177/made-invoice.png" /> <figcaption class="count">Here is a look at the QBD Invoice I made from the above QBXML.</figcaption> </figure> </li> </ul> Christian Pelczarski https://minimul.com/install-quickbooks-desktop-sdk.html 2019-09-16T00:00:00Z <p>Lesson 7 - Install The QuickBooks Desktop SDK</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/nA1OFSFl2jg?wmode=transparent" frameborder="0"></iframe> </p> <h3>Lesson Notes:</h3> <ul> <li>Additional Software you'll need to install on the Windows guest VM for QuickBooks Desktop.</li> <ul> <li>Alternate browser like Firefox or Chrome if you don't dig Edge or IE.</li> <li>A good text editor for hacking XML.</li> </ul> <li>You may want to use Intuit's sample company files instead of creating your own because they come populated with name list (customers, vendors, items, etc) and transaction (invoices, sales receipts, etc) entities.</li> <li style="list-style-type: none;"> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/176/sample-file.png" /> <figcaption class="count">QBD comes with sample company files preloaded with data.</figcaption> </figure> </li> <li><a href="https://developer.intuit.com/app/developer/qbdesktop/docs/get-started/download-and-install-the-sdk" target="_blank">Download</a> and install the QBSDK.</li> <li>If your dev environment is OSX or Linux you are going to need to do some host file hacking.</li> <li style="list-style-type: none;"> <div class="alert yellow margin-top"> This topic is explained starting around the <a href="https://youtube.com/watch?v=nA1OFSFl2jg#t=2m01s" target="_blank">2:01 mark</a>. </div> </li> <ol> <li>Search for the Windows console (cmd.exe) app.</li> <li>Right-click the cmd.exe and select "Run as Adminstrator".</li> <li>Paste in this command: <code>notepad.exe C:\Windows\System32\drivers\etc\hosts</code></li> <li>Using <code>ifconfig</code> to get your OSX or Linux IP Address.</li> <li>Create an entry in the Windows host file like so: <code>192.168.1.45 localhost.test</code></li> <ul> <li>My local address is <code>192.168.1.45</code> and I use a static DHCP address so it doesn't change.</li> </ul> <li>Save and exit the host file &mdash; these local DNS changes will take place immediately on Windows.</li> </ol> <li style="list-style-type: none;"> <div class="alert green margin-top"> <div class="minimul-says"> Minimul says &mdash; </div> <p> The QuickBooks Web Connector can only establish a SOAP server connection to an "https" enabled SOAP server or if there is "localhost" somewhere in the hostname than it can just be a plain "http" connection so that is why I use <code>localhost.test</code>. </p> </div> </li> <li style="list-style-type: none;"> <div class="alert yellow margin-top"> This topic is explained starting around the <a href="https://youtube.com/watch?v=nA1OFSFl2jg#t=4m00s" target="_blank">4:01 mark</a>. </div> </li> <li>Lastly, make sure you bind your local (on OSX/Linux) SOAP web application server to <code>0.0.0.0</code>. For example, for Rails I run this command <code>bundle exec rail s -b 0.0.0.0</code> or you will not be able to communicate from the Windows VM to your local SOAP dev environment.</li> </ul> Christian Pelczarski https://minimul.com/install-quickbooks-desktop-via-virtualbox.html 2019-09-14T00:00:00Z <p>Lesson 6 - Install QuickBooks Desktop via VirtualBox</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/jDtUW9Nv6os?wmode=transparent" frameborder="0"></iframe> </p> <h3>Run QuickBooks Desktop No Matter Your OS</h3> <p>In the last lesson, I discussed obtaining a copy of the QuickBooks Desktop Not For Resale (NFR) version. In this lesson, I'll teach you how to install QuickBooks Desktop even if you don't use Windows for development. To demonstrate, I'll be use the free and excellent <a href="https://www.virtualbox.org/" target="_blank">VirtualBox</a> so I can run Windows and therefore QuickBooks within MacOS. In my particular case, I have two Windows OS VirtualBox instances but you'll only need one.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/175/vb-1.png" /> <figcaption class="count">My VirtualBox Windows Instances</figcaption> </figure></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=jDtUW9Nv6os#t=0m28s" target="_blank">0:28 mark</a>. </div> <p>I have a Windows 7 instance (with IE 10 and QuickBooks Desktop Pro) and I have a Windows 10 instance (with IE 11 and QBD 2018). For both of these instances I purchased real Windows CD keys.</p> <h3>Getting Affordable Windows CD Keys</h3> <p>There are legitimate sites that sell CD keys for OEM versions of Windows. I believe I got that latest key for like $12 and I did check around to see if the site was legit and it was. Sadly, I forgot which site is was and I couldn't find the email receipt. These sites buy OEM keys in volume so the prices are low. The catch with an OEM is that you can only install them one time, which is not a drawback in this scenario where you're only using Windows for testing within a virtual machine.</p> <h3>Conclusion</h3> <p>After getting the Windows OEM key, use one of the many online tutorials for installing Windows on VirtualBox. After installing Windows within VirtualBox you'll download and install QuickBooks Desktop via the NFR link(s) that Intuit provided for you. That's all for this lesson. In the next lesson we'll install the QuickBooks SDK.</p> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-install-quickbooks-desktop-not-for-resale-copy.html 2019-09-11T00:00:00Z <p>Lesson 5 - Install QuickBooks Desktop Not For Resale (NFR) Copy</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/7fdVcLEX0zI?wmode=transparent" frameborder="0"></iframe> </p> <h3>But I Don't Have QuickBooks Desktop?</h3> <p>Let's talk about actually getting a copy of QuickBooks Desktop (QBD) up and running. If you're on Windows, this is going to be a lot easier. I'm on a Mac so these upcoming lessons should work fine on Linux as well. If you're on Windows, skip ahead to the lessons that are going to be relevant for you. But stay here right now, because what I'm going to talk about is this NFR stuff in a general way.</p> <h3>What is the Not For Resale Version?</h3> <p>Because you're watching this you are now an official QuickBooks developer and you can get a NFR (Not For Resale) copy of QuickBooks Desktop (not that I remember having to provide a reason for requesting the QBD NFR but you might have to). A QBD NFR copy is simply a version that is intended for testing and trial purposes only.</p> <h3>Obtaining The NFR</h3> <p>First, you got to get an <a href="https://developer.intuit.com" target="_blank">Intuit developer account</a> if you don't have one. Then go to the <a href="https://developer.intuit.com/nfr" target="_blank">NFR page</a> and call them up. Back in 2013, I paid like $300 to get a NFR subscription but I'm pretty sure that it is free nowadays. <a href="https://quickbase.com" target="_blank">QuickBase</a>, the Intuit subsidiary, is the company that you'll actually be talking to.</p> <h3>What version?</h3> <p>When requesting a NRF they're going to ask you what version you want. There are many versions of QBD, such as of Point of Sale, UK/Canadian versions, Premiere, Enterprise, Pro, Manufacturing, etc. Moreover, each version has a "year'd version" such as QuickBooks 2016 Pro. So of the older year'd versions may be in use by your customers. But don't fret too much, The QuickBooks Web Connector (QBWC) is mature technology and will work seamlessly across them. Truth is that QuickBooks Desktop has not changed much recently and you don't have to run and test each year'd version. Of course, if your user base is primarily in the manufacturing sector then grab a copy of 2019 QBD Manufacturer. There'll probably be a couple of GUI differences here and there but GUI differences shouldn't effect your integration aims, which most likely will revolve around basic syncing activities of name lists and transactions. These standard requests and responses you'll be performing are going to work fine across all QBD versions and year'd versions. QBD Pro, Premiere, or Enterprise, generally speaking, is what most of your customers will be running but don't complicate things right now and just pick one of them for the year 2019.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=7fdVcLEX0zI#t=0m50s" target="_blank">0:50 mark</a>. </div> <h3>Conclusion</h3> <p>This lesson explains how you can get a NFR copy if you don't already have a copy of QBD. If you do have one, great, then you don't need to go through this step. The next lesson is going to involve installing the QBD NFR with VirtualBox so if you're a Windows user you can skip that lesson.</p> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-high-level-soap-service-actions.html 2019-09-07T00:00:00Z <p>Lesson 4: QuickBooks Desktop Integration - High Level SOAP Service Actions</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/8eXog46GWvE?wmode=transparent" frameborder="0"></iframe> </p> <h3>You Have To Build A SOAP Server</h3> <p>Let's jump right into this SOAP server you are going to be constructing and discuss it at a high level. Moreover, let's start to look at some code too. The code I'll be going to be going over for the rest of the lessons is based on my private gem called QBSDK. However, when I personally do an integration I do not use the gem directly but instead put the core gem code directly into the Rails app as a first-class citizen. The reason is that I want to be as "close to the metal" as possible on an integration project because flexibility is key for a successful integration. In my projects I only use a generic SOAP server library called <a href="https://github.com/inossidabile/wash_out" target="_blank">wash_out</a>. You also, depending on your tech stack, should pick out a mature SOAP server library. What you need to avoid is a "full stack" QBD integration library such as the <a href="https://github.com/qbwc/qbwc" target="_blank">QBWC gem</a> (in the Ruby ecosystem) because again, I feel it is vital to the success of your integration project if you (and the rest of the team) understand at a "low level" what is going on.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=8eXog46GWvE#t=0m10s" target="_blank">0:10 mark</a>. </div> <h3>The QBSDK Gem</h3> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/173/gem-actions.png" /> <figcaption class="count">Service Action Code from the QBSDK Gem.</figcaption> </figure></p> <h3>Your First SOAP Action</h3> <p>Let's take a look at the <a href="https://www.w3.org/TR/2001/NOTE-wsdl-20010315" target="_blank">WSDL</a>, which the washout gem automatically generates. Note: you also are going to need to generate all of these WSDL actions exactly I'll be specifying them. Remember that the QuickBooks Web Connector (QBWC) will ping your SOAP server (we'll refer to this as a "session") and the first thing it's going to ask for is a "serverVersion" (see Fig. 1). A QBWC session has a life cycle of SOAP Server actions, which will be discussed in this article.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/173/wdsl-actions.png" /> <figcaption class="count">The First SOAP Server Action - The Server Version.</figcaption> </figure></p> <p>You then need to send back a response for the serverVersion request. I just send back a "blank" response. Same with the "clientVersion" action, which is the next request/response step in the QBWC session. You then send back a client version response, which again, I just send back a blank response. I have all these responses in the code which are partial displayed in Figure 1. I'll be going into these actions in detail later, but let's continue on at a high level.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=8eXog46GWvE#t=2m00s" target="_blank">2:00 mark</a>. </div> <h3>The Core Actions</h3> <p>The first two actions are benign so let's move on to the core actions. With these actions we now have to do something meaningful. First, is the "authenticate" action. I'm not going to show you what to do exactly for authentication in this lesson so let's keep moving. Next, we have the "sendRequestXML" action. This can be a query, an add, or a modify request. There will be many more details on this action in later lessons.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/173/auth-action.png" /> <figcaption class="count">The Core Actions</figcaption> </figure></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=8eXog46GWvE#t=3m05s" target="_blank">3:05 mark</a>. </div> <h3>The Receive Response</h3> <p>The receiveResponseXML action and its response get a little interesting in that when you provide the receive response you must return a progress indicator that is an integer, 1 through 100. If your progress is anything less than 100 the QBWC session is sent back to the sendRequestXML action, creating a loop. If you send back 100 that means there is no more work to do and the QBWC session will close by being sent to the closeConnection action.</p> <p>So with a progress of less than 100 you just keep on looping until you give a progress of "100" or a "-1" is returned. Negative one means an error, which will send the QBWC session to the getLastError action. This looping effect means that you're going to be creating a queue and constructing that queue properly is vital and will be discussed in detail later.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/173/loop.png" /> <figcaption class="count">How A "Loop" Is Created.</figcaption> </figure></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=8eXog46GWvE#t=4m15s" target="_blank">4:15 mark</a>. </div> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/173/error.png" /> <figcaption class="count">Actions for Closing A QBWC Session</figcaption> </figure></p> <h3>Conclusion</h3> <p>That gives you a high level of what's going on in these SOAP actions during a QBWC's session life-cycle. In the next lessons, we'll be diving into the details of each core action.</p> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-high-level-overview-of-qbxml.html 2019-09-05T00:00:00Z <p>Lesson 3: QuickBooks Desktop Integration - High-level overview of QBXML</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/s6GSoGq_6G0?wmode=transparent" frameborder="0"></iframe> </p> <h3>QBXML and Its Bible</h3> <p>In the last two lessons I talked about creating a SOAP server that talks to the QBWC, which is a SOAP client. Next, we'll discuss by which means or protocol does this client and server speak to each other. This means is a simple text protocol called QBXML. The best way to learn about QXML is through its bible, the <a href="https://developer-static.intuit.com/qbsdk-current/common/newosr/index.html" target="_blank">QBSDK On-Screen Reference</a> or OCR. You're always going having this reference open so go ahead and open it up now and start clicking around. As of this writing, the latest QBSDK version is 13.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=s6GSoGq_6G0#t=0m10s" target="_blank">0:10 mark</a>. </div> <h3>Using the QBSDK OCR</h3> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/172/ocr.png" /> <figcaption class="count">Diagram demonstrating using the QBSDK OCR.</figcaption> </figure></p> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p> You'll notice the "QBFC" option on the OCR but that is beyond the scope of this class, I'll just be focusing the QuickBooks Web Connector which uses QBXML.</p> </div> <p>QBSDK is an umbrella term that is exclusive to QuickBooks Desktop integration and is not related to integrating with QuickBooks Online. I say umbrella term, because it is really a group of technologies you're going to have implement and QBXML is one of those pieces. As you can see from browsing around the OCR that support is mature and you're most likely going to be able to achieve your integration goals.</p> <p>I believe version 13 of the QBSDK came out in around 2013 so it's unlikely that any of your customers are not going to qualify for version 13, meaning they're definitely going to be using a QuickBooks Desktop version beyond 2013.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/172/ocr-verbose.png" /> <figcaption class="count">Use the XMLOps tab to see the raw QBXML.</figcaption> </figure></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=s6GSoGq_6G0#t=2m23s" target="_blank">2:23 mark</a>. </div> <h3>Conclusion</h3> <p>QBXML is at the heart of QBD integration and it is the means of communicating with the QuickBooks Web Connector. In the next lessons I'll be diving more in-depth around create and serving up QBXML via the SOAP server you're going to be building.</p> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-soap-server-strategy.html 2019-08-23T00:00:00Z <p>Lesson 2: QuickBooks Desktop Integration - SOAP Server Strategy</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/KL2n2Emif2k?wmode=transparent" frameborder="0"></iframe> </p> <h3>SOAP Server - Don't Use An Existing Library</h3> <p>Now that you know that you need to make a SOAP server on your end to talk to the QBWC, what kind of strategy should you go about making and constructing this SOAP server? I am going to go against conventional wisdom and recommend throughout the entire training series that you <b>do not</b> use an "off the shelf" QuickBooks desktop integration framework or library from your particular tech stack. I'm going to advocate that you bake this integration right in to your web application code. You're gonna want to talk to the database at a very intimate level. Moreover, you're going to have a lot of edge cases, error reporting and other things that make it very difficult for a generic library to handle. In fact, I have not even released this "library" that you're going to see through out this series and when I do an integration I pull it right into the application itself. So I practice what I preach.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=KL2n2Emif2k#t=0m22s" target="_blank">0:22 mark</a>. </div> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> </div> <h3>Really, Don't Use an Existing Library?</h3> <p> You're not going to be doing a QuickBooks desktop integration many times &mdash; you're going to do this one time and you're gonna need to do it right too. You want this to actually work and your customers to be happy. To do a solid QuickBooks desktop integration you are going need to be close to the metal. In short, you need to know what's technically going on. You are not going to be able to outsource that to a generic library. Chances are if you use an off-the-shelf library you'll end up re-writing most of the thing anyway.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=KL2n2Emif2k#t=1m14s" target="_blank">1:14 mark</a>. </div> <h3>Conclusion</h3> <p>The only library you going to want to start with is a generic SOAP server library. From there you are going to build each of the SOAP actions directly into your app. In the upcoming lessons you'll see how to do just that.</p> Christian Pelczarski https://minimul.com/high-level-overview-of-the-quickbooks-web-connector.html 2019-08-22T00:00:00Z <p>Lesson 1: High Level Overview Of The QuickBooks Web Connector (QBWC)</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/4pwV2sBy2cA?wmode=transparent" frameborder="0"></iframe> </p> <h3>What is the QuickBooks Web Connector?</h3> <p> Let's talk at a high level about what the QuickBooks Web Connector (or QBWC) is and how it provides integration to QuickBooks Desktop (QBD). Take a look at Figure 1 as well. First, know that QuickBooks Desktop only runs on Windows and it is sort of like a program in and of itself. It acts like a middleware agent that will talk to your web app server, which you must configure as a SOAP server. You can create an integration between your app and QB Desktop using COM objects as well but that is beyond the scope of these lessons.</p> <p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=4pwV2sBy2cA#t=0m22s" target="_blank">0:22 mark</a>. </div></p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/170/high-level-qbwc.png" /> <figcaption class="count">Diagram demonstrating how the QBWC works.</figcaption> </figure></p> <h3>QBWC is a SOAP Client</h3> <p>On your end you have to create a SOAP sever because the QBWC is SOAP client. <a href="https://en.wikipedia.org/wiki/SOAP" target="_blank">SOAP</a> is an acronym for Simple Object Access Protocol, which is a protocol that sends text back and forth so you can integrate two systems no matter the operating systems. In the case of QBWC it is just XML going back a forth.</p> <h3>How is the QBWC Setup?</h3> <p>So how do you get your SOAP server to talk to the QBWC? Through a "application file" (.qwc file) that you would have to set up. That will be another another lesson, but in short you provide a URL for connecting to your SOAP server (I'll be helping you create this SOAP server in other lessons) and some other parameters. Every two minutes (this is the default but this duration can be adjusted higher), the QBWC kicks up and goes out to ask the SOAP server if it has anything for it to do.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=4pwV2sBy2cA#t=2m20s" target="_blank">2:20 mark</a>. </div> <h3>Then What?</h3> <p>You would make a queue on your SOAP server with different things for the QBWC to do. Let's say you wanted to know if certain payments in QuickBooks Desktop have now been put in the <i>Deposited Funds</i> chart of account. Your app puts something on the queue asking to query QB Desktop (via the QBWC) for recent payments and what chart of accounts they are in. Or if you wanted to make a customer on your app and then make it on QBD also you would put that request on the queue and on the next two minute run you'll have a customer in QBD. Simple, right? Maybe to explain but not in practice but remember, I'm here to help but again that'll be in another lesson.</p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=4pwV2sBy2cA#t=3m00s" target="_blank">3:00 mark</a>. </div> <h3>Conclusion</h3> <p>That's the QBWC in about 3 minutes and instead of being a high-level overview it probably feels more like a <i>10,000 foot view</i>. Don't worry, there are over 30 lessons in the master class series and every aspect of doing a proper integration will be gone over in detail.</p> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-master-class-is-here.html 2018-11-16T00:00:00Z <p>QuickBooks Desktop Integration Master Class Is Here!</p> <p></p> <h3>My Master Class on QuickBooks Desktop Integration via the QuickBooks Web Connector is now done.</h3> <p>The <a href="/teaches/quickbooks/desktop/integration" target="_blank">video portion of the class</a> is completely free!</p> <!--- <ul> <li>Lesson #1 QuickBooks Web Connector Overview</li> <li>Lesson #2 SOAP Server Strategy</li> <li>Lesson #3 QBXML Overview</li> <li>Lesson #4 SOAP Service Action Overview</li> <li>Lesson #5 Install QuickBooks Desktop NFR copy</li> <li>Lesson #6 Install QuickBooks Desktop via VirtualBox</li> <li>Lesson #7 Install the QBSDK</li> <li>Lesson #8 SDKTestPlus Tool</li> <li>Lesson #9 QBSDK Verbose Logging</li> <li>Lesson #10 QBWC Setup - Part 1</li> <li>Lesson #11 QBWC Setup - Part 2</li> <li>Lesson #12 QBWC Setup - Part 3</li> <li>Lesson #13 QBWC Setup - Part 4</li> <li>Lesson #14 Authentication, Request, & Response Actions</li> <li>Lesson #15 Send Request Overview</li> <li>Lesson #16 The Iterator - Part 1</li> <li>Lesson #17 The Iterator - Part 2</li> <li>Lesson #18 The Iterator Sequence - Part 3</li> <li>Lesson #19 The Iterator Sequence - Part 4</li> <li>Lesson #20 The Iterator Sequence - Part 5</li> <li>Lesson #21 How to Properly Handle Customers</li> <li>Lesson #22 Creating a Customer On Your App and Send It To QuickBooks</li> <li>Lesson #23 Updating or Modifying a Customer - Part 1</li> <li>Lesson #24 Updating or Modifying a Customers - Part 2</li> <li>Lesson #25 Creating a Invoice on QuickBooks - Part 1</li> <li>Lesson #26 Modifying an Existing Invoice - Part 1</li> <li>Lesson #27 Modifying an Existing Invoice - Part 2</li> <li>Lesson #28 QBXML & Special Characters</li> <li>Lesson #29 Trailing White Space Within Entity Unique Keys</li> <li>Lesson #30 Deleting Entities</li> <li>Lesson #31 Advanced Error Handling - Part 1</li> <li>Lesson #32 Advanced Error Handling - Part 2</li> <li>Lesson #33 Automated Testing</li> </ul> --> <p><b>Don't go it alone.</b> QuickBooks Desktop Integration is very unorthodox. My hands on consulting options will help you and your team <b>go fast and you'll have an integration that will not need to be rewritten after 6 months of rookie mistakes</b> that could have easily been avoided by enlisting me as your guide.</p> Christian Pelczarski https://minimul.com/quickbooks-desktop-integration-training-coming-soon.html 2018-11-16T00:00:00Z <p>QuickBooks Desktop Integration Training Coming Soon</p> <p></p> <p>I am working on a QuickBooks Desktop Integration via the QuickBooks Web Connector training course that I hope to have done by the end of the year. I am going to make it relevant no matter what tech stack you are on.</p> Christian Pelczarski https://minimul.com/getting-started-with-the-xero-api-gem.html 2018-11-02T00:00:00Z <p>Getting Started With The Xero Api Gem</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/e-lsLCAzjco?wmode=transparent" frameborder="0"></iframe> </p> <h2>Why Another Ruby Xero Gem?</h2> <p>I am slated to begin a Xero integration soon and I took a preliminary look-see. There is quite a bit in common with QuickBooks Online with regards to resources and entities, however, when I started looking closely at the Ruby-Xero gems the current crop of de facto ones were very old, being written at a time when the Xero API was XML only.</p> <p>This is the same kind of <a href="/introducing-a-new-ruby-quickbooks-online-client.html">thing that plagued the quickbooks-ruby gem</a>. When constructing an API client to a XML-only endpoint a lot of "cruft" can start to accumulate depending on how you approach building the XML payloads.</p> <p><strong>Therefore, I built a new <a href="https://github.com/minimul/xero-api">Ruby-Xero client</a> that is <em>pure</em> JSON-in, JSON-out and certified to be 100% <em>anti-cruft</em>.</strong> <em>Ok, maybe 95%</em> 😇</p> <h3>How Much Anit-Cruft?</h3> <p>Here are the line count totals (of <code>.rb</code> files):</p> <ul> <li>Total LOC count of : <ul> <li><a href="https://github.com/minimul/xero-api"><strong>minimul/xero-api</strong></a> => <strong>910!</strong> 🚀</li> <li><a href="https://github.com/waynerobinson/xeroizer">waynerobinson/xeroizer</a> => 6019</li> <li><a href="https://github.com/xero-gateway/xero_gateway">xero-gateway/xero_gateway</a> => 5545</li> </ul> </li> </ul> <h3>Get Started Hacking Xero With xero-api</h3> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=e-lsLCAzjco#t=1m30s" target="_blank">1:30 mark</a> of the screencast. 🎥 </div> <ol> <li>Follow and do all in Step 1 from the <a href="https://developer.xero.com/documentation/getting-started/getting-started-guide">Getting Started Guide</a>.</li> <li><code>git clone git://github.com/minimul/xero-api &amp;&amp; cd xero-api</code></li> <li><code>bundle</code></li> <li>Create a <code>.env</code> file <ol> <li><code>cp .env.example_app.oauth1 .env</code></li> <li>Edit the <code>.env</code> file values with <code>consumer_key</code> and <code>consumer_secret</code>.</li> </ol> </li> <li>Start up the example app => <code>ruby example/oauth.rb</code></li> <li>In browser go to <code>http://localhost:9393</code>.</li> <li>Use the <code>Connect to Xero</code> button to connect to your Xero account.</li> <li>After successfully connecting click on the displayed link => <code>View All Customers</code></li> <li>Checkout <a href="https://github.com/minimul/xero-api/blob/master/example/oauth.rb"><code>example/oauth.rb</code></a> to see what is going on under the hood. <ul> <li><strong>Important:</strong> In the <a href="https://github.com/minimul/xero-api/blob/master/example/oauth.rb"><code>/auth/xero/callback</code></a> route there is code there that will automatically update your <code>.env</code> file.</li> </ul> </li> </ol> <h3>Once your .env file is completely filled out you can use the bin/console test to play around</h3> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=e-lsLCAzjco#t=5m29s" target="_blank">5:29 mark</a> of the screencast. 🎥 </div> <pre> bin/console test >> @xero_api.get :contacts, id: '5345-as543-4-afgafadsafsad-45334' </pre> Christian Pelczarski https://minimul.com/quickbooks-connect-and-intuit-sign-on-buttons-in-svg.html 2018-04-10T00:00:00Z <p>QuickBooks Connect and Intuit Sign on Buttons in SVG</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/MSjn9ayoFC4?wmode=transparent" frameborder="0"></iframe> </p> <h2>SVG (plus some tweaking) = Responsive</h2> <p>I got tired of dealing with raster image versions when building responsive views. With SVG browser support having <a href="https://qbo-buttons.herokuapp.com/">fully arrived</a> it was time to not wait for Intuit and go ahead and get <a href="https://developer.intuit.com/docs/00_quickbooks_online/2_build/10_authentication_and_authorization/40_oauth_1.0a/widgets">their buttons</a> into the future.</p> <p>When downloading Intuit's buttons .zip file I noticed that they included an .eps file(s) covering of all the raster image kinds. I <a href="https://cloudconvert.com/eps-to-svg">converted</a> the .eps files into .svg counterparts and imported that into the awesome <a href="https://designer.gravit.io/">Gravit Designer</a>.</p> <p>From these <a href="https://github.com/minimul/qbo_buttons/tree/master/svg_src">Gravit Designer source files</a> I created the <a href="https://github.com/minimul/qbo_buttons/tree/master/app/assets/qbo_buttons">individual .svg source files</a> and built a <a href="https://github.com/minimul/qbo_buttons">Rails plugin</a>.</p> <h2>Demo please</h2> <p>See the <a href="https://qbo-buttons.herokuapp.com/">demo page</a> and scale the width to see how well these buttons adjust.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/166/qbo-buttons-demo.png" /> <figcaption class="count">The qbo_buttons gem <a href="https://qbo-buttons.herokuapp.com/">demo page</a>.</figcaption> </figure></p> <h2>I'm not Riding Rails</h2> <p>If you are not on the Rails tech stack, no problem, just grab the <a href="https://github.com/minimul/qbo_buttons/tree/master/svg_src">Gravit source files</a> or the <a href="https://github.com/minimul/qbo_buttons/tree/master/app/assets/qbo_buttons">individual .svg files</a> from the <a href="https://github.com/minimul/qbo_buttons">qbo_buttons</a> gem and build your own solution.</p> Christian Pelczarski https://minimul.com/test-for-quickbooks-api-tls-1-2-support.html 2017-12-22T00:00:00Z <p>Test for QuickBooks API TLS 1.2 support</p> <p>Intuit is requiring QBO API connections to be <a href="https://developer.intuit.com/hub/blog/2017/07/11/tls-1-0-1-1-disablement-intuit-developer-group" target="_blank">done over TLS 1.2 or greater starting on December 31st, 2017</a>. In this article, I'll show you how to test the 2 main QuickBooks Ruby libraries, <a href="https://github.com/ruckus/quickbooks-ruby" target="_blank">quickbooks-ruby</a> and <a href="https://github.com/minimul/qbo_api" target="_blank">qbo_api</a>.</p> <hr/> <h3>Notes:</h3> <pre> # Rails console for app you want to test # FOR QboApi gem. # 1. instantiate a QboApi object $ qbo_api_instance.connection(url: 'https://www.ssllabs.com/ssltest/viewMyClient.html').get # FOR quickbooks-ruby gem # 1. instantiate a quickbooks-ruby object $ quickbooks_ruby_instance.service.send(:do_http_get, 'https://www.ssllabs.com/ssltest/viewMyClient.html') # Search the HTML output using the search string 'protocol_tls1_2' and see if you get a "Yes*" # e.g. successful fragement => \"protocol_tls1_2\">Yes*&lt;/td>\ </pre> <hr/> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://www.youtube.com/embed/sZGvjWMlkv0?wmode=transparent" frameborder="0"></iframe> </p> <hr/> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://www.youtube.com/embed/5Qj4qvWig4I?wmode=transparent" frameborder="0"></iframe> </p> Christian Pelczarski https://minimul.com/modify-the-quickbooks-online-interface-with-a-chrome-extension.html 2017-10-25T00:00:00Z <p>Modify the QuickBooks Online Interface with a Chrome Extension</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/h1LtkLRcpPA?wmode=transparent" frameborder="0"></iframe> </p> <h2>Hacking the QuickBooks Online UI</h2> <p>On a recent client project I had been regularly monitoring the QBO sandbox audit log to make sure we were getting the proper results before going live. To validate these results I thought of providing a CSV file of the audit log table to my client's accounting staff.</p> <p>At first I was just cutting and pasting some JavaScript functions into the Chrome developer tools but that got old and time consuming so instead I just built a full-fledged extension, remarkably called "QBO Audit Log to CSV".</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/164/qbo-chrome-extension.png" /> <figcaption class="count">This is not a public released Chrome extension and it is only available at my <a href="https://github.com/minimul/qbo-audit-log-to-csv">Github repo</a>.</figcaption> </figure></p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/164/added-links.png" /> <figcaption class="count">The extension adds these 2 links to the Audit Log view only.</figcaption> </figure></p> <p>I did not officially release the extension on the Chrome Web Store so if you want to use it you'll have to follow the instructions in the next section.</p> <h2>Getting started</h2> <ol> <li>You are going to need the latest Node version.</li> <pre> $ mkdir ~/chrome-ext $ git clone git@github.com:minimul/qbo-audit-log-to-csv.git $ cd qbo-audit-log-to-csv $ npm install </pre> <li>In a separate terminal run <code>gulp watch</code>.</li> <li>Open up Chrome and go to <code>chrome://extensions</code>.</li> <li>Check "Developer mode:" ✔︎</li> <li>Click "Load Unpacked Extensions"</li> <li>And navigate to the <code>/app</code> directory e.g. <code>~/chrome-ext/qbo-audit-log-to-csv/app</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/164/qbo-chrome-extension-setup.png" /> <figcaption class="count">Here are the main points of note when setting up the extension.</figcaption> </figure> <li>Now you can open <code>qbo.intuit.com/app/auditlog</code> or <code>sandbox.qbo.intuit.com/app/auditlog</code> and you'll see the 2 links from Fig. 2.</li> </ol> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=h1LtkLRcpPA#t=2m20s" target="_blank">2:20 mark</a>. </div> <h2>Modifying the UI</h2> <p>The 3 main scripts that do all the work are: <code>app/scripts.babel/background.js</code>; <code>app/scripts.babel/contentscript.js</code>; and <code>/manifest.json</code>. I'll briefly discuss these files and some of things to take note of.</p> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>The <code>gulp watch</code> command will automatically compile the files in the <code>/app/scripts.babel</code> directory to the <code>/app</code> directory whenever there is a file modification.</p> </div> <h3>The manifest.json file</h3> <p>This is your config and setup file. Here is the <code>manifest.json</code> file for the QBO Audit Log To CSV extension.</p> <pre> { "name": "QboAuditLog to CSV", "version": "1.0.0", "manifest_version": 2, "description": "QboAuditLog to CSV", "icons": { "128": "images/128-icon.png" }, "default_locale": "en", "background": { "scripts": [ "scripts/chromereload.js", "scripts/background.js" ] }, "permissions": [ "tabs", "webNavigation", <span class="inner-highlight">"https://*.intuit.com/*/*"</span> ], "content_scripts": [ { "matches": [ <span class="inner-highlight">"https://*.intuit.com/*/*"</span> ], "js": [ "scripts/contentscript.js" ], "run_at": "document_end", "all_frames": false } ] } </pre> <p>In this file you determine which scripts are "background" and "content_scripts". You set permissions and determine what URL pattern the extension will run against. As you can see only permissions for "tabs" and "webNavigation" were needed.</p> <h3>The background.js file</h3> <p>The <code>background.js</code> file is a bit hard to explain so here is its definition according to the <a href="https://developer.chrome.com/extensions/background_pages" target="_blank">docs</a></p> <blockquote> <h1>❝</h1> <p>A common need for extensions is to have a single long-running script to manage some task or state.</p> <p>Background pages to the rescue.</p> <p> As the architecture overview explains, the background page is an HTML page that runs in the extension process. </p> <p> It exists for the lifetime of your extension, and only one instance of it at a time is active. (Exception: if your extension uses incognito "split" mode, a second instance is created for incognito windows.) </p> <h1>❞</h1> </blockquote> <p>Let's take a look at the extension's <code>background.js</code>.</p> <pre> 'use strict'; console.log('QboAudit Background Script'); chrome.runtime.onInstalled.addListener(details => { console.log('QboAudit previousVersion', details.previousVersion); }) chrome.webNavigation.onHistoryStateUpdated.addListener( (details) => { console.log('QboAudit Page uses History API and we heard a pushSate/replaceState.') if(typeof chrome._LAST_RUN === 'undefined' || notRunWithinTheLastSecond(details.timeStamp)){ chrome._LAST_RUN = details.timeStamp chrome.tabs.getSelected(null, function (tab) { <span class="inner-highlight">if(tab.url.match(/.*\/app\/auditlog1/)){ chrome.tabs.sendRequest(tab.id, 'runQboAuditLog') }</span> }) } }) const notRunWithinTheLastSecond = (dt) => { const diff = dt - chrome._LAST_RUN if (diff < 1000){ return false } else { return true } } </pre> <p>Of note is the highlighted line above. Naturally, you only want to run the extension within the QBO audit log, which has an ending URL like this: <code>*.intuit.com/app/auditlog</code>. To accomplish this I have a basic wild card set in the <code>manifest.json</code> but I couldn't dial in the permissions any further then what is presently set (see <code>manifest.json</code> above) without getting errors, therefore, I had to put the rest of the solution within the <code>background.js</code> file.</p> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>What's with all of the <code>chrome._LAST_RUN</code> stuff?</p> <p>Since QuickBooks Online is a single page app you'll need to monitor the push or history state to determine if the user is navigating around. There is a <a href="https://stackoverflow.com/questions/36808309/chrome-extension-page-update-twice-then-removed-on-youtube/46830077" target="_blank">"bug"</a> that duplicate push state events will get kicked off almost simultaneously. Therefore, the rest of the code in <code>background.js</code> deals with not running that duplicate event.</p> </div> <h3>The contentscript.js file</h3> <p>The <code>contentscript.js</code> is the one script that will actually get run inside the page in the traditional sense, like as if you are loading it in via the page's <code>&lt;script>&lt;/script></code> tag.</p> <p>This file contains all of the extension's logic and is called by the background script when the conditions are right.</p> <pre> // background.js excerpt that calls the contentscript.js when the page's URL // suffix of /app/auditlog is navigated to. if(tab.url.match(/.*\/app\/auditlog/)){ <span class="inner-highlight">chrome.tabs.sendRequest(tab.id, 'runQboAuditLog')</span> } // contentscript.js chrome.extension.onRequest.addListener((request, sender, sendResponse) => { <span class="inner-highlight">if (request == 'runQboAuditLog')</span> new QboAuditLog() }); </pre> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=h1LtkLRcpPA#t=9m29s" target="_blank">9:28 mark</a>. </div> <p>Here is the full <code>contentscript.js</code> file:</p> <pre> 'use strict'; class QboAuditLog { constructor(){ [this.date, this.time] = new Date().toLocaleString('en-US').split(', '); this.init() } init() { const domCheck = setInterval( () => { console.log('QboAudit loop running'); if(document.querySelector('.filterBarContainer')){ clearInterval(domCheck) this.createLinks() } else { console.log('QboAudit miss'); } }, 2000) } createLinks(){ this.csvLink() this.removeDupsLink() } csvLink(){ const link = document.createElement('a') link.innerHTML = 'CSV' link.addEventListener('click', () => { this.triggerDownload() return false }) return document.querySelector('.filterBarContainer').appendChild(link) } removeDupsLink(){ const link = document.createElement('a') link.innerHTML = 'Remove Dups' link.style.paddingLeft = '10px' link.addEventListener('click', () => { this.removeDups() return false }) return document.querySelector('.filterBarContainer').appendChild(link) } download(csv, filename) { const csvFile = new Blob([csv], {type: 'text/csv'}) const downloadLink = document.createElement('a') downloadLink.download = filename downloadLink.href = window.URL.createObjectURL(csvFile) downloadLink.style.display = 'none' document.body.appendChild(downloadLink) console.dir(downloadLink); downloadLink.dispatchEvent(new MouseEvent('click')) document.body.removeChild(downloadLink) } triggerDownload() { let csv = [] const rows = this.getRows() for (let i = 0; i < rows.length; i++) { let row = [], cols = rows[i].querySelectorAll('td') for (let j = 0; j < cols.length; j++){ row.push(`"${cols[j].innerText}"`) } csv.push(row.join(',')) } return this.download(csv.join('\n'), `daily-report-${this.date}.csv`) } getRows(){ return document.querySelectorAll('.dgrid-content table tr') } removeDups() { const sel = '.dgrid-column-4'; const els = document.querySelectorAll(sel) els.forEach((item) => { const name = item.innerText let seen = {} this.getRows().forEach((tr) => { const txt = tr.querySelector(sel).innerText //console.log(`QboAudit ${txt}`); if(txt === ''){ tr.closest('div').remove() return } if (name === txt){ if(seen[txt]){ tr.closest('div').remove() } else { seen[txt] = true } } }) }) } } chrome.extension.onRequest.addListener((request, sender, sendResponse) => { if (request == 'runQboAuditLog') new QboAuditLog() }); </pre> <p>Pay special attention to the <code>init()</code> method. I had to create a loop to check when the DOM was ready to properly insert the links. I tried various things but only constructing this manual loop worked.</p> <p>Also, take a look at the <code>download()</code> method. It uses the <code>Blob</code> object but I needed to dispatch a mouseclick event as seen in this line <code>downloadLink.dispatchEvent(new MouseEvent('click'))</code> to actually get the download to work.</p> <h2>Conclusion</h2> <p>Be sure to check out the <a href="https://youtu.be/h1LtkLRcpPA" target="_blank">screencast</a> as I have much more commentary including some useful information on debugging and on the automatic <code>chromereload.js</code> code that comes with the <a href="https://github.com/yeoman/generator-chrome-extension" target="_blank">Yeoman Chrome Generator</a>, which is what I used to generate the Chrome extension scaffolding. Lastly, don't forget all the code is in its own <a href="https://github.com/minimul/qbo-audit-log-to-csv" target="_blank">Github repo</a>.</p> Christian Pelczarski https://minimul.com/refreshing-the-quickbooks-oauth2-access-token.html 2017-08-19T00:00:00Z <p>Refreshing the QuickBooks OAuth2 access token</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/oG01brZHEwM?wmode=transparent" frameborder="0"></iframe> </p> <h2>The 1 Hour Problem</h2> <p>With QuickBooks OAuth 1a the access token was valid for 6 months. With respect to development, your experience might go something like this. Every 6 months or so when sandbox testing some QuickBooks API feature you would scratch your head a bit but then eventually figure out your development access token expired. You'd clear out some stuff in the database so that the QuickBooks Connect button would reappear and then reconnect to get a new access token. Rinse and repeat every 6 months.</p> <p>Sometimes I felt like automating this token renewal process (like in production) but with the 6 month expiry grace period the return on investment for automation was always too low.</p> <p>However, with OAuth 2, access tokens are only valid for 1 hour. This basically forces your hand to come up with development and production environment automated token renewing solutions &dash; immediately.</p> <h2>Proper persistence</h2> <p>In multi-tenant apps I usually use a <code>qbo_accounts</code> table (that belongs to an <code>accounts</code> table) to store credentials for an account.</p> <p>Here are the minimum columns needed.</p> <pre> $ ap QboAccount.columns.map(&:name) [ [ 0] "id", [ 1] "access_token", [ 2] "refresh_token", [ 3] "companyid", [ 4] "account_id", [ 5] "created_at", [ 6] "updated_at", [ 7] "token_expires_at", [ 8] "reconnect_token_at", ] </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>You should be encrypting the <code>access_token</code>, <code>refresh_token</code>, and the <code>companyid</code> columns at the database level for which I leverage the <a href="https://github.com/attr-encrypted/attr_encrypted" target="_blank">attr_encrypted</a> gem.</p> </div> <h2>Persisting a OAuth2 response</h2> <p>When the OAuth2 response comes back persist it being sure to set the <code>token_expires_at</code> attribute for 60 minutes and the <code>reconnect_token_at</code> attribute for 50 minutes.</p> <pre> account.create_qbo_account( access_token: response.access_token, refresh_token: response.refresh_token, companyid: params[:realmId], token_expires_at: 60.minutes.from_now, reconnect_token_at: 50.minutes.from_now ) </pre> <h2>Renewing Rake task</h2> <pre> namespace :quickbooks do task :renew_oauth2_tokens => :environment do if (qbo_accounts = QboAccount.where('reconnect_token_at <= NOW() AND token_expires_at >= NOW()')).empty? p "OAUTH2_RENEW_TOKEN: nothing to do" else qbo_accounts.each do |q| begin client = oauth2_client client.refresh_token = q.refresh_token if resp = client.access_token! duration_attrs = { reconnect_token_at: 1.hour.from_now, token_expires_at: 50.minutes.from_now } attrs = { token: resp.access_token, refresh_token: resp.refresh_token }.merge(duration_attrs) if q.update(attrs) p "SUCCESS_OAUTH2_RENEW_TOKEN: qbo_account: #{q.id}" else p "FAILED_OAUTH2_RENEW_TOKEN: qbo_account: #{q.id} error_message: #{resp}" end end rescue => e p "FAILED_OAUTH2_RENEW_TOKEN: qbo_account: #{q.id} error_message: #{e.message}" end end end end def oauth2_client Rack::OAuth2::Client.new( identifier: ENV['QBO_API_CLIENT_ID'], secret: ENV['QBO_API_CLIENT_SECRET'], redirect_uri: 'http://localhost:3000/accounts/oauth2_callback', authorization_endpoint: "https://appcenter.intuit.com/connect/oauth2", token_endpoint: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer" ) end end end </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>The reason I set the <code>reconnect_token_at</code> duration at 50 minutes is so to NOT run the refresh requests excessively. Moreover, we don't want to keep running refresh requests if there is no chance of renewal because the <code>access_token</code> expiry date is past. Therefore, I am only executing a refresh request when inside of this 10 minute window.</p> <p>The <code>access_token</code> remains static on a successful refresh, it does not change.</p> <p>The <code>renew_oauth2_tokens</code> rake task uses the <a href="https://github.com/nov/rack-oauth2" target="_blank">rack-oauth2</a> gem.</p> </div> <h2>Hook Up The Renew Rake Task in Development</h2> <p>For development I use OSX so I will detail making a <a href="https://en.wikipedia.org/wiki/Launchd" target="_blank">LaunchD</a> task but for Linux you, of course, have <a href="https://en.wikipedia.org/wiki/Cron" target="_blank">Cron</a>, which I use in production. I am not up to speed on Windows OS but leave a recipe in the comments and I will include it in the article.</p> <ol> <li><code>$ cd ~/Library/LaunchAgents</code></li> <li><code>$ vim com.minimul.bsmash.oauth2.renew.plist</code></li> <li>Example plist</li> <pre> &lt;?xml version="1.0" encoding="UTF-8"?> &lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> &lt;plist version="1.0"> &lt;dict> &lt;key>Label&lt;/key> &lt;string>com.minimul.bsmash.oauth2.renew&lt;/string> &lt;key>ProgramArguments&lt;/key> &lt;array> &lt;string>/bin/bash&lt;/string> &lt;string>-l&lt;/string> &lt;string>-c&lt;/string> &lt;string> cd /Users/minimul/www/projects/bordersmash; bin/rake quickbooks:renew_oauth2_tokens &lt;/string> &lt;/array> &lt;key>StartInterval&lt;/key> &lt;integer>300&lt;/integer> &lt;key>RunAtLoad&lt;/key> &lt;true/> &lt;/dict> &lt;/plist> </pre> <li>Save the .plist task.</li> <li><code>$ launchctl load com.minimul.bsmash.oauth2.renew.plist</code></li> </ol> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>This task will run at boot and then every 5 minutes.</p> <p>Note: In development I use <strong>chruby</strong> and <strong>ruby-install</strong> for Ruby management so the above plist may not work when using RVM or rbenv but the <code>/bin/bash -l -c</code> prefix should properly load the intended Ruby environment for all managers.</p> </div> <h2>Production</h2> <p>In production I use the <a href="https://github.com/javan/whenever" target="_blank">Whenever</a> gem in combination with Cron. So here is an example of my <code>config/schedule.rb</code></p> <pre> set :output, "/var/log/cron" every 5.minutes do rake "quickbooks:renew_oauth2_tokens" end </pre> <h2>Problem Solved, Sort of</h2> <p>The beauty of this setup is that you'll never have to worry about your QuickBooks API tokens expiring. However, that presupposes that your local development environment is always running. Have your laptop shutdown for over an hour and your access tokens will all be invalidated.</p> <p>You also must have a production renew job in place from a day one (of your integration), which with OAuth 1a you could have waited a few months before setting that up. Moreover, do you plan on having a maintenance outage for over an hour on your production app? <strong>Great</strong> all of your app's OAuth 2 access tokens just became invalid. What if Intuit itself has even a brief outage on their OAuth 2 server(s)?</p> <p>For these reasons I suspect Intuit will raise the expiry duration at some point. Until then, however, if you, your company or client has an existing (created before July 17th) Intuit developer account that supports OAuth 1a don't be quick to create a new Intuit Developer account meaning to switch your integration to OAuth 2 as this 60 minute expiry time will surely come back to bite you.</p> <p>Lastly, if you insist on implementing OAuth 2 or have a new Developer account and must implement it, consider purchasing <a href="https://leanpub.com/minimul-qbo-guide-vol-1" target="_blank">my book</a> as I have this OAuth 2 integration in much more detail and you'll be helping to support this educational resource site.</p> Christian Pelczarski https://minimul.com/access-the-quickbooks-online-api-with-oauth2.html 2017-07-31T00:00:00Z <p>Access the QuickBooks Online API with OAuth2</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/WFPl6yjwSl4?wmode=transparent" frameborder="0"></iframe> </p> <h2>OAuth2 required for new Intuit Developer Accounts</h2> <p>Starting July 17th, 2017 all new Intuit Developer accounts will need to use OAuth2 for API access. Here is how to use the <a href="https://github.com/minimul/qbo_api">QboApi gem</a> and OAuth2.</p> <p><figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/oauth2.png" /> <figcaption class="count">OAuth2 for new Intuit Developer Accounts.</figcaption> </figure></p> <h3>Choosing an OAuth2 gem or not</h3> <p>I really won't bother trying to re-invent the wheel by rolling your own OAuth2 code. That said, the OAuth2 2-legged process is simpler than the 3-legged OAuth 1a process and if you do want to roll your own OAuth2 code take a look at <a href="https://github.com/IntuitDeveloper/OAuth2PythonSampleApp">Intuit's Python example</a> for a good starting point.</p> <p>As for me, I'll choose to leverage the <a href="https://github.com/nov/rack-oauth2">Rack-OAuth2 gem</a> as I like its ability to directly set endpoints.</p> <h3>Spinning up an OAuth2 example</h3> <ol> <li>Clone the <code>qbo_api</code> gem, switch into the new directory, and <code>bundle</code></li> <pre> $ git clone git://github.com/minimul/qbo_api && cd qbo_api $ bundle </pre> <li> Create a .env file with the <code>client_id</code> and <code>client_secret</code> provided within the App settings page. See Fig. 2. </li> <pre> export QBO_API_CLIENT_ID= export QBO_API_CLIENT_SECRET= </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/keys.png" /> <figcaption class="count">Make a new app, I called this one <strong>QboApi OAuth2 Inc</strong> then go into its settings and then click on the <strong>Keys</strong> tab to get the <strong>client id</strong> and <strong>client secret</strong>. Put those values in the <code>.env</code> file.</figcaption> </figure> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=WFPl6yjwSl4#t=2m16s" target="_blank">2:16 mark</a>. </div> <li>Set the OAuth2 callback or redirect URI to <strong>http://localhost:9393/oauth2-redirect</strong></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/set-redirect-uri.png" /> <figcaption class="count"><strong>Massively IMPORTANT step</strong></figcaption> </figure> <li>Start up the example app</li> <pre> $ shotgun example/app.rb </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/fire-up.png" /> <figcaption class="count">Firing up the QboApi sample app.</figcaption> </figure> <li>Goto <code>https://localhost:9393/oauth2</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/start-url.png" /> <figcaption class="count">Make sure to use <strong>localhost</strong> and not <strong>127.0.0.1</strong>. Intuit only allows the host name <strong>localhost</strong> for OAuth2 sandbox callbacks.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/redirect-uri.png" /> <figcaption class="count">As you can see in the <a href="https://github.com/minimul/qbo_api/blob/master/example/app.rb">example/app.rb</a> file the callback URI is also properly set to <strong>localhost</strong> and not <strong>127.0.0.1</strong>.</figcaption> </figure> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=WFPl6yjwSl4#t=5m19s" target="_blank">5:19 mark</a>. </div> <li>Click on the 'Connect To QuickBooks' button, Sign in, and click on Authorize.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/authorize.png" /> <figcaption class="count">If you are not already signed in, you are going to sign in with your Intuit Developer credentials in case you might be confused. In short you are signing in and connecting your QuickBooks app's sandbox to your QuickBooks app's "App". Clear as mud? Lastly, click "Authorize".</figcaption> </figure> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=WFPl6yjwSl4#t=8m51s" target="_blank">8:51 mark</a>. </div> <li>The response.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/redirect.png" /> <figcaption class="count">Here is a successful response back. Highlighted is the access token.</figcaption> </figure> <li>The response code.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/resp-code.png" /> <figcaption class="count">Check out what's going on <a href="https://github.com/minimul/qbo_api/blob/master/example/app.rb" target="_blank">under the hood</a> on this response.</figcaption> </figure> <li>Click on <strong>Click here to make an API call</strong>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/api-call-1.png" /> <figcaption class="count">Let's test the new access token.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/dukes.png" /> <figcaption class="count"><strong>Success.</strong> We retrieved Sandbox customer # 5 &dash; "Dukes Basketball Camp".</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/162/get-customer.png" /> <figcaption class="count">Reference the <a href="https://github.com/minimul/qbo_api/blob/master/example/app.rb">example/app.rb</a> to see how to make a basic QuickBooks API call with the QBO API gem.</figcaption> </figure> </ol> Christian Pelczarski https://minimul.com/getting-started-with-quickbooks-online-webhooks.html 2016-07-26T00:00:00Z <p>Getting Started with QuickBooks Online Webhooks</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/OmyVXcuKy7E?wmode=transparent" frameborder="0"></iframe> </p> <h2>Webhooks finally have landed.</h2> <p>You can start rewriting your CDC polling code now <i class="icon icon-smile"></i>. Well <a href="https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/webhooks#Best_practices" target="_blank">maybe not</a>. As a prerequisite for this tutorial you need to be able <a href="/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html">to start up the qbo_api example app</a>. If you haven't done that, do it now and come back.</p> <ol> <li>Switch into your local <code>qbo_api</code> directory.</li> <li>Start up the example app</li> <pre> $ shotgun example/app.rb </pre> <li>Go to your QuickBooks Online API app you created for <a href="/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html">your start up example</a> and click on the "Settings" link.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-setup-1.png" /> <figcaption class="count">Make sure you are in the "Development" and NOT "Production" area.</figcaption> </figure> <li>Go down to the "Webhooks" section. You will see that we need to submit a URL so Intuit can POST webhook requests to. The standard way to do this in development is to use <a href="https://ngrok.com" target="_blank">ngrok</a>. </li> <li>Download ngrok and put it in a auto-loaded path. e.g. <code>/usr/local/bin/</code></li> <li>Fire up ngrok on port 9393.</li> <pre> $ ngrok http 9393 </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-ngrok.png" /> <figcaption class="count">Here is the output of the <code>ngrok http 9393</code> command.</figcaption> </figure> <li>Copy the <code>https://</code> url that the <code>ngrok</code> generates.</li> <li>Next, go back to <a href="https://developer.intuit.com">https://developer.intuit.com</a> and put this URL with <code>/webhooks</code> added to the end of it. <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-setup-2.png" /> <figcaption class="count">Add the <code>ngrok</code> URL plus <code>/webhooks</code> route in the app settings page.</figcaption> </figure> <li>Enable "Estimate" events to receive webhooks and hit "Save".</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-setup-3.png" /> <figcaption class="count">Now we are almost ready to test.</figcaption> </figure> <li>Let's add the <code>/webhooks</code> route to the example app.</li> <li>Open up <code>example/app.rb</code> and add this <code>post</code> route.</li> <pre> post '/webhooks' do request.body.rewind data = request.body.read puts JSON.parse data verified = verify_webhook(data, env['HTTP_INTUIT_SIGNATURE']) puts "Verified: #{verified}" end </pre> <li>Next, let's add the <code>verify_webhook</code> method.</li> <pre> helpers do def verify_webhook(data, hmac_header) digest = OpenSSL::Digest.new('sha256') calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, VERIFIER_TOKEN, data)).strip calculated_hmac == hmac_header end end </pre> <li>You'll also need to require the <code>openssl</code> and <code>base64</code> libraries.</li> <pre> require 'openssl' require 'base64' </pre> <li>You are going to need to add the <code>VERIFIER_TOKEN</code> variable.</li> <li>Go back Intuit Developer and copy the verifier token to your clipboard.</li> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=OmyVXcuKy7E#t=7m47s" target="_blank">7:47 mark</a>. </div> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-copy-token.png" /> <figcaption class="count">Copying the <code>verifier token</code>.</figcaption> </figure> <li>Add the verifier token to the <code>.env</code> file like so:</li> <pre> ... export QBO_API_COMPANY_ID=1441111111 export QBO_API_VERIFIER_TOKEN=[put here] </pre> <li>Then in the <code>example/app.rb</code> put in this (below the <code>CONSUMER_SECRET</code> constant):</li> <pre> VERIFIER_TOKEN = ENV['QBO_API_VERIFIER_TOKEN'] </pre> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=OmyVXcuKy7E#t=10m10s" target="_blank">10:10 mark</a>. </div> <li>Open up your sandbox that was used when performing <i>Spinning up an example app</i> <a href="/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html">step</a>.</li> <li>Copy this URL (<code>https://sandbox.qbo.intuit.com/app/estimate?txn=100</code>) and paste into the address bar after logging into the sandbox to go directly to Estimate 1001.</li> <li>Change the Status from "Pending" to "Accepted" and hit "Save".</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-change-status.png" /> <figcaption class="count">You don't have to fill out the Accepted Status date.</figcaption> </figure> <li>Go back the console where example app is running and look for the incoming request.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-request-1.png" /> <figcaption class="count">Look here for the incoming webhook request.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/161/quickbooks-webhooks-request-2.png" /> <figcaption class="count">In took about 15 seconds but it arrives!</figcaption> </figure> <li>From this point you want to parse the JSON, putting the results into a queue for backend processing.</li> </ol> <h3>Conclusion</h3> <p>QuickBooks Online Webhooks support is going to radically simplify syncing user actions on QBO to your app. I am planning on perhaps 2 more webhooks tutorials so sign up for the newsletter below. Lastly, make sure you read Intuit's Best Practices <a href="https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/webhooks#Best_practices">section</a>.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/the-modern-ruby-quickbooks-client-contributing.html 2016-03-01T00:00:00Z <p>The modern Ruby QuickBooks client: Part 3 - Gem Contributing</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/v6U5xcFMKpA?wmode=transparent" frameborder="0"></iframe> </p> <h2>Record your own QBO sandbox transactions in a PR</h2> <p>You would like to enhance or fix something in the <code>qbo_api</code> gem but aren't too sure about the "VCR-recorded-sandbox-transaction" part of the specs. Don't you worry a bit about that. Frankly, this "VCR-recorded-sandbox-transaction" thing will actually make it easier for you to contribute and not the other way around. Creating fictional API fixtures is the harder and more error-prone approach.</p> <ol> <li>Fork the <code>qbo_api</code> gem</li> <li>Clone your fork, switch into the new directory, and <code>bundle</code></li> <pre> $ git clone git://github.com/[your fork]/qbo_api && cd qbo_api $ bundle </pre> <li>Make a new branch for your PR e.g. <code>git checkout -b cust-ref-name</code></li> <li>Create a <code>.env</code> file and fill in all the values</li> <ul> <li>Please see <a href="/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html" target="_blank">part 1</a>, which will fully explain how to get all the values you need to properly fill in the <code>.env</code> file.</li> </ul> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=v6U5xcFMKpA#t=2m22s" target="_blank">2:22 mark</a>. </div> <li>Next, run the specs <code>bundle exec rspec spec/</code>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/160/qbo-api-specs-green.png" /> <figcaption class="count">Let's make sure the specs are green before we move on.</figcaption> </figure> <li>Open the spec <code>spec/create_update_delete_spec.rb</code></li> <li>Add a name attribute to the <code>CustomerRef</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/160/qbo-api-specs-add-name.png" /> <figcaption class="count">Here we are modifying an existing spec that creates an invoice. Its transaction has been recorded by VCR.</figcaption> </figure> <li>Change the record setting to "all". <code>record: :all</code> and rerun</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/160/qbo-api-specs-all-rerun.png" /> <figcaption class="count">We are rerunning the specs with a new payload. With the <code>record: :all</code> setting enabled the spec will test against your sandbox with VCR recording the transaction.</figcaption> </figure> <li>Run the <code>git status</code> to see which files have been modified.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/160/qbo-api-specs-git-status.png" /> <figcaption class="count">Notice that the VCR recorded file <code>spec/vcr/qbo_api/create/invoice.yml</code> has been updated with the new recording.</figcaption> </figure> <li>Change the <code>record: :all</code> back to <code>record: :none</code>.</li> <li>Commit the changes: <code>git commit -a -m "Add CustomerRef:name information when create a new invoice"</code></li> <li>Then push the branch and submit the pull request.</li> </ol> <h2>Conclusion</h2> <p>Contributing to the <code>qbo_api</code> gem is a cinch. The fact that the gem utilizes VCR-recorded transactions against real QuickBooks sandboxes makes it a great choice. That said, don't be intimated at all by VCR; follow this tutorial and you will easily be able to contribute in making the gem even better.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/the-modern-ruby-quickbooks-client-part-2.html 2016-01-23T00:00:00Z <p>The modern Ruby QuickBooks client Part 2</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/vjRQGvHbCV8?wmode=transparent" frameborder="0"></iframe> </p> <h2>"Real world" fixtures</h2> <p>The <a href="https://github.com/minimul/qbo_api" target="_blank">qbo_api</a> gem's specs were built against an official QuickBooks sandbox and then recorded using the <a href="https://github.com/vcr/vcr" target="_blank">VCR</a> gem. Therefore, you have a fantastically quick way to get started with your integration project in that you can see "real world" QBO API transaction without your own sandbox or even a network connection. Allow me to demonstrate.</p> <ol> <li>Clone the <code>qbo_api</code> gem, switch into the new directory, and <code>bundle</code></li> <pre> $ git clone git://github.com/minimul/qbo_api && cd qbo_api $ bundle </pre> <li> Create a .env file If you are just running the specs then at minimum your .env needs to look like the following: </li> <pre> export QBO_API_CONSUMER_KEY= export QBO_API_CONSUMER_SECRET= export QBO_API_ACCESS_TOKEN= export QBO_API_ACCESS_TOKEN_SECRET= export QBO_API_COMPANY_ID=12345 </pre> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=vjRQGvHbCV8#t=2m48s" target="_blank">2:48 mark</a>. </div> <li>Now you can run the specs by doing a <code>bundle exec rspec spec/</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-green.png" /> <figcaption class="count">Let's get some green before we dive in further. Note: the <code>be</code> is a bash alias for <code>bundle exec</code>.</figcaption> </figure> <li>Let's open up the <code>spec/create_update_delete_spec.rb</code></li> <li>Then go to the first spec where abouts the VCR cassette is being recorded.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-init.png" /> <figcaption class="count">Here you see how the gem is initialized.</figcaption> </figure> <li>The <code>creds</code> method is defined in the <code>spec/spec_helper.rb</code> </li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-helper.png" /> <figcaption class="count">The <code>.env</code> file is loaded by the <code>Dotenv</code> gem.</figcaption> </figure> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>If you want to see an example of an advanced VCR custom matcher keep reading the <code>spec/spec_helper.rb</code> file. The purpose of the matcher is to make VCR more lenient so that any QuickBooks sandbox can be used.</p> </div> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=vjRQGvHbCV8#t=5m29s" target="_blank">5:29 mark</a>. </div> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-named-params.png" /> <figcaption class="count">The <code>qbo_api</code> initialization and many other methods use keyword arguments, which is the primary reason for the Ruby 2.2.2 requirement.</figcaption> </figure> <li>Before the initialize set <code>QboApi.log = true</code> and run the individual spec.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-log-1.png" /> <figcaption class="count">Use the <code>log</code> feature to view the HTTP transaction to QBO API.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-log-2.png" /> <figcaption class="count">Here is the full (real not a fictional fixture) request and response for creating an invoice.</figcaption> </figure> <li>Now let's mess around with the response by outputing the invoice's customer name</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-customer-name.png" /> <figcaption class="count">Add a <code>p response['CustomerRef']['name']</code> and then comment out the <code>QboApi.log = true</code> to isolate the output to only the customer name.</figcaption> </figure> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=vjRQGvHbCV8#t=7m44s" target="_blank">7:44 mark</a>. </div> <li>Next, open up the <code>spec/error_spec.rb</code> and goto the spec titled "handles a validation error".</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/159/quickbooks-api-specs-error.png" /> <figcaption class="count">The spec displays how you can detect an error and how to potentially respond.</figcaption> </figure> </ol> <h2>Conclusion</h2> <p>When getting started with a QuickBooks integration project using the <code>qbo_api</code> gem leverage the "real world" recorded specs to help you get started quickly.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/multiple-quickbooks-sandboxes.html 2016-01-19T00:00:00Z <p>Multiple QuickBooks Sandboxes</p> <p></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/znKouosTMcI?wmode=transparent" frameborder="0"></iframe> </p> <h3>Adding and naming a sandbox</h3> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>Intuit allows a developer account to have up to 5 sandboxes. Let's take a look at making a new sandbox and giving it a descriptive name.</p> </div> <ol> <li>Login to your developer account and select "Sandbox" from the "Hello, &lt;login name> dropdown." </li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-select.png" /> <figcaption class="count">Sandboxes are tied to your developer account not your apps.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-default.png" /> <figcaption class="count">Here is an example of a default developer sandbox.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-countries.png" /> <figcaption class="count">Sandbox countries supported besides U.S. are Austraila, Canada, India, and U.K.</figcaption> </figure> <li>Click the "Add" button.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-2nd-company.png" /> <figcaption class="count">After the screen refreshes you will see the second sandbox.</figcaption> </figure> <li>The new sandbox is not too descriptive but let's change that.</li> <li>Open up "Go to company" into a new browser tab.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-click-company.png" /> <figcaption class="count">Be nice if there was an easier way to change the sandbox company name but for the time being we have go into the account.</figcaption> </figure> <li>Once into the sandbox account click on the gear icon and then "Company settings".</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-company-settings.png" /> <figcaption class="count">(1) Click gear icon; (2) then "Company settings".</figcaption> </figure> <li>Then within the "Company" section => "Company Name" section, click on the pencil icon.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-edit-company-name.png" /> <figcaption class="count">Clicking pencil/edit icon.</figcaption> </figure> <li>Since I want to use this sandbox for testing the <a href="https://github.com/minimul/qbo_api" target="_blank">qbo_api</a> gem I will name this sandbox accordingly. </li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-change-company-name.png" /> <figcaption class="count">Edit the name inline, click "Save", and then "Done".</figcaption> </figure> <li>Go back to your Intuit developer account tab and click "Reload".</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/158/quickbooks-multi-sandboxes-final-name.png" /> <figcaption class="count">Now we have a nice descriptive name for our new sandbox.</figcaption> </figure> </ol> <h2>Conclusion</h2> <p>You don't have fit all of your integration needs by using just one sandbox. Instead, take advantage of the ability to make additional descriptive QuickBooks Online sandboxes to aid in your developer experience.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html 2016-01-18T00:00:00Z <p>Getting starting with the modern Ruby QuickBooks Online client, the qbo_api gem, Part 1</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/5FPrw0q4JS4?wmode=transparent" frameborder="0"></iframe> </p> <h3>Spin up an example app</h3> <p>Ok, I made the <a href="/introducing-a-new-ruby-quickbooks-online-client.html" target="_blank">arguments</a> why the <a href="https://github.com/minimul/qbo_api" target="_blank">qbo_api</a> gem is the "modern" Ruby QuickBooks Online API client but what is the best way to get started using it? I'd advise you to spin up the example app included in the library.</p> <ol> <li>Clone the <code>qbo_api</code> gem, switch into the new directory, and <code>bundle</code></li> <pre> $ git clone git://github.com/minimul/qbo_api $ cd qbo_api $ bundle </pre> <li>Create a .env file with your QuickBooks Online API app's <code>consumer key</code> &amp; <code>consumer secret</code></li> <li>Consumer key and secret? Keep following.</li> <li>If needed create an account at <a href="https://developer.intuit.com" target="_blank">https://developer.intuit.com</a></li> <li>Click "Get started coding"</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-start-coding.png" /> <figcaption class="count">First screen after creating a new account.</figcaption> </figure> <li>Create an app with both the Accounting &amp; Payments selected.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-api-features.png" /> <figcaption class="count">Although, Payments is not needed for the example it is turned on in the example app "Connect to Intuit" button.</figcaption> </figure> <li>Go to the Development tab and copy and paste the consumer key and secret into the .env file.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-keys.png" /> <figcaption class="count">Copy the "OAuth Consumer Key" and the "OAuth Consumer Secret"</figcaption> </figure> <pre> # and put'em the .env file (located at the qbo_api root dir) like so export QBO_API_CONSUMER_KEY=&lt;Your QuickBooks apps consumer key> export QBO_API_CONSUMER_SECRET=&lt;Your QuickBooks apps consumer secret> </pre> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=5FPrw0q4JS4#t=5m33s" target="_blank">5:33 mark</a>. </div> <li> Start up the example app </li> <pre> $ ruby example/app.rb </pre> <li> Goto <code>http://localhost:9393</code> </li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-index.png" /> <figcaption class="count">Simple page with just the "Connect to QuickBooks" button</figcaption> </figure> <li> Use the Connect to QuickBooks button to connect to your QuickBooks sandbox, which you receive when signing up at https://developer.intuit.com. </li> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=5FPrw0q4JS4#t=10m07s" target="_blank">10:07 mark</a>. </div> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-sandbox-1.png" /> <figcaption class="count">To launch the sandbox</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-sandbox-2.png" /> <figcaption class="count">Click on "Go to company"</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-sandbox-3.png" /> <figcaption class="count">Here is your sandbox. Note: it is tied to your login and not the created app. <a href="https://youtube.com/watch?v=5FPrw0q4JS4#t=10m07s" target="_blank">Listen</a> to me talk more about sandboxes on the screencast.</figcaption> </figure> <li>Click on "Connect to QuickBooks"</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-authorize.png" /> <figcaption class="count">You will be presented with this view if you are already logged into your Intuit developer account. Otherwise, you will get a login screen before this view.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-after-auth.png" /> <figcaption class="count">After authorizing you will be returned to the example app. Your OAuth info will be displayed for convenience, although not needed for this tutorial. The <code>token</code>, <code>token_secret</code>, and <code>realm_id</code> are needed to <a href="https://github.com/minimul/qbo_api#initialize" target="_blank">initialize</a> the <code>qbo_api</code> gem. </figcaption> </figure> <li>Next, goto the address is <code>http://localhost:9393/customer/5</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-display-customer.png" /> <figcaption class="count">Should get "Dukes Basketball Camp". Check out the <a href="https://github.com/minimul/qbo_api/blob/master/example/app.rb#L27" target="_blank">code </a> to see how it works.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/157/qbo-api-gem-dukes-in-sandbox.png" /> <figcaption class="count">Dukes Basketball Camp is a sandbox customer.</figcaption> </figure> </ol> <h2>Summary</h2> <p>The fastest way to dig into the <a href="https://github.com/minimul/qbo_api" target="_blank">qbo_api</a> gem is to spin up the example app. After you accomplish that take a look <a href="https://github.com/minimul/qbo_api/blob/master/example/app.rb" target="_blank">under the hood</a> and you will be ready to integrate your app with QuickBooks Online.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/introducing-a-new-ruby-quickbooks-online-client.html 2016-01-05T00:00:00Z <p>Introducing a Faraday-powered, JSON-only Ruby-QuickBooks client</p> <p>&nbsp;</p> <h2>Jealousy</h2> <p>The JSON-only <code>node-quickbooks</code> package has provoked me to a certain level of jealousy when compared to using the XML-only <code>quickbooks-ruby</code> gem. Using JSON means I can get closer to the metal when constructing requests and interpreting responses. Since all the <a href="https://developer.intuit.com/docs/api/accounting" target="_blank">QBO API examples</a> now-a-days are in JSON having a JSON client is essential for a smooth development experience.</p> <h2>Problems with the current defacto Ruby QuickBooks Online library.</h2> <ul> <li><p>Since the QBO API v2 was XML only, <a href="https://github.com/ruckus" target="_blank">Cody Caughlan</a> the creator and maintainer of the v2 <code>quickeebooks</code> gem naturally used a similar XML-centric approach when creating the new QBO API v3 gem, <code>quickbooks-ruby</code>. The choice was practical and sound as it even appeared early on (in v3) that Intuit seemed to favor XML as some entities had better XML support than JSON. That has perceived favoritism has flipped. <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/156/quickbooks-online-json-future.png" /> <figcaption class="count">Intuit employee Tony Chang comments on the future of data formats with regards to the QuickBooks Online API. <a href="https://github.com/ruckus/quickbooks-ruby/issues/257#issuecomment-126834454">Read</a> the full conversation. tldr; The present and future of the QuickBooks Online API is the JSON data format.</figcaption> </figure></p></li> <li><p>Because of the XML choice the <code>quickbooks-ruby</code> gem spends a lot code trying (and rightly so) to abstract XML's verbosity. As a result you need to be constantly looking at the gem code (models in <code>quickbooks-ruby</code>) to see how the QBO API is being translated to <code>quickbooks-ruby</code> models. With a JSON client all of this cruft is not needed. You can literally just grab the JSON from an API example and form a request e.g.</p></li> </ul> <pre> invoice = { "Line": [ { "Amount": 100.00, "DetailType": "SalesItemLineDetail", "SalesItemLineDetail": { "ItemRef": { "value": "1", "name": "Services" } } } ], "CustomerRef": { "value": "1" } } response = qbo_api.create(:invoice, payload: invoice) </pre> <ul> <li>The response is simply <code>JSON.parse</code>'d into a Ruby Hash e.g.</li> </ul> <pre> p response['Id'] # => 65 </pre> <h2>Wouldn't it be nice to able to do that in Ruby?</h2> <h3> You can! Introducing the <a href="https://github.com/minimul/qbo_api" target="_blank">qbo_api gem</a>. </h3> <p>It is based on Ruby &gt;=2.2 and Faraday and is incredibly lightweight compared to the <code>quickbooks-ruby</code> gem.</p> <p>Features:</p> <ul> <li>JSON-only</li> <li>Has robust error handling built in.</li> <li>Has specs written against official QBO sandboxes. <ul> <li>Uses the VCR gem to record sandbox transactions so you're not having to construct fictional fixtures.</li> </ul> </li> </ul> <h2>Why not just use node-quickbooks?</h2> <p>Using Node.js feels like going back many steps compared to the Ruby ecosystem (especially Ruby 2.2 and greater).</p> <p><span id="nodejs-cons">Node.js Cons:</span></p> <ul> <li>No Rails equivalent.</li> <li>The fact that ES6 is only now being aggressively merged into Node.js core is a real detriment to using Node.js. This should have been done long ago as most code is still ES5.</li> <li>Even ES6 is still so far from the polish, design, and features that is Ruby 2.2 and greater.</li> <li>The fact that everything is asynchronous is annoying and results in "silly" troubleshooting situations. I like asynchronous programming when it makes sense, but not as a default.</li> <li>Node.js is akin to the "wild west" and too much is in "flux" for me spend time keeping up with it. I run a consulting business not a lab. <ul> <li>Do I use ES5 or ES6 or Babel or TypeScript or Coffeescript? Seriously.</li> <li>In comparison, Ruby/Rails is stable and "grown-up".</li> </ul> </li> </ul> <h2>My future with quickbooks-ruby?</h2> <p>I'm an official contributor and have clients using <code>quickbooks-ruby</code>. Personally, I will continue looking at and submitting PRs, answering issues, and so forth. There are a huge number of organizations using <code>quickbooks-ruby</code> and many more will continue to choose the gem for new projects. I just released <code>qbo_api</code> and there are virtually <a href="https://rubygems.org/gems/qbo_api" target="_blank">no downloads</a> . The <code>qbo_api</code> gem also has a hard Ruby 2.2 and greater requirement so who knows what adoption it will achieve. On the other hand, the <code>quickbooks-ruby</code> gem is the entrenched de facto Ruby library and will continue to be well into the future. I will be helping maintain it for at a minimum, professional reasons, but I also enjoying helping developers with QuickBooks Online integration.</p> <p>That said, all of my new QuickBooks integration projects will be with the <code>qbo_api</code> gem and my time will be focused (there is still a ton to do, like Payments API, etc.) on it. I don't plan on dedicating any time to enhancing <code>quickbooks-ruby</code> with respect to new features and will not be appending to the JSON support I and <a href="https://github.com/raecoo" target="_blank"><code>raecoo</code></a> added in <code>0.3.0</code>.</p> <h2>Summary</h2> <p>A new QuickBooks Online Ruby JSON-only client, <a href="https://github.com/minimul/qbo_api" target="_blank"><code>qbo_api</code></a>, has been released but the existing Ruby client, <code>quickbooks-ruby</code> remains a viable option.</p> Christian Pelczarski https://minimul.com/getting-started-with-nodejs-and-quickbooks-online-part-4.html 2015-09-17T00:00:00Z <p>Getting started with Nodejs and QuickBooks Online Part 4</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/VRtmFZdJb4U?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>Let's create a Sales Receipt!</h3> <p>In Part 4, I'll show how you can use the <a href="https://github.com/mcohen01/node-quickbooks/tree/master/test" target="_blank">node-quickbooks</a>'s tests to create a <a href="https://developer.intuit.com/docs/api/accounting/SalesReceipt" target="_blank">SalesReceipt</a> transaction against your developer sandbox. Specifically, I will be answering <a href="https://intuitdeveloper.lc.intuit.com/questions/1216361-error-when-trying-to-create-a-sales-receipt-in-the-api-explorer-i-have-used-the-default-and-also-read-an-existing-record-and-tried-to-use-that-but-both-get-an-error" target="_blank">this question</a> from the Intuit Developer forums.</p> <ol> <li>Make a new branch.</li> <pre> $ git branch salesreceipt </pre> <li>Turn <code>on</code> debugging in <code>config.js</code>.</li> <pre> <span class="minus">- debug: false,</span> <span class="plus">+ debug: true,</span> </pre> <li>Open up <code>test/index.js</code>.</li> <li>Go down to the test starting with <code>describe('SalesReceipt', ..</code>.</li> <li>Let's put this test that creates a sales receipt into its own test file <code>test/salesreceipt.js</code>.</li> <li>Then let's apply the JSON request example in the <a href="https://intuitdeveloper.lc.intuit.com/questions/1216361-error-when-trying-to-create-a-sales-receipt-in-the-api-explorer-i-have-used-the-default-and-also-read-an-existing-record-and-tried-to-use-that-but-both-get-an-error" target="_blank">question</a>.</li> <pre> var os = require('os'), fs = require('fs'), util = require('util'), expect = require('expect'), async = require('async'), _ = require('underscore'), config = require('../config'), QuickBooks = require('../index'), qbo = new QuickBooks(config); describe('SalesReceipt', function() { this.timeout(30000); it('should create a new SalesReceipt', function (done) { var sr = { "Line": [{ "Id": "1", "LineNum": 1, "Amount": 35.0, "DetailType": "SalesItemLineDetail", "SalesItemLineDetail": { "ItemRef": { <span class="highlight">"value": "61",</span> "name": "10 Pack Group X Classes" }, "UnitPrice": 35, "Qty": 1, "TaxCodeRef": { "value": "NON" } } }] } qbo.createSalesReceipt(sr, function(err, salesReceipt) { expect(err).toBe(null) expect(salesReceipt.Fault).toBe(undefined) done() }) }) }) </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>Ok, so we modified the <code>test/salesreceipt.js</code> test with the JSON request from the forums question. But one glaring problem is the <code>ItemRef: "61"</code> is not going to work because there is no Item with an Id of 61 in the sandbox. The Item Id 61 is coming from the users QBO.</p> <p>Therefore, we need to find an adequate replacement from the developer sandbox but the sandbox UI doesn't show Ids for Items. The API does show ids so let's create a temporary "test" that will display sandbox items and their Ids.</p> </div> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=VRtmFZdJb4U#t=5m33s" target="_blank">5:33 mark</a>. </div> <li>Make a another test above the current one like this.</li> <pre> // ... this.timeout(30000); it.only('find sandbox items', function (done) { qbo.findItems(function(_, items) { items.QueryResponse.Item.forEach(function(item) { console.log("id:" + item.Id + " name:" + item.Name + " type:" + item.Type) }) }) }); // ... </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>Notice, the <code>it.only</code> syntax. These tests use the <a href="https://mochajs.org" target="_blank"><code>mocha</code></a> framework, which allows using <code>only</code> so only this one test will run and all others will be skipped. Perfect for what we need here, which is simple request so we can plug in a valid <code>ItemRef</code> Id to the other test.</p> </div> <li>Now let's run the test</li> <pre> $ npm test test/salesreceipt.js </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/155/node-quickbooks-display-items.png" /> <figcaption class="count">You should see a listing of sandbox items.</figcaption> </figure> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=VRtmFZdJb4U#t=9m54s" target="_blank">9:54 mark</a>. </div> <li>Let's use a <code>Service</code> type item so Item Id = 6, "Gardening" should work just fine.</li> <pre> "ItemRef": { <span class="minus">- "value": "61",</span> <span class="plus">+ "value": "6",</span> "name": "10 Pack Group X Classes" }, </pre> <li>Next, make sure you either delete the test from step 7 or mark it "skip".</li> <pre> <span class="minus">- it.only('find sandbox items', function (done) {</span> <span class="plus">+ it.skip('find sandbox items', function (done) {</span> </pre> <li>Run the test</li> <pre> $ npm test test/salesreceipt.js </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/155/node-quickbooks-create-salesreceipt.png" /> <figcaption class="count">That did the trick.</figcaption> </figure> </ol> <h3>Conclusion</h3> <p>That's a wrap for this 4 part series, cumulating in creating a transaction entity in QuickBooks Online. Also, <a href="https://github.com/minimul/node-quickbooks/commit/97c4a2d48a2fa6947ba328083df6fa97da0f52e0" target="_blank">reference the code</a> for this article as well as <a href="/getting-started-with-nodejs-and-quickbooks-online-part-3.html">Part 3</a>.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/getting-started-with-nodejs-and-quickbooks-online-part-3.html 2015-09-15T00:00:00Z <p>Getting started with Nodejs and QuickBooks Online Part 3</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/_9mLAq9rkbM?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>Let's get <code>npm test</code> working</h3> <p>In <a href="/getting-started-with-nodejs-and-quickbooks-online-part-1.html">part 1</a> and <a href="/getting-started-with-nodejs-and-quickbooks-online-part-2.html">part 2</a> we just got our feet wet. An important part of digging deeper is to check out the tests, which display more complex transactions. Now, in part 3 we will get <code>npm test</code> working being it is not as simple as you might think.</p> <ol> <li>Grab the latest from <code>node-quickbooks</code> or your fork of it.</li> <pre> git checkout pull upstream master </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>I am back to using the <code>node-quickbooks</code> master so in that regard I am not picking up directly from part 2.</p> <p>We need to spin up the example app again to get the access tokens and realm id so we can record them into <code>config.js</code>, which is used by <code>npm test</code> to properly talk to your sandbox.</p> </div> <li>I noticed some changes in for <code>example/package.json</code> after the upstream pull so:</li> <pre> $ cd example $ npm install </pre> <li>Edit <code>example/app.js</code> and make the following mods:</li> <pre> <span class="minus">-var consumerKey = '', - consumerSecret = '' </span><span class="plus">+var consumerKey = process.env.MINIMULCASTS_CONSUMER_KEY, // substitute your env variable here + consumerSecret = process.env.MINIMULCASTS_CONSUMER_SECRET </span>// ... <span class="minus">- true); // turn debugging on</span> <span class="plus">+ false); // turn debugging off</span> // ... accounts.QueryResponse.Account.forEach(function(account) { <span class="minus">- console.log(account.Name)</span> <span class="plus">+ //console.log(account.Name)</span> }) </pre> <li>Still in the <code>example</code> dir do a <code>nodemon app.js</code></li> <li>Go to the browser <code>http://localhost:3000/</code>.</li> <li>Click the "Connect to QuickBooks" button and authenticate with your sandbox.</li> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>Make sure your Intuit app is enabled both for "QuickBooks" & "Payments" or you will get an error when connecting.</p> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/154/node-quickbooks-app-settings.png" /> <figcaption class="count">Need to be enabled for both API capabilities.</figcaption> </figure> </div> <li>Go back to your console running <code>nodemon app.js</code> and you should see the information we need for the <code>config.js</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/154/node-quickbooks-app-tokens.png" /> <figcaption class="count">Copy the app tokens and realm id from the console.</figcaption> </figure> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=_9mLAq9rkbM#t=8m44s" target="_blank">8:44 mark</a>. </div> <li>You can shutdown the <code>example</code> app now.</li> <li>Create a <code>.env</code> file (don't commit to source control) in the project root directory looking like this:</li> <pre> export OAUTH_TOKEN_SECRET=[ paste secret here (from step 7) ] export OAUTH_TOKEN=[ paste token here ] export REALM_ID=[ paste realm id here ] </pre> <li>Make sure to "source" this file in all of your console windows: <code>source .env</code></li> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=_9mLAq9rkbM#t=10m33s" target="_blank">10:33 mark</a>. </div> <li>Edit <code>config.js</code> to look like this:</li> <pre> module.exports = { consumerKey: process.env.MINIMULCASTS_CONSUMER_KEY, // change for your app's consumer key consumerSecret: process.env.MINIMULCASTS_CONSUMER_SECRET, // change for your app's consumer secret token: process.env.OAUTH_TOKEN, tokenSecret: process.env.OAUTH_TOKEN_SECRET, realmId: process.env.REALM_ID, useSandbox: true, debug: false, // // Set useSandbox to false when moving to production. For info, see the following url: // https://developer.intuit.com/v2/blog/2014/10/24/intuit-developer-now-offers-quickbooks-sandboxes testEmail: '' // Use this email address for testing send*Pdf functions } </pre> <li>Now you are ready to run the tests, so again in the console that you ran <code>source .env</code> run <code>npm test</code></li> <pre> $ source .env $ npm test </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/154/node-quickbooks-npm-test.png" /> <figcaption class="count">Getting some <span class="green">green</span>!</figcaption> </figure> </ol> <h3>Conclusion</h3> <p>That's a wrap for part 3, in which we "simply" got the tests running. In <a href="/getting-started-with-nodejs-and-quickbooks-online-part-4.html">part 4</a> I am going to be leveraging this test setup to answer a question on the <a href="https://intuitdeveloper.lc.intuit.com/questions/1216361-error-when-trying-to-create-a-sales-receipt-in-the-api-explorer-i-have-used-the-default-and-also-read-an-existing-record-and-tried-to-use-that-but-both-get-an-error" target="_blank">Intuit developer forums</a> that will show how you can leverage the tests to getting going on more complex transactions. Also, <a href="https://github.com/minimul/node-quickbooks/commits/part-3-and-4" target="_blank">reference the code</a> for this and <a href="/getting-started-with-nodejs-and-quickbooks-online-part-4.html">part 4</a> of the tutorial.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/getting-started-with-nodejs-and-quickbooks-online-part-2.html 2015-09-08T00:00:00Z <p>Getting started with Nodejs and QuickBooks Online Part 2</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/k2zYxOCSkAw?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>Continuing from Part 1</h3> <p>In <a href="/getting-started-with-nodejs-and-quickbooks-online-part-1.html">part 1</a> we just did the bare minimum. Now, in part 2 we are going to go a bit further by creating a customer route where we will pass in an id and then retrieve the associated customer from the developer sandbox and output their "DisplayName" value.</p> <ol> <li>Let's start by making that new route right below the <code>/start</code> route in <code>app.js</code>.</li> <pre> app.get('/customer/:id', function (req, res) { console.log(req.params.id); res.render('customer.ejs', { locals: { customer: customer }}) } </pre> <li>Then make a new customer view in <code>views/customer.ejs</code></li> <pre> &lt;!DOCTYPE html> &lt;html lang="en"> &lt;head> &lt;meta charset="UTF-8"> &lt;title>&lt;/title> &lt;/head> &lt;body> &lt;h1>Customer&lt;/h1> &lt;/body> &lt;/html> </pre> <li>Go to the browser and run the route <code>http://localhost:3000/customer/5</code>.</li> <li>You should see "5" output to the console running <code>nodemon app.js</code>.</li> <li>Next, we are going implement basic persistence with the <code>cookie-session</code> NPM package</li> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>We need to persist the OAuth token, secret, and Intuit's realmId (now referred as the "company id") to make repeated API calls. In a real app you would use a data store like Postgres, Redis, etc. Here we will use sessions, however, the <code>express-session</code> only persists for a short duration and is meant for data stores. Therefore, let's use the <code>cookie-session</code> package to get some basic persistence.</p> </div> <pre> $ cd example/ $ npm install cookie-session --save // --save option saves the entry to example/package.json </pre> <li>Back in <code>app.js</code> make the following modifications.</li> <pre> .... cookieParser = require('cookie-parser'), - session = require('express-session'), + cookieSession = require('cookie-session'), .... -app.use(session({resave: false, saveUninitialized: false, secret: 'smith'})); +app.use(cookieSession({name: 'session', keys: ['key1']})) .... </pre> <li>Let's save the OAuth token, secret, and realmId. Go to line 78 or <code>app.js</code> and insert this code to persist these values across requests.</li> <pre> // Line 78 req.session.qbo = { token: accessToken.oauth_token, secret: accessToken.oauth_token_secret, companyid: postBody.oauth.realmId }; </pre> <li>We must make a way to call the Quickbooks initialization code a little easier and DRYer.</li> <li>Go to the end of <code>app.js</code> and make a global function to easily initialize the Quickbooks object from node-quickbooks.</li> <pre> var getQbo = function (args) { return new QuickBooks(consumerKey, consumerSecret, args.token, args.secret, args.companyid, true, // use the Sandbox true); // turn debugging on }; </pre> <li>Replace this similar code from the <code>/callback</code> method after the session save code in step 7.</li> <pre> qbo = getQbo(req.session.qbo); </pre> <li>Now modify the <code>/customer/:id</code> route to look like this:</li> <pre> app.get('/customer/:id', function (req, res) { console.log(req.session); var qbo = getQbo(req.session.qbo); qbo.getCustomer(req.params.id, function(err, customer) { console.log(customer); res.render('customer.ejs', { locals: { customer: customer }}) }) }) </pre> <li>Then modify the <code>views/customer.ejs</code> to output the <code>DisplayName</code>.</li> <pre> &lt;h1>Customer &lt;%= customer.DisplayName %>&lt;/h1> </pre> <li>Go back to the browser at <code>http://localhost:3000/start</code> and click on the "Connect to QuickBooks" button and run through OAuth again. The will save your OAuth token, secret, and realmId in sessions.</li> <li>After that run the <code>http://localhost:3000/customer/5</code> route.</li> <li>You should get:</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/153/qbo-node-display-name.png" /> <figcaption class="count">"Dukes Basketball Camp" is sandbox customer id 5.</figcaption> </figure> </ol> <h3>Conclusion</h3> <p>That's a wrap for part 2, in which I created a customer route that looked up and outputted a QuickBooks Online customer "DisplayName". Also, <a href="https://github.com/minimul/node-quickbooks/commit/97c4a2d48a2fa6947ba328083df6fa97da0f52e0" target="_blank">reference the code</a> for this <a href="/getting-started-with-nodejs-and-quickbooks-online-part-1.html">2-part</a> tutorial.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/getting-started-with-nodejs-and-quickbooks-online-part-1.html 2015-09-08T00:00:00Z <p>Getting started with Nodejs and QuickBooks Online Part 1</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/0f8Y0Y2sPS0?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>A Great tech stack and NPM package</h3> <p>Michael Cohen has written a full-featured NPM package for QuickBooks Online integration called <a href="https://github.com/mcohen01/node-quickbooks" target="_blank">node-quickbooks</a>. Nodejs is <a href="http://thenextweb.com/dd/2015/06/16/node-js-and-io-js-are-settling-their-differences-merging-back-together/">merging with Iojs</a> and will continue its march as a formidable tech stack. What's great about Node is its robust NPM ecosystem. Since Node is relatively new the NPM packages are fresh and generally constructed with modern best practices. In the case of node-quickbooks it has full support for JSON, <a href="https://intuitdeveloper.lc.intuit.com/replies/2664243">which Intuit has blessed as its primary data format</a> for API version 3. This is something of a challenge for other libraries such as <a href="https://github.com/ruckus/quickbooks-ruby" target="_blank">quickbooks-ruby</a> and <a href="https://github.com/consolibyte/quickbooks-php" target="_blank">quickbooks-php</a>, which are primary XML based. With all of those bonus points, let's investigate getting up and running with some basic integration.</p> <ol> <li>Clone either your fork or the main repo, switch into it, and make sure to run <code>npm install</code> in both the root and in the <code>example</code> directory.</li> <pre> $ git clone git@github.com:mcohen01/node-quickbooks.git $ cd node-quickbooks $ npm install $ cd example $ npm install </pre> <li>Run <code>tree -I "node_modules|build"</code></li> <pre> $ cd .. $ tree -I "node_modules|build" . ├── README.md ├── config.js <span class="inner-highlight">├── example │   ├── app.js │   ├── package.json │   └── views │   └── intuit.ejs ├── index.js </span>├── package.json └── test ├── batch.js ├── cdc.js ├── charge.js └── index.js </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>All of the integration code is in <code>index.js</code>. We are mostly going to be working within the <code>example</code> directory.</p> </div> <li>While still in the <code>example</code> directory fire up the example app. You can simply <code>node app.js</code> but do yourself a favor and use <a href="https://www.npmjs.com/package/nodemon" target="_blank"><code>nodemon</code></a> instead.</li> <pre> $ pwd $ ../node-quickbooks/example $ nodemon app.js [nodemon] v1.4.1 [nodemon] to restart at any time, enter `rs` [nodemon] watching: *.* [nodemon] starting `node app app.js` Express server listening on port 3000 </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/152/qbo-nodejs-start-route.png" /> <figcaption class="count">Go to <code>http://localhost:3000/start</code> and you will see a bare-bones page with the "Connect to QuickBooks" Button</figcaption> </figure> <li>Next open up <code>example/app.js</code> and notice the <code>/start</code> route</li> <pre> app.get('/start', function(req, res) { res.render('intuit.ejs', {locals: {port:port, appCenter: QuickBooks.APP_CENTER_BASE}}) }) </pre> <li>Right above that code are variables for <code>CONSUMER_KEY</code> and <code>CONSUMER_SECRET</code>. These values are from an "Intuit app". You must make an QuickBooks app with Intuit if you want to integrate with the Online API.</li> <li>Goto <a href="https://developer.intuit.com">https://developer.intuit.com</a>, sign up, and login.</li> <li>Once logged in hit the "Create a new app" button.</li> <li>Follow the steps and then grab the consumer key and secret.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/152/qbo-nodejs-dev-keys.png" /> <figcaption class="count">Click on the "Keys" tab to reveal the consumer key and secret.</figcaption> </figure> <li>Plug these values into <code>app.js</code></li> <pre> var consumerKey = 'aqerba34524sfasfdfafdadfasdf', consumerSecret = '52345adfa4qoxouagfaouasfdsy3422' // Or the more "proper" way is to pass in as ENV variables var consumerKey = process.env.MINIMULCASTS_CONSUMER_KEY, consumerSecret = process.env.MINIMULCASTS_CONSUMER_SECRET </pre> <li>Also open update <code>views/index.ejs</code> and turn off the payments API if you didn't enable it when you created the Intuit QuickBooks app.</li> <pre> intuit.ipp.anywhere.setup({ grantUrl: 'http://localhost:' + &lt;%= port %&gt; + '/requestToken', datasources: { quickbooks : true, // set to false if NOT using Quickbooks API <span class="inner-highlight">payments : false</span> } }); </pre> <li>Go back to <code>http://localhost:3000/start</code> and click on "Connect to QuickBooks".</li> <li>Finish the the 3-legged OAuth. </li> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>If you are prompted for a username and password supply your Intuit developer credentials created earlier. When you create an Intuit QuickBooks app you will automatically have a sandboxed QuickBooks Online client app created for your developer account, therefore, this is what you are going to be connecting to with the "Connect to QuickBooks" button.</p> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/152/qbo-nodejs-sandbox-link.png" /> <figcaption class="count">Click here to access your developer sandbox.</figcaption> </figure> </div> <li>After OAuth you will be returned the <code>/start</code> page.</li> <li>Go back to the console where you are running the <code>nodemon app.js</code> you should see the following output.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/152/qbo-nodejs-coa-names.png" /> <figcaption class="count">This output is the dev sandbox chart of account names.</figcaption> </figure> <li>You can see from <code>app.js</code> starting at line 78 what the actually find code is. </li> <pre> // test out account access qbo.findAccounts(function(_, accounts) { accounts.QueryResponse.Account.forEach(function(account) { console.log(account.Name) }) }) </pre> </ol> <h3>Conclusion</h3> <p>That'll do it for part 1. In <a href="/getting-started-with-nodejs-and-quickbooks-online-part-2.html">part 2</a> I will expand briefly on this by creating a customer route that will look up and output the QuickBooks Online customer "DisplayName". Also, <a href="https://github.com/minimul/node-quickbooks/commit/97c4a2d48a2fa6947ba328083df6fa97da0f52e0">reference the code</a> for this 2-part tutorial.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/how-i-test-against-the-quickbooks-online-api.html 2015-03-04T00:00:00Z <p>How I test against the QuickBooks Online API</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/SWiPrKBr4Fo?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>A better way to write your QuickBooks integration code</h3> <p>Are you just "winging it" with your production QuickBooks integration code? Pushing changes and hoping all goes well? Or perhaps you are a bit more righteous and manually test against the sandbox. Even then, why create 30 duplicate invoices when working on some simple QBO API error handling code? I am going to demonstrate in less than 20 minutes how to get your Rails app ready for testing against live QBO API interactions in an automated way. As a baseline I am going to start with my <a href="https://github.com/minimul/minimulcasts/tree/account" target="_blank">minimulcasts account branch</a>, which is a Rails 4.1 app. That said, most of this tutorial should should translate well for a Rails 3.2 app.</p> <ol> <li>Clone my <a href="https://github.com/minimul/minimulcasts/tree/account" target="_blank">minimulcasts account branch</a> and switch into it.</li> <pre> $ git clone -b account git@github.com:minimul/minimulcasts.git $ cd minimulcasts </pre> <li>Update the quickbooks-ruby gem.</li> <pre> $ bundle update quickbooks-ruby </pre> <li>Add these to the Gemfile next</li> <pre> group :development do gem 'spring' gem 'better_errors' gem 'spring-commands-rspec' end group :test do gem 'rspec-rails' gem 'factory_girl_rails' gem 'vcr' gem 'webmock' end </pre> <pre> $ bundle install </pre> <li>Next, enable spring and spring-rspec commands.</li> <pre> $ bundle exec spring binstub --all </pre> <li>Run the rspec install generator.</li> <pre> $ bin/rails g rspec:install </pre> <li>Get OAuth tokens from your <a href="https://developer.intuit.com" target="_blank">Intuit developer account</a>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/151/qbo-testing-get-oauth-tokens.png" /> <figcaption class="count">This is fastest way to get OAuth Creds.</figcaption> </figure> <li>Put those credentials into environmental shell variables.</li> <li>Open up <code>spec/rails_helper.rb</code> and modify it as such.</li> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=SWiPrKBr4Fo#t=6m47s" target="_blank">6:47 mark</a>. </div> <pre> require 'factory_girl_rails' require 'vcr' VCR.configure do |config| config.cassette_library_dir = Rails.root.join('spec', 'vcr') config.hook_into :webmock config.filter_sensitive_data('&lt;ACCESS_TOKEN>') { URI.encode_www_form_component(ENV['MINIMULCASTS_ACCESS_TOKEN']) } config.filter_sensitive_data('&lt;CONSUMER_KEY>') { URI.encode_www_form_component(ENV['MINIMULCASTS_CONSUMER_KEY']) } end </pre> <li>Also add FactoryGirl syntax methods within the RSpec configure block</li> <pre> RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" <span class="inner-highlight">config.include FactoryGirl::Syntax::Methods</span> </pre> <li>Create a <code>spec/factories.rb</code> and put in an account factory.</li> <pre> FactoryGirl.define do factory :account do name 'Tamar Trick' qb_token ENV['MINIMULCASTS_ACCESS_TOKEN'] qb_secret ENV['MINIMULCASTS_ACCESS_TOKEN_SECRET'] qb_company_id '1292740145' end end </pre> <li>Create a <code>spec/requests</code> directory with a spec called <code>qbo_spec.rb</code>.</li> <pre> require 'rails_helper' describe 'QBO requests' do it 'creates an invoice' do account = create(:account) base = Quickbooks::Base.new(account, :invoice) invoice = base.qr_model(:invoice) invoice.customer_id = 2 line_item = base.qr_model(:invoice_line_item) line_item.amount = 50 line_item.description = "Plush Baby Doll" line_item.sales_item! do |detail| detail.unit_price = 50 detail.quantity = 1 detail.item_id = 1 detail.tax_code_id = 'NON' end invoice.line_items &lt;&lt; line_item VCR.use_cassette("qbo/invoice/create", record: :all) do result = base.service.create(invoice) expect(result.id).to eq 159 end end end </pre> <li>We are ready to run the spec.</li> <pre> $ bin/rspec spec </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>The first run will take a bit of time as we have on <code>record: :all</code>, which is making a live request to the sandbox. This run will fail because I have hard coded the resulting id back from the API. I use this expectation so to verify that only the recorded cassette is being used when <code>record: :none</code> is invoked on the next run.</p> </div> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> This section is at the <a href="https://youtube.com/watch?v=SWiPrKBr4Fo#t=19m02s" target="_blank">19:02 mark</a>. </div> <li>After the successful run you can grab the returned ID and stick in the expectation. Then change the record option to <code>:none</code> and re-run the spec.</li> <pre> VCR.use_cassette("qbo/invoice/create", record: <span class="inner-highlight">:none</span>) do result = base.service.create(invoice) expect(result.id).to eq <span class="inner-highlight">Put in the returned ID you get from the previous run.</span> end </pre> <li>Run the spec again and you will notice how fast it runs as it is using the recorded cassette from VCR.</li> <li>Finally, take a look at the recorded cassette at <code>spec/vcr/qbo/invoice/create.yml</code>.</li> </ol> <h3>Conclusion</h3> <p>Not so bad, eh? Now you can develop integration code in a more assured fashion. This approach can also aid you when working on a PR for the quickbooks-ruby gem as you can take the XML from the cassette and put into a fixture. The code for this tutorial can be found <a href="https://github.com/minimul/minimulcasts/commits/spec" target="_blank">on Github</a>.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/encoding-a-quickbooks-qbxml-request.html 2015-02-05T00:00:00Z <p>Encoding a QuickBooks QBXML Request</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/RA-FRqBa1FU?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>In short: QBSDK doesn't like non ASCII characters</h3> <p>Are you receiving a <code>QuickBooks found an error when parsing the provided XML text stream</code> and are not missing required tags, tags are in the exact order, tags are all closed, and XML attributes are properly escaped? Then you probably have a non-ASCII character in your request.</p> <p>This encoding is built-in to my <code><a href="https://github.com/minimul/to_qbxml" target="_blank">to_qbxml</a></code> gem but if you are using just using Nokogiri you can simply use <code>.to_xml(encoding: 'US-ASCII')</code> to get the proper encoding. If you aren't using Ruby you can use a standard HTML entities library with decimal encoding to get special characters into QuickBooks via the SDK.</p> Christian Pelczarski https://minimul.com/quickbooks-v3-api-error-java-lang-numberformatexception-for-blank-node.html 2014-12-31T00:00:00Z <p>QuickBooks v3 API error: An application error has occurred while processing your request: System Failure Error: java.lang.NumberFormatException: For input string: ""</p> <h4>Problem:</h4> <p>In this particular case, an invoice had a line item with just a <code>&lt;ItemId /&gt;</code>. If you include the ItemId node within an invoice it must be filled in with a value or you need to leave it off completely. If you are getting this error on another transaction or name entity then look for blank nodes.</p> Christian Pelczarski https://minimul.com/quickbooks-web-connector-and-subdomains.html 2014-12-29T00:00:00Z <p>QuickBooks Web Connector and subdomains</p> <p>You <a href="http://wiki.consolibyte.com/wiki/doku.php/quickbooks_web_connector#do_you_really_need_an_ssl_certificate" target="_blank">don't have to use SSL/TLS (HTTPS)</a> when testing against the QuickBooks Web Connector. You can just use something like <code>http://localhost</code> or basically some URL with localhost at the start e.g. <code>http://localhost-qbsdk</code> or if a subdomain like this <code>http://localhost.localhost</code> but this <code>http://demo.localhost</code> will not work. Again, it must have <code>localhost</code> right after the <code>http://</code>.</p> Christian Pelczarski https://minimul.com/connect-rails-and-quickbooks-online-via-a-sandbox.html 2014-11-15T00:00:00Z <p>Connect Rails and QuickBooks Online via Sandbox</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/hel3f__8XeM?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>A better way: Sandboxes</h3> <p>In my series on <a href="/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-1.html" target="_blank">wiring up Rails and QBO</a> you needed to have a real QBO account to test against. Intuit was good about giving free trial accounts but that process has been eliminated with regards to U.S. based QuickBooks Online. In its place is the much more proper concept of a <a href="https://developer.intuit.com/v2/blog/2014/10/24/intuit-developer-now-offers-quickbooks-sandboxes/" target="_blank">sandbox</a>. In this screencast, I will upgrade the "Minimulcasts" app to use a sandbox, therefore, see this as an amendment to <a href="/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-1.html" target="_blank">part 1</a> of the original series.</p> <h3>tl;dw</h3> <ol> <li>To create a sandbox, go to <a href="https://developer.intuit.com" target="_blank">https://developer.intuit.com</a> and sign in. At the bottom of the page, click on the link for setting up a sandbox.</li> <li>Upgrade Rails and the Quickbooks-Ruby Gem</li> <pre> # previous : gem 'rails', '4.0.2' gem 'rails', '4.1.6' # previous : gem 'quickbooks-ruby', git: 'git://github.com/ruckus/quickbooks-ruby.git' gem 'quickbooks-ruby', github: 'ruckus/quickbooks-ruby' </pre> <li>Bundle update Rails and Quickbooks-Ruby Gem</li> <pre> $ bundle update rails $ bundle update quickbooks-ruby </pre> <li>Enable Sandbox mode in <code>config/initializers/quickbooks.rb</code> and use shell variables.</li> <pre> # previous : QB_KEY = "<insert>" # previous : QB_SECRET = "<insert>" QB_KEY = ENV['MINIMULCASTS_CONSUMER_KEY'] QB_SECRET = ENV['MINIMULCASTS_CONSUMER_SECRET'] # add to the end of file Quickbooks.sandbox_mode = true </pre> <li>Restart pow</li> <pre> $ touch tmp/restart.txt </pre> <li>Go to <code>http://minimulcasts.dev</code></li> <li>Click on the "Connect to Intuit button" and authorize your sandbox for usage.</li> <li>To test, make a new vendor. Don't update an existing vendor as that will not work because it doesn't exist in this new sandbox.</li> <li>Vendor should show up in your sandbox. (make sure you refresh the Vendor's link on the left side).</li> </ol> <ul> <li>Remember the check the screencast for more details as well as the <a href="https://github.com/minimul/minimulcasts" target="_blank">repo</a>.</li> </ul> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/connect-to-quickbooks-angular-directive.html 2014-11-03T00:00:00Z <p>Connect to QuickBooks Angular directive</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/OAIVD13s590?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>Simple Connect button rendering</h3> <p>I was attempting to answer <a href="http://stackoverflow.com/questions/26591124/integration-quickbooks-online-button-in-angularjs" target="_blank">this SO question</a> which referenced <a href="https://gist.github.com/ahey/14f75186674a5fdac318" target="_blank">this angular directive</a> for rendering a "Connect to QuickBooks" button. I fired up the gulp-angular generator but couldn't get that directive to work and in the process was was able to come up with a simpler approach.</p> <ol> <li>Install the <a href="https://github.com/Swiip/generator-gulp-angular" target="_blank">gulp-angular</a> Yeoman generator, make a new dir, and run the generator.</li> <pre> $ npm install -g generator-gulp-angular $ cd ~/github/labs $ mkdir connect-intuit-angular && cd $_ $ yo gulp-angular </pre> <li>Install no additional items when prompted by the Yeoman wizard.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular.png" /> <figcaption class="count">Barebones setup</figcaption> </figure> <li>Oops. The bower install failed.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular-bower-error.png" /> <figcaption class="count">Bower install crashes with EMALFORMED in bower.json</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular-bower-error-fix.png" /> <figcaption class="count">Take out the comma to fix.</figcaption> </figure> <li>Re-run <code>bower install</code>.</li> <li>Test the generator doing a <code>gulp serve</code>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular-gulp-serve.png" /> <figcaption class="count">Ok, the generator scaffolding is working.</figcaption> </figure> <li>Let's clean up some of the scaffolding stuff. Remove the <code>awesomeThings</code> array in <code>src/app/main/main.controller.js</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular-clean-up-1.png" /> <figcaption class="count">Remove awesomeThings array in controller.</figcaption> </figure> <li>Then remove the corresponding <code>ng-repeat</code> in <code>src/index.html</code>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular-clean-up-2.png" /> <figcaption class="count">Clean up the view as well.</figcaption> </figure> <li>Make a new file <code>src/app/main/directives.js</code> and add the following.</li> <pre> /* src/app/main/directives.js */ angular.module('connectIntuitAngular') .directive('connectToQuickbooks', function($window){ return { restrict: 'E', template: "&lt;ipp:connectToIntuit>&lt;/ipp:connectToIntuit>", link: function(scope) { var script = $window.document.createElement("script"); script.type = "text/javascript"; script.src = "//js.appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js"; script.onload = function () { scope.$emit('intuitjs:loaded'); }; $window.document.body.appendChild(script); scope.$on('intuitjs:loaded', function (evt) { $window.intuit.ipp.anywhere.setup({ grantUrl: '/' }); scope.connected = true; scope.$apply(); }); } } }); </pre> <li>Then in <code>src/index.html</code> do:</li> <pre> &lt;!-- index.html --> &lt;div class="jumbotron" ng-init="connected = false"> &lt;div class="control-group"> &lt;div class="controls" ng-show="!connected"> Loading QuickBooks Connect button &lt;/div> &lt;div class="controls" ng-show="connected"> &lt;connect-to-quickbooks /> &lt;/div> &lt;/div> &lt;/div> </pre> <li>Let's check it out.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/140/quickbooks-connect-angular-button-1.png" /> <figcaption class="count">Connect to QuickBooks button is ready to go.</figcaption> </figure> </ol> <h3>Handling external Javascript</h3> <p>There a few other ways to handle external Javascript dependencies in Angular but I like using the onload attribute in combination with Angular events as it is simple and easy to follow. In this refactoring I was able to save quite a few lines of code while improving read-ability.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/bare-bones-quickbooks-single-sign-on-with-rails.html 2014-10-27T00:00:00Z <p>Bare-bones QuickBooks Single Sign On with Rails</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/4FZnmmrkmfY?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <h3>Fundamental Rails/QuickBooks Single Sign On (SSO)</h3> <p>In my <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-4-implementing-single-sign-on.html" target="_blank">previous tutorial</a> I went over implementing SSO with Rails and Devise. This tutorial will focus on a basic implementation featuring just Rails (4.1.6) with subdomains</p> <p></p> <ol> <li>Fire up a new rails app</li> <pre> $ rails new intuit_sso -T $ cd intuit_sso </pre> <li>Add these to the Gemfile next</li> <pre> gem "omniauth", ">= 1.0.3" gem 'omniauth-openid' </pre> <pre> bundle install </pre> <li>The <code>config/initializers/omniauth.rb</code> should look like this.</li> <pre> Rails.application.config.middleware.use OmniAuth::Builder do provider :open_id, :name => 'intuit', :identifier => 'https://openid.intuit.com/openid/xrds' end </pre> <li>While in the initializers edit the <code>config/initializers/cookies_serializer.rb</code></li> <pre> Rails.application.config.action_dispatch.cookies_serializer = :marshal </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>It seems one of the core underlying gems, <code>ruby-openid</code>, is not playing nicely with the Rails 4.1 default of <code>Rails.application.config.action_dispatch.cookies_serializer = :json</code> </p> </div> <li>Make a Login controller with 2 actions, <code>index</code> and <code>callback</code>.</li> <pre> $ bin/rails g controller Login index callback </pre> <li>In <code>app/views/login/index.html.erb</code> put the Intuit SSO button.</li> <pre> &lt;h1>Login#index&lt;/h1> &lt;ipp:login href="/auth/intuit" type="horizontal">&lt;/ipp:login> &lt;script type="text/javascript" src="https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js">&lt;/script> </pre> <li>Add these routes.</li> <pre> match '/auth/:provider/callback' => 'login#callback', via: [:get, :post] root 'login#index', :constraints => { :subdomain => /.+/ } </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>Just a reminder: Make sure you add both :get and :post for the callback.</p> </div> <li>Before we start working on the callback let's make a user.</li> <pre> $ bin/rails g model User email password </pre> <li>The <code>app/models/user.rb</code> should look like this:</li> <pre> class User < ActiveRecord::Base def self.find_for_open_id(access_token) data = access_token.info if user = User.where(:email => data["email"]).first user else User.create!(email: data["email"], password: generate_token) end end def self.generate_token Digest::SHA1.hexdigest("#{Rails.application.config.secret_token}#{Time.now.to_i}") end end </pre> <li>Now we can implement the callback action within <code>app/controllers/login_controller.rb</code> as so:</li> <pre> class LoginController < ApplicationController skip_before_filter :verify_authenticity_token, :only => [:callback] def index end def callback if user = User.find_for_open_id(request.env["omniauth.auth"]) session[:user_id] = user.id redirect_to start_index_url else redirect_to root_url, notice: 'Auth fail' end end end </pre> <div class="alert green margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>If you like to spin your wheels a bit and waste an hour or so go ahead and NOT skip the verify_authenticity_token.</p> </div> <li>You may have noticed a redirect to a non-existent route and controller, <code>start_index_url</code>, so let's implement that now.</li> <pre> $ bin/rails g controller Start index </pre> <li>In conjunction, let's fill in the route, controller, and view for this start redirect.</li> <pre> # config/routes.rb get 'start/index', :constraints => { :subdomain => /.+/ } </pre> <pre> # app/controllers/start_controller.rb class StartController < ApplicationController def index @current_user = User.find(session[:user_id]) end end </pre> <pre> <!-- app/views/start/index.html.erb --> &lt;h1>Start#index&lt;/h1> &lt;h2>&lt;%= "Welcome #{@current_user.email}" %>&lt;/h2> &lt;p>Find me in app/views/start/index.html.erb&lt;/p> </pre> <li>Now we are ready to test.</li> <pre> $ bin/rails s </pre> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/139/quickbooks-single-sign-on-login.png" /> <figcaption class="count">Pump in a subdomain with <code>lvh.me:3000</code> and click "Sign in with Intuit".</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/139/quickbooks-single-sign-on-intuit-login.png" /> <figcaption class="count">Fill in your QBO creds.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/139/quickbooks-single-sign-on-completed.png" /> <figcaption class="count">Successful sign on.</figcaption> </figure> </ol> <h3>That concludes the tutorial for <i>Bare-bones QuickBooks Single Sign On with Rails</i></h3> <p>Be sure to check out the screencast for more commentary as well as the <a href="https://github.com/minimul/intuit_sso" target="_blank">code</a>. For feedback use the comments area below rather than my email. If the question is broader than this tutorial use the <a href="https://intuitpartnerplatform.lc.intuit.com" target="_blank">Intuit Developer Forums</a>, in which I regularly hang out.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/easily-turn-a-ruby-hash-to-quickbooks-xml.html 2014-10-07T00:00:00Z <p>Easily turn a Ruby Hash to QuickBooks XML</p> <h3>&nbsp;</h3> <p>I recently released a new Gem called <a href="https://github.com/minimul/to_qbxml" target="_blank"><code>to_qbxml</code></a>. I created it in response to the current de facto Ruby Gem, <a href="" target="_blank">Qbxml</a>. Let me state right off the bat that I used much of that Gem's code for <code>to_qbxml</code> and want to thank the author, <a href="http://www.skryl.org/" target="_blank">Alexsey</a>, for all his OSS QuickBooks contributions.</p> <p>Here are some of the issues I had with the <code>Qbxml</code> Gem:</p> <ol> <li>QBXML requires repeatable nodes such as the <code>InvoiceLineAdd</code> node in the sample below but I had no idea how to do this with the <code>Qbxml</code> Gem.</li> <pre> &lt;?xml version="1.0" encoding="utf-8"?> &lt;?qbxml version="7.0"?> &lt;QBXML> &lt;QBXMLMsgsRq onError="stopOnError"> &lt;InvoiceAddRq requestID="1"> &lt;InvoiceAdd> &lt;InvoiceLineAdd> &lt;!-- omitted --> &lt;/InvoiceLineAdd> &lt;InvoiceLineAdd> &lt;!-- omitted --> &lt;/InvoiceLineAdd> &lt;/InvoiceAdd> &lt;/InvoiceAddRq> &lt;/QBXMLMsgsRq> &lt;/QBXML> </pre> I am sure that <code>Qbxml</code> Gem does support this as it is a crucial part of creating valid QBXML but instead I created a solution that uses a reserved hash key, <code>repeat</code>, that can be passed an array of hashes to create the above XML. <pre> # ---- # to_qbxml gem example of creating repeatable nodes # ---- def line_item(line_number) { invoice_line_add: { item_ref: { list_id: '3243' }, desc: "Line #{line_number}", amount: 10.99, is_taxable: true, quantity: 3, rate_percent: 0, repeat: [{ line: { desc: 'inside' } }] } } end hash = { repeat: [ line_item(1), line_item(2) ] } xml = ToQbxml.new(hash).make(:invoice) </pre> <li>Reliance on ActiveSupport::Inflector to handle QBXML case conversions</li> Since Inflector is <a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/dependencies/autoload.rb#L38" target="_blank">used within Rails</a> autoload this can adversely effect autoloading when included into an existing Rails app. Instead, <code>to_qbxml</code> removes that dependency and uses a simple regular expression to achieve the same result. <li>Built-in validation is not needed.</li> In my experience, it is best to use the <a href="http://developer-static.intuit.com/qbsdk-current/common/newosr/index.html" target="_blank">OCR docs</a> and the <a href="https://developer.intuit.com/docs/0250_qb/0010_get_oriented/0060_sdk_components" target="_blank">QBXML validator and SDKTest utilities</a> that come bundled with the standard QBSDK installation. <li> Turning QBXML into a Ruby hash is not useful when it comes to reading QBXML </li> Instead use the rich parsing and searching of <a href="http://nokogiri.org" target="_blank">Nokogiri</a>. With <code>to_qbxml</code> you can return a Nokogiri document back to by passing <code>doc: true</code> into the initialization options. Below is an example of using Nokogiri's rich CSS selector support to parse QBXML. <pre> n = ToQbxml.new(hash, doc: true).make(:invoice, action: :query) expect(n.at('InvoiceQueryRq > IncludeLineItems').content).to eq 'true' expect(n.at('InvoiceQueryRq > ModifiedDateRangeFilter > FromModifiedDate').content).to eq hash[:modified_date_range_filter][:from_modified_date] expect(n.at('InvoiceQueryRq > ModifiedDateRangeFilter > ToModifiedDate').content).to eq hash[:modified_date_range_filter][:to_modified_date] end </pre> </ol> <h3>Hence a new Gem is born</h3> <p>Therefore, I ripped out these and some other "features" to create a lightweight and flexible but yet powerful Ruby Hash to QBXML library. Enjoy, and check out the screencast for more commentary.</p> <p><br/></p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/4Fw-ouYa1hg?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/improve-your-quickbooks-ruby-integration-experience-with-the-quickbooks-ruby-base-gem.html 2014-04-21T00:00:00Z <p>Improve your QuickBooks/Ruby integration experience with the quickbooks-ruby-base gem</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/2nX-BkiPG9s?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> Code from screencast and tutorial is <a href="https://github.com/minimul/minimulcasts#account" target="_blank">available</a> on Github. </div> <h3>Real-world to gem</h3> <p>I was recently able to wrap up a pattern from my QuickBooks/Ruby consulting into a gem. The <a href="https://github.com/minimul/quickbooks-ruby-base" target="_blank">gem</a> complements and extends the <a href="https://github.com/ruckus/quickbooks-ruby" target="_blank">quickbooks-ruby</a> gem. Let's see how. <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-github-page.png" /> <figcaption class="count">Speed up QB integration.</figcaption> </figure></p> <ol> <li>First, install the gem along with <code>quickbooks-ruby</code>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-install.png" /> <figcaption class="count">Don't forget to <code>bundle install</code>.</figcaption> </figure> <li>Now you need to configure the persistent locations of the Intuit OAuth connection.</li> <ul> <li>For example, if you are storing the token, secret, and company_id like this:</li> <li> <pre> account = Account.find(1) account.qb_token account.qb_secret account.company_id </pre> </li> <li>Your config file might look like this.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-config.png" /> <figcaption class="count">This example comes from <code>config/initializers/quickbooks.rb</code>.</figcaption> </figure> </ul> <li>Next, I am going fire up the <code>rails console</code> to further demonstrate.</li> <li>To create a <code>quickbooks-ruby</code> service takes only one line of code.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-service.png" /> <figcaption class="count">The <code>service</code> method is available off the base object so you can quickly access <code>query</code>, <code>create</code>, and <code>update</code>.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-service-save.png" /> <figcaption class="count">This one line saves 4 lines of code and is generic.</figcaption> </figure> <li>There is a <code>qr_model</code> method that generates and initializes a quickbooks-ruby model, which are long to type out by hand.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-qr-model-before.png" /> <figcaption class="count">Before</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-qr-model-after.png" /> <figcaption class="count">After</figcaption> </figure> <li>You can pass as many arguments to the model as it takes.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-qr-model-args.png" /> <figcaption class="count">This is an example of the <code>email_address</code> model.</figcaption> </figure> <li>Lastly, the <code>show</code> method will return a "smart" array, customers in this example.</li> <ul> <li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/118/quickbooks-rails-base-gem-show.png" /> <figcaption class="count">"Smart" because the <code>DESC</code> value returned adapts to which service is being shown. For example, if its the invoice service calling show, then the <code>doc_number</code> is displayed for <code>DESC</code> (for customer service its <code>display_name</code>).</figcaption> </figure> </li> <li>The show method saves you from having type the following below and it is most useful for console usage.</li> <pre> $ base.service.query.entities.map{ |e| "ID: #{e.id} DESC: #{e.display_name}" } </pre> <li>You can also pass the same options you would to the <code>quickbooks-ruby</code> query method e.g.</li> <pre> $ base.show(page: 1, per_page: 150) </pre> <div class="alert green margin-top"> <i class="icon-thumbs-up icon-2x pull-left icon-border"></i> The <code>show</code> alone method is worth installing the gem. </div> </ul> </ol> <h3>Contributing</h3> <p>I want to keep the gem light weight but I would love to see pull requests coming from your QuickBooks/Ruby integration experiences. You can see the making of this gem in this <a href="/create-a-ruby-gem-real-world-play-by-play-part-1.html" target="_blank">4 part series</a>.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-3.html 2014-02-07T00:00:00Z <p>Integrating Rails and QuickBooks Online via the version 3 API: Part 3</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" type="text/html" style="display: none;" src="https://youtube.com/embed/RDrIHBtn5lM?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> Tutorial and screencast use <code>Rails 4</code>, <code>Ruby 1.9.3</code>, and <code>Pow</code>. Be sure to watch the screencast above as I unveil more details and tidbits than can be found in the article itself. </div> <p> <li>Code from screencast is <a href="https://github.com/minimul/minimulcasts/tree/part-3" target="_blank">available</a> on Github</li></p> <ol class="no-indent li-margin-top"> <li>Continuing with <a href="/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-2.html" target="_blank">Part 2</a>, let's change the email address to test the update functionality. Go back to the browser and click 'Edit'.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/110/quickbooks-rails-edit-vendor-link.png" /> <figcaption class="count">Stop at the edit view so we can add some code to the vendors controller.</figcaption> </figure> <li>Add the following to the <code>update</code> action within the <code>vendors_controller</code>.</li> <pre> def update respond_to do |format| if @vendor.update(vendor_params) <span class="inner-highlight">vendor = @vendor_service.query.entries.find{ |e| e.given_name == vendor_params[:name] } # If you have more than 20 Vendor's use this # vendor = @vendor_service.query("SELECT * FROM VENDOR WHERE GivenName = '#{vendor_params[:name]}'").entries.first vendor.email_address = vendor_params[:email_address] @vendor_service.update(vendor)</span> format.html { redirect_to @vendor, notice: 'Vendor was successfully updated.' } format.json { head :no_content } else format.html { render action: 'edit' } format.json { render json: @vendor.errors, status: :unprocessable_entity } end end end </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> The <code>query.entries</code> line is searching over your QuickBooks vendors that match the submitted name and then returning the vendor. </div> <li>Go back to the browser and alter the email address.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/110/quickbooks-rails-edit-vendor-submit.png" /> <figcaption class="count">Modify the email and click 'Update Vendor'.</figcaption> </figure> <li>Go back to QuickBooks Online and ascertain that the update was successful.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/110/quickbooks-rails-edit-vendor-qbo.png" /> <figcaption class="count">Go to the detail view to see if the email address was updated.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/110/quickbooks-rails-edit-vendor-qbo-detail.png" /> <figcaption class="count">The email address update worked!</figcaption> </figure> <li>Now that you can connect your Rails app to QuickBooks Online and create and update objects, let me demonstrate how to use logging to examine API interaction more closely.</li> <li>Add the following right above the update action in the <code>vendors_controller.rb</code>.</li> <pre> def update <span class="inner-highlight">Quickbooks.logger = Rails.logger Quickbooks.log = true</span> vendor = @vendor_service.query.entries.find{ |e| e.given_name == vendor_params[:name] } vendor.email_address = vendor_params[:email_address] @vendor_service.update(vendor) # ..omitted </pre> <li>Change the email address again and hit 'Update Vendor'.</li> <li>Open the <code>log/development.log</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/110/quickbooks-rails-dev-log.png" /> <figcaption class="count">The <code>quickbooks-ruby</code> logging is demarcated with <code>------ New Request ------</code></figcaption> </figure> </ol> <h3>Stay tuned</h3> <p>Now you should be pretty dangerous when tackling Rails and QBO integration using the version 3 API. I do have more articles and screencasts coming so be sure to sign up for my newsletter.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-2.html 2014-02-05T00:00:00Z <p>Integrating Rails and QuickBooks Online via the version 3 API: Part 2</p> <p>&nbsp;</p> <p class="video-container"> <iframe class="youtube-player" style="display: none;" type="text/html" src="https://youtube.com/embed/jqM1e_Z-Y8U?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> Tutorial and screencast use <code>Rails 4</code>, <code>Ruby 1.9.3</code>, and <code>Pow</code>. Be sure to watch the screencast above as I unveil more details and tidbits than can be found in the article itself. </div> <p> <li>Code from screencast is <a href="https://github.com/minimul/minimulcasts/tree/part-2" target="_blank">available</a> on Github</li></p> <ol class="no-indent li-margin-top"> <li>Continuing with <a href="/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-1.html" target="_blank">Part 1</a>, add an <code>email_address</code> attribute to the Vendor.</li> <pre> $ rails g migration add_email_address_to_vendors email_address $ bundle exec rake db:migrate </pre> <li>Update the views to reflect adding <code>email_address</code>.</li> <ul> <li><code>app/views/vendors/_form.html.erb</code></li> <pre> ... omitted <span class="inner-highlight">&lt;div class="field"> &lt;%= f.label :email_address %>&lt;br> &lt;%= f.email_field :email_address %> &lt;/div></span> ... omitted </pre> <li><code>app/views/vendors/index.html.erb</code></li> <pre> ... omitted &lt;tr> &lt;th>Name&lt;/th> <span class="inner-highlight">&lt;th>Email address&lt;/th></span> &lt;th>&lt;/th> &lt;th>&lt;/th> &lt;/tr> &lt;/thead> &lt;tbody> &lt;% @vendors.each do |vendor| %> &lt;tr> ... omitted <span class="inner-highlight">&lt;td>&lt;%= vendor.email_address %>&lt;/td></span> &lt;td>&lt;%= link_to 'Show', vendor %>&lt;/td> &lt;td>&lt;%= link_to 'Edit', edit_vendor_path(vendor) %>&lt;/td> &lt;td>&lt;%= link_to 'Destroy', vendor, method: :delete, data: { confirm: 'Are you sure?' } %>&lt;/td> &lt;/tr> &lt;% end %> ... omitted </pre> <li><code>app/views/vendors/show.html.erb</code></li> <pre> ... omitted <span class="inner-highlight">&lt;p> &lt;strong>Email address:&lt;/strong> &lt;%= @vendor.email_address %> &lt;/p></span> ... omitted </pre> </ul> <li>Reload browser to test that the views have been updated.</li> <li>Create a new vendor on Rails and QuickBooks Online.</li> <ul> <li>Create a <code>before_action</code> filter within <code>app/controllers/vendors_controller.rb</code> to activate the vendor service.</li> <pre> class VendorsController < ApplicationController before_action :set_vendor, only: [:show, :edit, :update, :destroy] <span class="inner-highlight">before_action :set_qb_service, only: [:create, :edit, :update, :destroy]</span> #... omitted private #... omitted <span class="inner-highlight">def set_qb_service oauth_client = OAuth::AccessToken.new($qb_oauth_consumer, session[:token], session[:secret]) @vendor_service = Quickbooks::Service::Vendor.new @vendor_service.access_token = oauth_client @vendor_service.company_id = session[:realm_id] end</span> end </pre> <li>Whitelist the <code>email_address</code> attribute within the <code>vendors_controller</code>.</li> <pre> def vendor_params params.require(:vendor).permit(:name, <span class="inner-highlight">:email_address</span>) end </pre> <li>Alter the <code>create</code> action within the <code>vendors_controller</code>.</li> <pre> # .. omitted def create @vendor = Vendor.new(vendor_params) <span class="inner-highlight">vendor = Quickbooks::Model::Vendor.new vendor.given_name = vendor_params[:name] vendor.email_address = vendor_params[:email_address] @vendor_service.create(vendor)</span> respond_to do |format| # .. omitted end </pre> <div class="alert margin-top"> <i class="icon-coffee icon-2x pull-left icon-border"></i> For a “real app” you should have the <code>quickbooks-ruby</code> code always handy to guide you in mapping your vendors (or customers, employees, etc.). In this example, use the <a href="https://github.com/ruckus/quickbooks-ruby/blob/master/lib/quickbooks/model/vendor.rb" target="_blank">quickbooks-ruby</a> vendor model code as a reference. </div> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/109/quickbooks-ruby-rails-docs-model.png" /> <figcaption class="count">To see all available Vendor attribute mappings, reference the <a href="https://github.com/ruckus/quickbooks-ruby/blob/master/lib/quickbooks/model/vendor.rb" target="_blank">gem code itself</a>.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/109/quickbooks-ruby-rails-docs-model-mixins.png" /> <figcaption class="count">Also be aware that many models have mixed-in methods, so check for <code>include</code> keywords. For example, the vendor model draws methods from the <a href="https://github.com/ruckus/quickbooks-ruby/blob/master/lib/quickbooks/util/name_entity.rb" target="_blank">NameEntity</a> module.</figcaption> </figure> </ul> <li>Reload browser and then click <code>New vendor</code> link.</code> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/109/quickbooks-ruby-rails-create-a-new-vendor-fill-in.png" /> <figcaption class="count">Fill in form and click 'Create Vendor'.</figcaption> </figure> <li>Go over to your QuickBooks Online account and check if the vendor was composed.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/109/quickbooks-ruby-rails-create-a-new-vendor-check.png" /> <figcaption class="count">Goto 'Company' tab, then to 'Activity Log' submenu. Next, click on the vendor's name within the activity tab to get more details.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/109/quickbooks-ruby-rails-create-a-new-vendor-check-email.png" /> <figcaption class="count">Looking good.</figcaption> </figure> </ol> <h3>Next, updating a vendor in Rails and QuickBooks Online</h3> <p>In <a href="/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-3.html" target="_blank">Part 3</a> I demonstrate updating an entity at QBO. I also exhibit basic debugging using the logging feature of <code>quickbooks-ruby</code>.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-1.html 2014-02-03T00:00:00Z <p>Integrating Rails and QuickBooks Online via the version 3 API: Part 1</p> <p>&nbsp;</p> <div class="alert red margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>IMPORTANT: Intuit has introduced and requires developing against sandboxes for U.S. QBO.</p> <p>See my new article, <a href="/connect-rails-and-quickbooks-online-via-a-sandbox.html" target="_blank">Connect Rails and QuickBooks Online via Sandbox</a>, which would come in around step 4 or so for this article.</p> </div> <p class="video-container"> <iframe class="youtube-player" type="text/html" style="display: none;" src="https://youtube.com/embed/XCXQmFm0bFk?wmode=transparent" frameborder="0"></iframe> </p> <p><br/></p> <div class="alert yellow margin-top"> <i class="icon-film icon-2x pull-left icon-border"></i> Tutorial and screencast use <code>Rails 4</code>, <code>Ruby 1.9.3</code>, and <code>Pow</code>. Be sure to watch the screencast above as I unveil more details and tidbits than can be found in the article itself. </div> <p> <li>Code from screencast is <a href="https://github.com/minimul/minimulcasts/tree/part-1" target="_blank">available</a> on Github</li></p> <ol class="no-indent li-margin-top"> <li>If you haven't already, sign up at <a href="https://developer.intuit.com/" target="_blank">Intuit Partner Platform</a></li> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> If you already have a QBO account you can use your username and password to join as a developer. If you are a developer desiring a test account then email Intuit @ <code>ippdevelopersupport@intuit.com</code> and request a developer subscription to QBO. </div> <li>Create a new app.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-create-app.png" /> <figcaption class="count">Select 'Quickbooks API'.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-create-app-form.png" /> <figcaption class="count outline">Fill in the form like in this figure and "Save". Note: the minimulcasts.io is provided because a real domain is needed to get past the validations.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-create-app-tokens.png" /> <figcaption class="count">Next, your app tokens and keys are generated.</figcaption> </figure> <li>Create a new Rails app and hook it up with Pow.</li> <pre> $ cd ~/www/labs # example $ rails new minimulcasts $ ln -s ~/www/labs/minimulcasts ~/.pow $ curl -L minimulcasts.dev | grep "Welcome" # Test POW $ cd minimulcasts </pre> <li>Add the <a href="https://github.com/ruckus/quickbooks-ruby" target="_blank">quickbooks-ruby</a> and <a href="https://github.com/pelle/oauth-plugin" target="_blank">oauth-plugin</a> gems.</li> <pre> # Gemfile -> add these two gems: gem 'quickbooks-ruby' gem 'oauth-plugin' $ bundle install </pre> <li>Beget Vendor scaffolding</li> <pre> $ rails g scaffold Vendor name $ bundle exec rake db:migrate </pre> <li>Create <code>config/initializers/quickeebooks.rb</code>.</li> <pre> QB_KEY = "&lt;copy from developer.intuit.com>" QB_SECRET = "&lt;copy from developer.intuit.com>" $qb_oauth_consumer = OAuth::Consumer.new(QB_KEY, QB_SECRET, { :site => "https://oauth.intuit.com", :request_token_path => "/oauth/v1/get_request_token", :authorize_url => "https://appcenter.intuit.com/Connect/Begin", :access_token_path => "/oauth/v1/get_access_token" }) </pre> <li>Modify <code>config/routes.rb</code>.</li> <pre> Minimulcasts::Application.routes.draw do resources :vendors do collection do get :authenticate get :oauth_callback end end root to: 'vendors#index' end </pre> <li>Make sure application level changes take by reloading with a <code>touch tmp/restart.txt</code></li> <li>Next, hook actions up to those new routes <code>app/controllers/vendors_controller.rb</code>.</li> <div class="alert red margin-top"> <i class="icon-bolt icon-2x pull-left icon-border"></i> <div class="minimul-says"> Minimul says &mdash; </div> <p>Pay attention to the Rails 4.1 and greater notes in the next 2 methods.</p> </div> <pre> # app/controllers/vendors_controller.rb # .. code omitted def authenticate callback = oauth_callback_vendors_url token = $qb_oauth_consumer.get_request_token(:oauth_callback => callback) session[:qb_request_token] = token # If Rails >= 4.1 you need to do this => session[:qb_request_token] = Marshal.dump(token) redirect_to("https://appcenter.intuit.com/Connect/Begin?oauth_token=#{token.token}") and return end def oauth_callback at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier]) # If Rails >= 4.1 you need to do this => at = Marshal.load(session[:qb_request_token]).get_access_token(:oauth_verifier => params[:oauth_verifier]) session[:token] = at.token session[:secret] = at.secret session[:realm_id] = params['realmId'] redirect_to root_url, notice: "Your QuickBooks account has been successfully linked." end private # .. code omitted </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> You will persist the token, secret & realmId to the database. Those 3 items are needed to communicate with QBO. For brevity, sessions are used for persistence. </div> <li>Add Intuit Connect button to application layout at <code>app/view/layouts/application.html.erb</code>.</li> <pre> .. omitted &lt;body> &lt;!-- REPLACE THE BODY of the default application.html.erb with this --> &lt;% unless session[:token] %> &lt;ipp:connectToIntuit>&lt;/ipp:connectToIntuit> &lt;% end %> &lt;% if notice %> &lt;div style="padding: 10px;background: gainsboro;font-weight: 900;width: 50%;">&lt;%= notice %>&lt;/div> &lt;% end %> &lt;%= yield %> &lt;script type="text/javascript" src="https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js">&lt;/script> &lt;script> intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '&lt;%= authenticate_vendors_url %>'}); &lt;/script> &lt;/body> </pre> <li>In your browser go to: <code>http://minimulcasts.dev</code></li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-show-connect-button.png" /> <figcaption class="count">Connect with QuickBooks button is generated from Intuit.</figcaption> </figure> <li>Click on the 'Connect with QuickBooks' button</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-intuit-authentication.png" /> <figcaption class="count">Put in your QuickBooks Online username and password.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-authorize.png" /> <figcaption class="count">Then click the 'Authorize' button.</figcaption> </figure> <li>Once authenticated Intuit will return a GET request to the <code>/vendors/oauth_callback</code>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-successful.png" /> <figcaption class="count">We are now authorized to communicate with QuickBooks Online.</figcaption> </figure> <li>Finally, double check that the QBO connection was successful by logging into <a href="https://qbo.intuit.com" target="_blank">QuickBooks Online</a> then go to the 'Company' tab and then to the 'Activity Log' submenu.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/108/quickbooks-ruby-rails-check-sign-in.png" /> <figcaption class="count">That confirms it.</figcaption> </figure> </ol> <h3>Next, creating a vendor in Rails and QuickBooks Online</h3> <p>In <a href="/integrating-rails-and-quickbooks-online-via-the-version-3-api-part-2.html" target="_blank">Part 2</a> I demonstrate actually creating an entity over at QBO.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> Christian Pelczarski https://minimul.com/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-4-implementing-single-sign-on.html 2014-01-02T00:00:00Z <p>Get started integrating Rails 4 and QuickBooks Online with the Quickeebooks Gem: Part 4 : Implementing Single Sign On.</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> <ul class="alert toc yellow"> <i class="icon-list-ul icon-2x pull-left icon-border"></i> <h3>Table of Contents</h3> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html">Part 1: Make an OAuth connection between a Rails 4 app &amp; QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-2.html">Part 2: Create a Vendor on both Rails app and QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-3-disconnecting-with-quickbooks.html">Part 3: Disconnecting from QBO.</a></li> <li>Part 4: Implementing Single Sign On.</li> <li>Code presented below is also <a href="https://github.com/minimul/billtastic-quickbooks" target="_blank">available</a> on Github</li> </ul> <h3>Part 4: Implementing Single Sign On for a Rails app.</h3> <ol class="no-indent li-margin-top"> <li>Install devise for authentication</li> <ul> <li>Add <code>gem 'devise'</code> in Gemfile and run <code>bundle install</code></li> <li>Run <code>rails g devise:install</code> on command line</li> <li>Modify <code>config/environments/development.rb</code></li> <pre> config.action_mailer.default_url_options = { host: 'billtastic.dev' } </pre> <li>Modify <code>config/routes.rb</code> with <code>root to: 'vendors#index'</code></li> <pre> Billtastic::Application.routes.draw do resources :vendors resources :quickbooks do collection do get :authenticate get :oauth_callback get :disconnect end end <span class="inner-highlight">root to: 'vendors#index'</span> end </pre> <li>Create a Devise user.</li> <pre> rails g devise user bundle exec rake db:migrate </pre> <li>Add sign up code to the <code>app/views/layouts/application.html.erb</code></li> <pre> &lt;section id="sign-in"> &lt;% if user_signed_in? %> Signed in as &lt;strong>&lt;%= current_user.email %>&lt;/strong>. &lt;%= link_to 'Edit profile', edit_user_registration_path %> | &lt;%= link_to "Sign out", destroy_user_session_path, method: :delete %> &lt;% else %> &lt;%= link_to "Sign up", new_user_registration_path %> | &lt;%= link_to "Sign in", new_user_session_path %> &lt;% end %> &lt;/section> </pre> <li>Issue a <code>touch tmp/restart.txt</code> to reload Rails.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/102/quickbooks-rails-basic-sign-in.png" /> <figcaption class="count">Refresh <code>billtastic.dev/vendors</code> and you will see a basic sign in implementation</figcaption> </figure> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> Note: Styling code, which is very minimal, will not be included in the tutorial. See the <a href="https://github.com/minimul/billtastic-quickbooks" target="_blank">source</a> instead. </div> </ul> <li>Add the "Sign on with Intuit" button.</li> <ul> <li>Add the <code>omniauth-openid</code> gem to the <code>Gemfile</code> and <code>bundle install</code>.</li> <li>Modify <code>config/initializers/devise.rb</code> with the following:</li> <pre> require 'openid/store/filesystem' Devise.setup do |config| # ommited .... # Insert this next line down in the Omniauth section config.omniauth :open_id, :store => OpenID::Store::Filesystem.new('/tmp'), :name => 'intuit', :identifier => 'https://openid.intuit.com/openid/xrds', :require => 'omniauth-openid' </pre> <li>Modify <code>app/models/user/rb</code>:</li> <pre> class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, <span class="inner-highlight"> :omniauthable, :omniauth_providers => [:intuit] def self.find_for_open_id(access_token, signed_in_resource=nil) data = access_token.info if user = User.where(:email => data["email"]).first user else User.create!(:email => data["email"], :password => Devise.friendly_token[0,20]) end end</span> end </pre> <li>Create a new file <code>app/controllers/users/omniauth_callbacks_controller.rb</code>:</li> <pre> class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController skip_before_filter :verify_authenticity_token, :only => [:intuit] def intuit @user = User.find_for_open_id(request.env["omniauth.auth"], current_user) if @user.persisted? flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Intuit" sign_in_and_redirect @user, :event => :authentication else session["devise.intuit_data"] = request.env["omniauth.auth"] redirect_to new_user_registration_url end end end </pre> <li>Modify the Devise users route in <code>config/routes.rb</code> to:</li> <pre> Billtastic::Application.routes.draw do devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" } # ommitted </pre> <li>Add "Sign on with Intuit" button to <code>app/views/layouts/application.html.erb</code>.</li> <pre> &lt;!-- omitted .. --> &lt;%= link_to "Sign up", new_user_registration_path %> | &lt;%= link_to "Sign in", new_user_session_path %> | <span class="inner-highlight">&lt;ipp:login href="&lt;%= user_omniauth_authorize_path(:intuit) %>">&lt;/ipp:login></span> &lt;% end %> </pre> <li>Issue a <code>touch tmp/restart.txt</code> to reload Rails.</li> <figure> <img src="/images/articles/102/quickbooks-rails-sign-in-with-intuit-button.png" /> <figcaption class="count">Refresh <code>billtastic.dev/vendors</code> and you will now see the official Intuit Sign On button.</figcaption> </figure> </ul> <li>Automatically authenticate with the QBO API after signing on with Intuit.</li> <pre> &lt;!-- app/views/layouts/application.html.erb --> &lt;!-- ommited.. --> &lt;script type="text/javascript" src="https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js">&lt;/script> &lt;!-- configure the Intuit object: 'grantUrl' is a URL in your application which kicks off the flow, see below --> &lt;script> intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '&lt;%= authenticate_quickbooks_url %>'}); <span class="inner-highlight"> &lt;% if user_signed_in? && !session[:token] %> intuit.ipp.anywhere.directConnectToIntuit(); &lt;% end %></span> &lt;/script> &lt;/body> </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> Discussion: Even though we are using Intuit's authentication to sign into our Rails app we still need to "connect" to QuickBooks to integrate with the QBO API. The <code>directConnectToIntuit()</code> Javascript function automatically starts the API authentication process where the <code>connectToIntuit()</code> requires a user to click the button. </div> <div class="alert"> <i class="icon-bullhorn icon-2x pull-left icon-border"></i> Note: We are simply using the Devise's <code>user_signed_in?</code> method but in a real-world implementation we would have to further distinguish whether the login originated at Intuit. </div> <ul> <li>Since <code>directConnectToIntuit()</code> will not be launched in a popup we need to modify the <code>app/views/vendors/close_and_redirect.html.erb</code> view to handle this condition.</li> <pre> &lt;script> setTimeout(function(){ <span class="inner-highlight">if(window.opener == null){ window.location = '&lt;%= @url %>';</span> }else{ window.opener.location = '&lt;%= @url %>'; window.close(); } }, 10000); &lt;/script> </pre> <div class="alert"> <i class="icon-bullhorn icon-2x pull-left icon-border"></i> Note: If <code>window.opener</code> is null then the Intuit API authentication request is not running in a popup (as seen in <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html" target="_blank">Part 1</a>). </div> </ul> <li>Now we are ready to sign in using Intuit.</li> <figure> <img src="/images/articles/102/quickbooks-rails-click-on-sign-in-with-intuit-button.png" /> <figcaption class="count">Click on the "Sign in with Intuit" button.</figcaption> </figure> <figure> <img src="/images/articles/102/quickbooks-rails-sign-in-with-intuit-button-ask-password.png" /> <figcaption class="count">Next, you will be prompted for your QuickBook's credentials.</figcaption> </figure> <figure> <img src="/images/articles/102/quickbooks-rails-sign-in-with-intuit-button-authorize.png" /> <figcaption class="count">You will then be briefly brought back to the Rails app, which then the <code>directConnectToIntuit()</code> code will kick in and take you to this page.</figcaption> </figure> <figure> <img src="/images/articles/102/quickbooks-rails-sign-in-with-intuit-button-redirecting.png" /> <figcaption class="count">Here is the <code>close_and_redirect.html.erb</code> modifications from step 3 in action.</figcaption> </figure> <figure> <img src="/images/articles/102/quickbooks-rails-sign-in-with-intuit-final.png" /> <figcaption class="count">Lastly, we make it back again to the Rails app. Notice that the diconnect link is showing because we are authenticated against the Intuit API (1). Moreover, using Devise we are properly logged into the Billtastic Rails app (2) using our Intuit credentials.</figcaption> </figure> </ol> <h3>The infamous "blue dot" menu</h3> <p>Stay tuned to Part 5 where I will implement the Intuit blue dot menu.</p> Christian Pelczarski https://minimul.com/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-3-disconnecting-with-quickbooks.html 2013-12-17T00:00:00Z <p>Get started integrating Rails 4 and QuickBooks Online with the Quickeebooks Gem: Part 3 : Disconnecting with QuickBooks</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> <ul class="alert toc yellow"> <i class="icon-list-ul icon-2x pull-left icon-border"></i> <h3>Table of Contents</h3> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html">Part 1: Make an OAuth connection between a Rails 4 app &amp; QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-2.html">Part 2: Create a Vendor on both Rails app and QBO.</a></li> <li>Part 3: Disconnecting from QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-4-implementing-single-sign-on.html">Part 4: Implementing Single Sign On.</a></li> <li>Code presented below is also <a href="https://github.com/minimul/billtastic-quickbooks" target="_blank">available</a> on Github</li> </ul> <h3>Part 3: Disconnect the Rails app from QB0.</h3> <ol class="no-indent li-margin-top"> <li>Let's start by updating the view, <code>app/views/layouts/application.html.erb</code>, to show a disconnect link</li> <pre> &lt;% unless session[:token] %> &lt;ipp:connectToIntuit>&lt;/ipp:connectToIntuit> &lt;% else %> &lt;div> <span class="inner-highlight">&lt;%= link_to 'Disconnect from QuickBooks', '', data: { confirm: 'Are you sure you want to disconnect?' </span><lt></lt><td></td> &lt;/div> &lt;% end %> </pre> <li>Reload</li> <figure> <img src="/images/articles/099/quickbooks-rails-simple-disconnect-link.png" /> <figcaption class="count">Simple disconnect link to get the ball rolling. We'll come back later and add the href target.</figcaption> </figure> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: We need a <code>disconnect</code> action but the <code>vendors_controller</code> is getting crowded, therefore, let's create the <code>quickbooks_controller</code>. This is also a perfect place for the <code>authenticate</code> and <code>oauth_callback</code> actions so we are going to move them before creating the <code>disconnect</code> action. </div> <li>Make a new quickbooks_controller and move the <code>authenticate</code> and <code>oauth_callback</code> actions from the <code>vendors_controller</code> to here.</li> <ul> <li>The new controller at <code>app/controllers/quickbooks_controller.rb</code> should look like the code below. Notice the highlighted sections that also need to be modified.</li> <pre> class QuickbooksController < ApplicationController def authenticate <span class="inner-highlight">callback = oauth_callback_quickbooks_url</span> token = $qb_oauth_consumer.get_request_token(:oauth_callback => callback) session[:qb_request_token] = token redirect_to("https://appcenter.intuit.com/Connect/Begin?oauth_token=#{token.token}") and return end def oauth_callback at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier]) session[:token] = at.token session[:secret] = at.secret session[:realm_id] = params['realmId'] flash.notice = "Your QuickBooks account has been successfully linked." @msg = 'Redirecting. Please wait.' @url = vendors_path <span class="inner-highlight">render 'vendors/close_and_redirect', layout: false</span> end end </pre> <li>Modify <code>config/routes.rb</code>:</li> <pre> Billtastic::Application.routes.draw do resources :vendors resources :quickbooks do collection do get :authenticate get :oauth_callback end end end </pre> <li>Then modify the following line in <code>app/views/layouts/application.html.erb</code> to reflect the new authenticate route.</li> <pre> &lt;script> intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', <span class="inner-highlight">grantUrl: '&lt;%= authenticate_quickbooks_url</span> %>'}); &lt;/script> </pre> <li>Finally, make sure you issue a <code>touch tmp/restart.txt</code> to restart the app as the routes have changed.</li> </ul> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: We are still not done with the refactoring. The <code>disconnect</code> action uses a different Quickeebooks service then from the one used in the vendors controller so let's make the <code>set_qb_service</code> method more flexibility. </div> <li>Allow <code>set_qb_service</code> to create additional services.</li> <ul> <li>Remove the <code>set_qb_service</code> from <code>vendors_controller</code> and move it to <code>application_controller</code> and modify it as such:</li> <pre> class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception private def set_qb_service(type = 'Vendor') oauth_client = OAuth::AccessToken.new($qb_oauth_consumer, session[:token], session[:secret]) <span class="inner-highlight">@service = "Quickeebooks::Online::Service::#{type}".constantize.new</span> @service.access_token = oauth_client @service.realm_id = session[:realm_id] end end </pre> <li>Next, modify the <code>vendors_controller</code> instance vars of <code>@vendor_service</code> to be just <code>@service</code></li> </ul> <li>Reload <code>billtastic.dev/vendors</code> to test that the refactorings have taken effect with no errors.</li> <li>Add the <code>disconnect</code> route.</li> <pre> Billtastic::Application.routes.draw do resources :vendors resources :quickbooks do collection do get :authenticate get :oauth_callback <span class="inner-highlight">get :disconnect</span> end end end </pre> <li>Add the <code>disconnect</code> action to the <code>quickbooks_controller</code></li> <pre> def disconnect set_qb_service('AccessToken') result = @service.disconnect if result.error_code == '270' msg = 'Disconnect failed as OAuth token is invalid. Try connecting again.' else msg = 'Successfully disconnected from QuickBooks' end session[:token] = nil session[:secret] = nil session[:realm_id] = nil redirect_to vendors_path, notice: msg end </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: The <code>disconnect</code> action uses the AccessToken module/service from Quickeebooks. It provides a <code>disconnect</code> instance method that makes an API call requesting the disconnect. Intuit states that an error_code of <code>0</code> indicates a successful request but I have found that an error_code of <code>'22'</code> worked fine and the connection was properly disconnected. The session's are cleared in all cases because even on a disconnect failure this indicates that the current credentials are invalid and creating a new connection may be the solution. </div> <div class="alert red margin-top"> <i class="icon-exclamation icon-2x pull-left icon-border"></i> IMPORTANT: There are many points were the Intuit v2 API doesn't work exactly as stated in the docs as in the above example were I received an error code of <code>'22'</code> but the disconnect worked fine. Don't beat yourself up because you are not obtaining responses precisely as described in the documentation. Just shrug your shoulders and move on. </div> <li>Before we test disconnecting, let's log into the <a href="https://developer.intuit.com/" target="_blank">Intuit Partner Platform</a> like we did in <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html">Part 1</a>. From there navigate to <code>My Apps</code>-><code>Manage My Apps</code> and then to the Billtastic app.</li> <ul> <li>Notice that you should have at least one live API connection</li> <figure> <img src="/images/articles/099/quickbooks-rails-ipp-test-connections-before.png" /> <figcaption class="count">We will use this view to confirm that we successfully disconnected.</figcaption> </figure> <li>While we are here let's also change the disconnect url to <code>billtastic.dev/quickbooks/disconnect</code>.</li> <figure> <img src="/images/articles/099/quickbooks-rails-ipp-disconnect-link-before.png" /> <figcaption class="count">Before</figcaption> </figure> <figure> <img src="/images/articles/099/quickbooks-rails-ipp-disconnect-link-after.png" /> <figcaption class="count">After</figcaption> </figure> <div class="alert"> <i class="icon-bullhorn icon-2x pull-left icon-border"></i> NOTE: This url change is not needed for this tutorial and is only used for when a <code>disconnect</code> action is initiated from the Intuit App Center. We want to make the change now while we are here and the new disconnect route is fresh in our minds. </div> <li>Let's go back to the Billtastic.app at <code>billtastic.dev/vendors</code> and click on the 'Disconnect from QuickBooks' link.</li> <figure> <img src="/images/articles/099/quickbooks-rails-click-disconnect-link.png" /> <figcaption class="count">Click thru the confirmation dialog.</figcaption> </figure> <li>Yeah! A successfully disconnect.</li> <figure> <img src="/images/articles/099/quickbooks-rails-disconnect-success.png" /> <figcaption class="count">The 'Connect to QuickBooks' button also correctly displays now that the <code>session[:token]</code> is nil.</figcaption> </figure> <li>Let's make sure the disconnect occurred by checking over at the Intuit Partner Platform.</li> <figure> <img src="/images/articles/099/quickbooks-rails-ipp-test-connections-after.png" /> <figcaption class="count">Ok, that confirms it. There was 1 connection last time we checked (see Fig. 2).</figcaption> </figure> </ul> </ol> <h3>Implementing Single Sign On</h3> <p>Stay tuned to <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-4-implementing-single-sign-on.html">Part 4</a> in where I will hook up the Billtastic app with the ability to use your Intuit QBO credentials to sign on. This is referred to as "Single Sign On" by Intuit and is needed if you desire to have your application listed on their App Center.</p> Christian Pelczarski https://minimul.com/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-2.html 2013-11-04T00:00:00Z <p>Get started integrating Rails 4 and QuickBooks Online with the Quickeebooks Gem: Part 2</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> <ul class="alert toc yellow"> <i class="icon-list-ul icon-2x pull-left icon-border"></i> <h3>Table of Contents</h3> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html">Part 1: Make an OAuth connection between a Rails 4 app &amp; QBO.</a></li> <li>Part 2: Create a Vendor on both Rails app and QBO.</li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-3-disconnecting-with-quickbooks.html">Part 3: Disconnecting from QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-4-implementing-single-sign-on.html">Part 4: Implementing Single Sign On.</a></li> <li>Code presented below is also <a href="https://github.com/minimul/billtastic-quickbooks" target="_blank">available</a> on Github</li> </ul> <h3>Part 2 aim</h3> <p>Create and update a vendor on both the Rails app and QB0.</p> <ol class="no-indent li-margin-top"> <li>Add an <code>email_address</code> attribute to the Vendor.</li> <pre> $ rails g migration add_email_address_to_vendors email_address $ bundle exec rake db:migrate </pre> <li>Add <code>email_address</code> to the views.</li> <ul> <li><code>app/views/vendors/_form.html.erb</code></li> <pre> ... omitted <span class="inner-highlight">&lt;div class="field"> &lt;%= f.label :email_address %>&lt;br> &lt;%= f.email_field :email_address %> &lt;/div></span> ... omitted </pre> <li><code>app/views/vendors/index.html.erb</code></li> <pre> ... omitted &lt;tr> &lt;th>Name&lt;/th> <span class="inner-highlight">&lt;th>Email address&lt;/th></span> &lt;th>&lt;/th> &lt;th>&lt;/th> &lt;/tr> &lt;/thead> &lt;tbody> &lt;% @vendors.each do |vendor| %> ... omitted <span class="inner-highlight">&lt;tr> &lt;td>&lt;%= vendor.email_address %>&lt;/td> </span> &lt;td>&lt;%= link_to 'Show', vendor %>&lt;/td> &lt;td>&lt;%= link_to 'Edit', edit_vendor_path(vendor) %>&lt;/td> &lt;td>&lt;%= link_to 'Destroy', vendor, method: :delete, data: { confirm: 'Are you sure?' } %>&lt;/td> &lt;/tr> &lt;% end %> ... omitted </pre> <li><code>app/views/vendors/show.html.erb</code></li> <pre> ... omitted <span class="inner-highlight">&lt;p> &lt;strong>Email address:&lt;/strong> &lt;%= @vendor.email_address %> &lt;/p></span> ... omitted </pre> </ul> <li>Reload browser at <code>billtastic.dev/vendors</code> and test that the views have been updated.</li> <li>Create a new vendor locally and within QBO.</li> <ul> <li>Add a <code>before_action</code> filter within <code>app/controllers/vendors_controller.rb</code> to activate the Quickeebooks vendor service.</li> <pre> class VendorsController < ApplicationController before_action :set_vendor, only: [:show, :edit, :update, :destroy] <span class="inner-highlight">before_action :set_qb_service, only: [:create, :edit, :update, :destroy]</span> #... omitted private #... omitted <span class="inner-highlight">def set_qb_service oauth_client = OAuth::AccessToken.new($qb_oauth_consumer, session[:token], session[:secret]) @vendor_service = Quickeebooks::Online::Service::Vendor.new @vendor_service.access_token = oauth_client @vendor_service.realm_id = session[:realm_id] end</span> end </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: Put <code>before_action</code> after the <code>set_vendor</code> filter, which was created by the scaffolding generator in <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html" target="_blank">Part 1</a>. The <code>set_qb_service</code> method should be <code>private</code> and the very last method of the <code>class</code>. </div> <li>Whitelist the <code>email_address</code> attribute within the <code>vendors_controller</code>.</li> <pre> def vendor_params params.require(:vendor).permit(:name, <span class="inner-highlight">:email_address</span>) end </pre> <li>Modify the <code>create</code> action within the <code>vendors_controller</code>.</li> <pre> def create @vendor = Vendor.new(vendor_params) <span class="inner-highlight">vendor = Quickeebooks::Online::Model::Vendor.new vendor.name = vendor_params[:name] vendor.email_address = vendor_params[:email_address] @vendor_service.create(vendor)</span> respond_to do |format| if @vendor.save format.html { redirect_to @vendor, notice: 'Vendor was successfully created.' } format.json { render action: 'show', status: :created, location: @vendor } else format.html { render action: 'new' } format.json { render json: @vendor.errors, status: :unprocessable_entity } end end end </pre> </ul> <li>Go to the browser and refresh <code>billtastic.dev/vendors</code> and then click <code>New vendor</code> link.</code> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-create-a-new-vendor-fill-in.png" /> <figcaption class="count">Fill in these fields and click 'Create Vendor'.</figcaption> </figure> <li>Go over to QBO and check to see that the vendor was created.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-create-a-new-vendor-check.png" /> <figcaption class="count">Navigate to the 'Company' tab and then to the 'Activity Log' submenu. Also, click on the vendor's name within the activity tab to get more details.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-create-a-new-vendor-check-email.png" /> <figcaption class="count">Great, we can see that the email address was also created.</figcaption> </figure> <li>Now let's change the email address to exercise Quickeebooks' update functionality. Go back to the browser and click 'Edit'.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-edit-vendor-link.png" /> <figcaption class="count">After you get to the edit view stop as we are going add some code the vendors_controller.rb.</figcaption> </figure> <li>Modify the <code>update</code> action within the <code>vendors_controller</code>.</li> <pre> def update respond_to do |format| if @vendor.update(vendor_params) <span class="inner-highlight">vendor = @vendor_service.list.entries.find{ |e| e.name == vendor_params[:name] } vendor.email_address = vendor_params[:email_address] @vendor_service.update(vendor)</span> format.html { redirect_to @vendor, notice: 'Vendor was successfully updated.' } format.json { head :no_content } else format.html { render action: 'edit' } format.json { render json: @vendor.errors, status: :unprocessable_entity } end end end </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: The first highlighted line is searching your QBO vendor entries that match the submitted name and then returning the vendor. Notice in the next line that I only have the <code>email_address</code> as an update-able attribute and not <code>name</code>. This is because the <code>name</code> attribute for a Vendor (and other QBO entities such as Employee and Customer) is a unique attribute and can't be modified. </div> <li>Let's go back to the browser, which should be at the url <code>billtastic.dev/vendors/1/edit</code>.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-edit-vendor-submit.png" /> <figcaption class="count">Change the email address and click 'Update Vendor'.</figcaption> </figure> <li>Go back to QBO and check to see that the update was successful.</li> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-edit-vendor-on-qbo.png" /> <figcaption class="count">Click on the vendor's name to see if the email address was updated.</figcaption> </figure> <figure> <img src="https://d38ux1iykvusrb.cloudfront.net/articles/098/quickeebooks-quickbooks-rails-edit-vendor-on-qbo-detail-view.png" /> <figcaption class="count">Yay! The email address update worked!</figcaption> </figure> </ol> <h3><del datetime="2013-12-17T07:45:31 -0500">Finished</del> Next</h3> <p><del datetime="2013-12-17T06:43:37 -0500">This concludes a 2-part tutorial where a QuickBooks Online entity was created and updated from a Rails app. I sincerely hope this series aids your ramp up time in getting integrated with QuickBooks Online. Of course, don't hesitate to point out errors or ask for clarification within the article's comments.</del> This tutorial continues with <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-3-disconnecting-with-quickbooks.html">Part 3</a> that demonstrates disconnecting from QBO.</p> Christian Pelczarski https://minimul.com/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-1.html 2013-10-31T00:00:00Z <p>Get started integrating Rails 4 and QuickBooks Online with the Quickeebooks Gem: Part 1</p> <p><span class="which-css" style="display: none;">https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome</span></p> <ul class="alert toc yellow"> <i class="icon-list-ul icon-2x pull-left icon-border"></i> <h3>Table of Contents</h3> <li>Part 1: Make an OAuth connection between a Rails 4 app &amp; QBO.</li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-2.html">Part 2: Create a Vendor on both Rails and QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-3-disconnecting-with-quickbooks.html">Part 3: Disconnecting from QBO.</a></li> <li><a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-4-implementing-single-sign-on.html">Part 4: Implementing Single Sign On.</a></li> <li>Code presented below is also <a href="https://github.com/minimul/billtastic-quickbooks" target="_blank">available</a> on Github</li> </ul> <div class="alert red margin-top"> <i class="icon-exclamation icon-2x pull-left icon-border"></i> IMPORTANT: Tutorial uses <code>Rails 4</code>, <code>Ruby 1.9.3</code>, and <code>Pow</code>(hence OSX). Adapt for your ecosystem. </div> <h3>Part 1 aim</h3> <p>Perform all the steps necessary to make an <a href="http://oauth.net" target="_blank">OAuth</a> connection from a Rails 4 app (called <i>Billtastic</i>) to your QuickBooks Online (QBO) Account.</p> <ol class="no-indent li-margin-top"> <li>Join the <a href="https://developer.intuit.com/" target="_blank">Intuit Partner Platform</a></li> <p></p> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: I am going to assume you have a QBO account, which you can use your existing QBO username and password to join as a developer. If you are a developer and want to have a sandbox account then email Intuit @ <code>ippdevelopersupport@intuit.com</code> and request a developer subscription to QBO Plus. </div> <div class="alert"> <i class="icon-exclamation icon-2x pull-left icon-border"></i> After joining you will be asked to confirm your email. Even after confirming, you may continue to receive an error stating that you haven't confirmed. To get past this, log on and then off again. </div> <li>Create a new app.</li> <figure> <img src="/images/articles/097/intuit-partner-platform-create-app.png" /> <figcaption class="count">After joining up and confirmation your email, create a new QuickBooks API app.</figcaption> </figure> <figure> <img src="/images/articles/097/intuit-partner-platform-development-settings.png" /> <figcaption class="count outline">Fill in your card like this one and click the "Save". Note: the billtastic.io is entered just because you need a real domain to get past the field's validations.</figcaption> </figure> <figure> <img src="/images/articles/097/intuit-partner-platform-app-tokens-and-keys.png" /> <figcaption class="count">After saving, your app tokens and keys will be presented. The <code>consumer key and secret</code> need to be added to the Rails app, which is down in step 6.</figcaption> </figure> <li>Create a new Rails app and hook it up with Pow.</li> <pre> $ cd ~/www/labs # My experimental area $ rails new billtastic $ ln -s ~/www/labs/billtastic ~/.pow # Hook up Pow $ curl -L billtastic.dev | grep "Welcome" # Test hook up </pre> <li>Add the <a href="https://github.com/ruckus/quickeebooks" target="_blank">quickeebooks</a> and <a href="https://github.com/pelle/oauth-plugin" target="_blank">oauth-plugin</a> gems to the Gemfile and run <code>bundle install</code>.</li> <pre> # Edit the Gemfile and add these two gems: gem 'quickeebooks' gem 'oauth-plugin' $ bundle install </pre> <li>Generate Vendor scaffolding</li> <pre> $ rails g scaffold Vendor name $ rake db:migrate </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: In Part 2 a Vendor will be created in both Rails &amp; QBO but for now we are going to piggyback on the Vendor scaffolding view code to do the authentication. </div> <li>Create a new YAML config file called <code>config/quickbooks.yml</code>:</li> <pre> # The values here are just examples. Cut and paste the values that were generated in Step 2. See Fig. 3 key: qshfas789asfa98fafdafaff secret: FFasfafs435345FAADFGAGA452345345afdfaffasdfaf </pre> <li>Create <code>config/initializers/quickeebooks.rb</code>.</li> <pre> QB_CREDS = YAML.load(File.read(File.expand_path('./config/quickbooks.yml', Rails.root))) $qb_oauth_consumer = OAuth::Consumer.new(QB_CREDS['key'], QB_CREDS['secret'], { site: "https://oauth.intuit.com", request_token_path: "/oauth/v1/get_request_token", authorize_url: "https://appcenter.intuit.com/Connect/Begin", access_token_path: "/oauth/v1/get_access_token" }) </pre> <li>Add the following routes to <code>config/routes.rb</code>.</li> <pre> Billtastic::Application.routes.draw do resources :vendors do collection do get :authenticate get :oauth_callback end end end </pre> <li>Add the actions <code>app/controllers/vendors_controller.rb</code> for the routes we added in the previous step.</li> <pre> # app/controllers/vendors_controller.rb # .. code omitted def authenticate callback = oauth_callback_vendors_url token = $qb_oauth_consumer.get_request_token(:oauth_callback => callback) session[:qb_request_token] = token redirect_to("https://appcenter.intuit.com/Connect/Begin?oauth_token=#{token.token}") and return end def oauth_callback at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier]) session[:token] = at.token session[:secret] = at.secret session[:realm_id] = params['realmId'] flash.notice = "Your QuickBooks account has been successfully linked." @msg = 'Redirecting. Please wait.' @url = vendors_path render 'close_and_redirect', layout: false end private # .. code omitted </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: In a real app you will persist the token, secret & RealmID to the database for a user. Those 3 items are needed to communicate with QBO. For convenience, sessions are being used for persistence in this tutorial. </div> <li>Add Intuit Connect button code to application layout on <code>app/view/layouts/application.html.erb</code>.</li> <pre> .. omitted &lt;body> &lt;!-- REPLACE THE BODY of the default application.html.erb with this --> &lt;% unless session[:token] %> &lt;ipp:connectToIntuit>&lt;/ipp:connectToIntuit> &lt;% end %> &lt;% if notice %> &lt;div style="padding: 10px;background: gainsboro;font-weight: 900;width: 50%;">&lt;%= notice %>&lt;/div> &lt;% end %> &lt;%= yield %> &lt;script type="text/javascript" src="https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js">&lt;/script> &lt;script> intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '&lt;%= authenticate_vendors_url %>'}); &lt;/script> &lt;/body> </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: For a tutorial it is a bit heavy handed to put this code into the application layout, however, for a production app Intuit requires that their <a href="https://developer.intuit.com/docs/0025_quickbooksapi/0060_auth_auth/widgets/blue_dot_menu" target="_blank">blue dot menu</a> to be displayed on each page so you might find yourself putting Intuit scripts in the layout. </div> <li>Reload with a <code>touch tmp/restart.txt</code>. <li>Let's take a look. Navigate your browser to <code>billtastic.dev/vendors</code> and you should see this:</li> <figure> <img src="/images/articles/097/intuit-partner-platform-list-vendors.png" /> <figcaption class="count">Now we are almost ready to Connect with QuickBooks</figcaption> </figure> <li>Create <code>app/views/vendors/close_and_redirect.html.erb</code> to handle the redirect from QuickBooks.</li> <pre> &lt;!DOCTYPE html> &lt;html> &lt;head> &lt;title>&lt;%= @msg %>&lt;/title> &lt;/head> &lt;body> &lt;h3>&lt;%= @msg %>&lt;/h3> &lt;script> setTimeout(function(){ window.opener.location = '&lt;%= @url %>'; window.close(); }, 10000); &lt;/script> &lt;/body> &lt;/html> </pre> <div class="alert"> <i class="icon-coffee icon-2x pull-left icon-border"></i> DISCUSSION: Go back to step 9 and checkout the <code>oauth_callback</code> action as it is responsible for rendering the <code>close_and_redirect</code> view. Also, read my <a href="/handling-the-quickbooks-oauth-callback.html" target="_blank">article</a> that explains this above code in more detail. </div> <li>Go back to the browser on <code>billtastic.dev/vendors</code>, click on the 'Connect with QuickBooks' button, and sign in</li> <figure> <img src="/images/articles/097/intuit-partner-platform-auth-popup.png" /> <figcaption class="count">Put in your QBO username and password and click the 'Sign in' button.</figcaption> </figure> <figure> <img src="/images/articles/097/intuit-partner-platform-authorize.png" /> <figcaption class="count">Then click the 'Authorize' button.</figcaption> </figure> <li>After a successful authentication, Intuit will return a GET request to the <code>/vendors/oauth_callback</code>. Again, see the <code>oauth_callback</code> action in step 9 to see what is going on.</li> <figure> <img src="/images/articles/097/intuit-partner-platform-redirecting-please-wait.png" /> <figcaption class="count">This is the Step 13 view code in action.</figcaption> </figure> <figure> <img src="/images/articles/097/intuit-partner-platform-successful-link.png" /> <figcaption class="count">Success! We are now authorized to communicate with QuickBooks.</figcaption> </figure> <li>Lastly, let's double check that the QBO connection was indeed successful. Go to QBO, login, and navigate to the 'Company' tab and then to the 'Activity Log' submenu.</li> <figure> <img src="/images/articles/097/intuit-partner-platform-check-sign-in.png" /> <figcaption class="count">You should see that you signed in.</figcaption> </figure> </ol> <h3>Next episode, creating an entity</h3> <p>In <a href="/get-started-integrating-rails-4-and-quickbooks-online-with-the-quickeebooks-gem-part-2.html">Part 2</a> I will dig a bit more into the Quickeebooks gem by creating a vendor on the Rails app as well as in QBO.</p> Christian Pelczarski https://minimul.com/does-intuit-require-openid-for-quickbooks-integration.html 2013-08-26T00:00:00Z <p>Does Intuit require OpenID for QuickBooks Online integration?</p> <p><strong>You do not need to implement OpenID</strong> to integrate with QuickBooks Online (QBO). However, if you want your app listed on the Intuit App Center than yes, OpenID support is required.</p> <h2>First, ask yourself if your app needs a Intuit App Center listing.</h2> <p>It might seem like a <strong>no brainer to desire an Intuit App Center listing</strong> but since implementing OpenID is just another checkbox in your long list of integration todos considering skipping the App Center listing if your application has the following characteristics:</p> <ul> <li>It is not a horizontal, financial based application. <ul> <li>Applications that benefit most from App Center exposure are going to be ones that are financial in nature such as a <a href="http://en.wikipedia.org/wiki/Software_as_a_service" target="_blank">SAAS</a> billing service since they have strong ties into accounting. Horizontal applications, whether financial in essence or not, may also stand to benefit from a listing.</li> </ul> </li> <li>Is an "internal" application only used inside the company firewall.</li> <li><strong>Your sign ups are primarily coming from different marketing channels</strong>. <ul> <li>If your application sells say, cedar-based sidings, your target audience is not going to find you through the Intuit App Center but instead from channels closely aligned to your niche.</li> </ul> </li> </ul> <p>In short, take some time to consider the importance of App Center publicity so as to not further delay the launch of your application's QBO integration.</p> Christian Pelczarski https://minimul.com/handling-the-quickbooks-oauth-callback.html 2013-07-03T00:00:00Z <p>Handling the QuickBooks OAuth callback</p> <h2>Intuit requires a popup to OAuth.</h2> <p>When OAuth'ing with another service like Twitter, EBay, etc. it is common to redirect the user using the entire page as it makes returning back to the host application technically easier. Intuit, however, requires for C2QB compliance that authentication happens in a popup.</p> <p><img src="images/intuit/connect-to.png" /></p> <p><img src="images/intuit/popup.png" /></p> <h2>Solution: 2 step process to handle the QuickBooks OAuth callback</h2> <p>Example is in Rails but easily translates to different web dev ecosystems.</p> <h3>1.</h3> <p>Example of the controller action that maps to the Intuit App Card route/url regarding the handling of the OAuth callback (e.g. /quickbooks/oauth_callback). The "Intuit App Card" is what they call the area that various names and urls are set for development and production. Make sure to persist the QuickBooks OAuth credentials in this step.</p> <pre class="brush:ruby;"> def oauth_callback # ... code omitted for saving the account's OAuth secret and token if # ... omitted ... # Set flash.notice as this will persist even when using the javascript based redirect in step 2 flash.notice = "Your QuickBooks account has been successfully linked to your account." @msg = 'Redirecting. Please wait.' @url = quickbooks_accounts_path end render 'shared/close_and_redirect' end </pre> <h3>2.</h3> <p>Here is the content of <code>shared/close_and_redirect</code> rendered from step 1. The <code>window.opener</code> refers to your main or original page that initiated the authentication popup. Therefore, the <code>window.opener.location</code> will navigate to the route you specified in <code>@url</code> within the main page. Lastly, the authentication popup is closed and the user is properly OAuth'd and ready to communicate with QuickBooks.</p> <pre class="brush:ruby;"> !!! %html %head %title= @msg %body %h3= @msg :javascript setTimeout(function(){ window.opener.location = '#{@url}';window.close(); }, 2000); </pre> Christian Pelczarski