We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Set your QUBE API key and secret as environment variables:
$ export QUBE_API_KEY=your_api_key
$ export QUBE_WEBHOOK_SECRET=your_webhook_secret
Store metadata about your customer's QuickBooks Desktop connection. Keeping track of the company file helps prevent accidental data corruption when your customer inevitably changes it.
# db/migrate/20210101000000_create_quickbooks_desktop_connections.rb
class CreateQuickbooksDesktopConnections < ActiveRecord::Migration[6.0]
def change
create_table :quickbooks_desktop_connections do |t|
t.references :account, null: false, foreign_key: true
t.string :company_file
t.string :username
end
end
end
Track the requests you've queued for the Web Connector, so you can process the responses later.
# db/migrate/20210101000001_create_quickbooks_desktop_requests.rb
class CreateQuickbooksDesktopRequests < ActiveRecord::Migration[6.0]
def change
create_table :quickbooks_desktop_requests do |t|
t.references :quickbooks_desktop_connection, null: false, foreign_key: true
t.string :qube_request_id, null: false
t.jsonb :request_json
t.text :response_json
t.string :state, null: false, default: 'queued'
end
end
end
Create models for the QuickBooks entities you care about
# db/migrate/20210101000001_create_quickbooks_desktop_customers.rb
class CreateQuickbooksDesktopCustomers < ActiveRecord::Migration[6.0]
def change
create_table :quickbooks_desktop_customers do |t|
t.references :quickbooks_desktop_connection, null: false, foreign_key: true
t.string :list_id, null: false
t.string :full_name
t.string :company_name
t.string :first_name
t.string :last_name
t.string :email
t.string :phone
end
end
end
The models
# app/models/account.rb
class Account < ApplicationRecord
has_one :qbd_connection, class_name: 'QuickbooksDesktop::Connection', dependent: :destroy
end
#app/models/quickbooks_desktop/connection.rb
class QuickbooksDesktop::Connection < ApplicationRecord
belongs_to :account
has_many :requests, class_name: 'QuickbooksDesktop::Request', dependent: :destroy
has_many :customers, class_name: 'QuickbooksDesktop::Customer', dependent: :destroy
end
# app/models/quickbooks_desktop/request.rb
class QuickbooksDesktop::Request < ApplicationRecord
belongs_to :qbd_connection, class_name: 'QuickbooksDesktop::Connection'
after_create :queue_request
def queue_request
response = QubeSync.queue_request(qbd_connection.connection_id, { request_json: as_qube_json })
update!(qube_request_id: response.fetch("id"))
end
def webhook_url
Rails.application.routes.url_helpers.webhooks_qube_sync_url
end
end
# app/models/quickbooks_desktop/customer_query_request.rb
class QuickbooksDesktop::CustomerQueryRequest < QuickbooksDesktop::Request
def as_qube_json
QubeSync::Builder.new(version: "15.0") do |b|
b.QBXML do
b.QBXMLMsgsRq(onError: "stopOnError", iterator: "Start") do
b.CustomerQueryRq(requestID: id) do
b.MaxReturned(25)
end
end
end
end.as_json
end
def process_response!(response)
case response.fetch('state')
when 'response_received'
# this might be called multiple times for a single request
# if you specified `iterator="Start"` in the request
json = response.fetch('response_json')
json.fetch('data').map do |attrs|
qbd_connection
.customers
.create_with(
full_name: attrs['FullName'],
company_name: attrs['CompanyName'],
first_name: attrs['FirstName'],
last_name: attrs['LastName'],
email: attrs['Email'],
phone: attrs['Phone'])
.find_or_create_by!(list_id: attrs['list_id'])
if json.fetch('iteratorRemainingCount') > 0
update!(state: 'iterating')
else
update!(state: 'completed')
end
when 'error'
update!(state: 'error',
error_message: response.dig('error', 'message'),
error_type: response.dig('error', 'error_type'),
user_message: response.dig('error', 'user_message'))
else
end
end
end
Customize the Quickbooks entity models to fit your needs. You'll use the response from QuickBooks to store the data in these models.
# app/models/quickbooks_desktop/customer.rb
class QuickbooksDesktop::Customer < ApplicationRecord
belongs_to :qbd_connection
end
Time to set up the QuickBooks Desktop connection
# app/controllers/accounts_controller.rb
class QuickbooksDesktop::ConnectionsController < ApplicationController
before_action :set_account
before_action :set_qbd_connection, only: [:show, :download_qwc, :get_password]
def create
connection =
QubeSync
.create_connection(name: account.company_name)
.fetch("id")
@qbd_connection = @account.create_qbd_connection(connection_id: connection_id)
initial_sync_requests = [
QuickbooksDesktop::CustomerQueryRequest.new,
# QuickbooksDesktop::TaxCodeQueryRequest.new,
# QuickbooksDesktop::ItemQueryRequest.new,
# QuickbooksDesktop::VendorQueryRequest.new,
# ...
]
# Queue up the initial requests to QuickBooks Desktop,
# usually importing data like customers, items, etc.
@qbd_connection.requests = initial_sync_requests
redirect_to quickbooks_desktop_connection_path(@qbd_connection)
end
def show
@qbd_connection = @account.qbd_connection
# render instructions for the user, along with
# links to download the QWC file and get their
# Web Connector password (see below)
end
def download_qwc
qwc_content = QubeSync.download_qwc(@qbd_connection.connection_id)
send_data qwc_content, filename: "qube.qwc"
end
def get_password
password = QubeSync.generate_password(@qbd_connection.connection_id)
render json: { password: password }
end
private
def set_qbd_connection
@qbd_connection = @account.qbd_connection
end
end
# app/controllers/webhooks/qube_sync_controller.rb
class Webhooks::QubeSyncController < ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :authenticate_user!
def customer_query_response
response = QubeSync.verify_webhook!(
request.body.read,
request.headers['X-QUBE-Signature']
)
# We background the response processing so we can respond to the webhook quickly
ProcessCustomerQueryResponseJob.perform_later(response)
head :ok
end
end
# app/jobs/process_customer_query_response_job.rb
class ProcessCustomerQueryResponseJob < ApplicationJob
def perform(response)
request_id = response.fetch('id')
request = QuickbooksDesktop::Request.find_by(qube_request_id: request_id)
request.process_response!(response)
end
end
# app/models/quickbooks_desktop/customer_query_request.rb
class QuickbooksDesktop::CustomerQueryRequest < QuickbooksDesktop::Request
def to_xml
<<~XML
<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="16.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError" iterator="Start">
<CustomerQueryRq requestID="#{id}">
<MaxReturned>25</MaxReturned>
</CustomerQueryRq>
</QBXMLMsgsRq>
</QBXML>
XML
end
end