Ruby client for the QuickBooks Online API version 3.
quickbooks-ruby
gem has fantastic support for those who favor XML.Add this line to your application's Gemfile:
gem 'qbo_api'
And then execute:
$ bundle
Or install it yourself as:
$ gem install qbo_api
qbo_api = QboApi.new(access_token: 'REWR342532asdfae!$4asdfa', realm_id: 32095430444)
- qbo_api.get :customer, 1
- cd ~/<local dir>
- git clone git@github.com:minimul/qbo_api.git && cd qbo_api
- bundle
- bin/console
- QboApi.production = true
- qbo_api = QboApi.new(access_token: "qyprd2uvCOdRq8xzoSSiiiiii", realm_id: "12314xxxxxx7")
- qbo_api.get :customer, 1
Some QBO entities have attributes of type DateTime (e.g., Time Activities with StartTime and EndTime). All DateTimes passed to the QBO API must be serialized in ISO 8601 format. If ActiveSupport is loaded, you can achieve proper serialization with the following configuration:
ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
ActiveSupport::JSON::Encoding.time_precision = 0
If you're not using ActiveSupport, you'll need to use #iso8601
method to convert your Time
/DateTime
instances to strings before passing them to a QboApi instance. Failure to do so will result in a raised QboApi::BadRequest exception.
QboApi.production = true
QboApi.log = true
$stdout
e.g.
QboApi.logger = Rails.logger
QboApi.request_id = true
resp = qbo_api.create(:bill, payload: bill_hash, params: { requestid: qbo_api.uuid })
# Works with .get, .create, .update, .query methods
QboApi.minor_version = 8
resp = qbo_api.get(:item, 8, params: { minorversion: 8 })
# Works with .get, .create, .update, .query methods
invoice = {
"Line": [
{
"Amount": 100.00,
"DetailType": "SalesItemLineDetail",
"SalesItemLineDetail": {
"ItemRef": {
"value": "1",
"name": "Services"
}
}
}
],
"CustomerRef": {
"value": "1"
}
}
response = qbo_api.create(:invoice, payload: invoice)
p response['Id'] # => 65
customer = {
DisplayName: 'Jack Doe',
PrimaryPhone: {
FreeFormNumber: "(415) 444-1234"
}
}
response = qbo_api.update(:customer, id: 60, payload: customer)
p response.fetch('PrimaryPhone').fetch('FreeFormNumber') # => "(415) 444-1234"
response = qbo_api.delete(:invoice, id: 145)
p response['status'] # => "Deleted"
NOTE: If you are deleting a journal entry you have to use the following syntax with the underscore, even though this is inconsistent with how you create journal entries.
response = qbo_api.delete(:journal_entry, id: 145)
p response['status'] # => "Deleted"
response = qbo_api.deactivate(:employee, id: 55)
p response['Active'] # => false
response = qbo_api.get(:customer, 5)
p response['DisplayName'] # => "Dukes Basketball Camp"
response = qbo_api.get(:customer, ["DisplayName", "Dukes Basketball Camp"])
p response['Id'] # => 5
response = qbo_api.get(:customer, ["DisplayName", "LIKE", "Dukes%"])
p response['Id'] # => 5
response = qbo_api.get(:vendor, ["DisplayName", "IN", "(true, false)"])
p response.size # => 28
Note: There is some overlap with the all
and the get
methods. The get
method is limited to 1000 results where the all
method will return all the results no matter the number.
# retrieves all active customers
qbo_api.all(:customers).each do |c|
p "#{c['Id']} #{c['DisplayName']}"
end
# retrieves all active or inactive employees
qbo_api.all(:employees, inactive: true).each do |e|
p "#{e['Id']} #{e['DisplayName']}"
end
# retrieves all vendors by groups of 5
qbo_api.all(:vendor, max: 5).each do |v|
p v['DisplayName']
end
# retrieves all customers by groups of 2 using a custom select query
where = "WHERE Id IN ('5', '6', '7', '8', '9', '10')"
qbo_api.all(:customer, max: 2, select: "SELECT * FROM Customer #{where}").each do |c|
p c['DisplayName']
end
api.all(:clients).take(50).each { |c| p c["Id"] }
api.all(:clients).count
api.all(:clients).first
api.all(:clients).to_a
# Use the .esc() method
name = qbo_api.esc "Amy's Bird Sanctuary"
response = qbo_api.query(%{SELECT * FROM Customer WHERE DisplayName = '#{name}'})
# OR USE .get() method, which will automatically escape
response = qbo_api.get(:customer, ["DisplayName", "Amy's Bird Sanctuary"])
p response['Id'] # => 1
api.send_invoice(invoice_id: 1, email_address: 'billy@joe.com')
payload = {"AttachableRef":
[
{"EntityRef":
{
"type": "Invoice",
"value": "111"
}
}
],
"FileName": "test.txt",
"ContentType": "text/plain"
}
# `attachment` can be either an IO stream or string path to a local file
response = qbo_api.upload_attachment(payload: payload, attachment: '/tmp/test.txt')
p response['Id'] # => 5000000000000091308
Be aware that any errors will not raise a QboApi::Error
, but will be returned in the following format:
{"AttachableResponse"=>
[{"Fault"=>
{"Error"=>
[{"Message"=>"Object Not Found",
"Detail"=>
"Object Not Found : Something you're trying to use has been made inactive. Check the fields with accounts, customers, items, vendors or employees.",
"code"=>"610",
"element"=>""}],
"type"=>"ValidationFault"}}],
"time"=>"2018-01-03T13:06:31.406-08:00"}
response = qbo_api.cdc(entities: 'estimate', changed_since: '2011-10-10T09:00:00-07:00')
# You can also send in a Time object e.g. changed_since: Time.now
expect(response['CDCResponse'].size).to eq 1
ids = response['CDCResponse'][0]['QueryResponse'][0]['Estimate'].collect{ |e| e['Id'] }
p ids
payload = {
"BatchItemRequest":
[
{
"bId": "bid1",
"operation": "create",
"Vendor": {
"DisplayName": "Smith Family Store"
}
}, {
"bId": "bid2",
"operation": "delete",
"Invoice": {
"Id": "129",
"SyncToken": "0"
}
}
]
}
response = qbo_api.batch(payload)
expect(response['BatchItemResponse'].size).to eq 2
expect(batch_response.detect{ |b| b["bId"] == "bid1" }["Vendor"]["DisplayName"]).to eq "Smith Family Store"
params = { start_date: '2015-01-01', end_date: '2015-07-31', customer: 1, summarize_column_by: 'Customers' }
response = qbo_api.reports(name: 'ProfitAndLoss', params: params)
p response["Header"]["ReportName"]) #=> 'ProfitAndLoss'
See docs
response = qbo_api.reconnect
#=> if response['ErrorCode'] == 0
#=> p response['OAuthToken'] #=> rewq23423424afadsdfs==
#=> p response['OAuthTokenSecret'] #=> ertwwetu12345312005343453yy=Fg
See docs
response = qbo_api.disconnect
#=> if response['ErrorCode'] == 0
#=> # Successful disconnect
customer = { DisplayName: 'Weiskopf Consulting' }
begin
response = qbo_api.create(:customer, payload: customer)
rescue QboApi::BadRequest => e
if e.message =~ /Another customer already exists with this name/
# Query for Id using DisplayName
# Do an qbo_api.update instead
end
end
p qbo_api.is_transaction_entity?(:invoice) # => true
# Plural is supported as well
p qbo_api.is_transaction_entity?(:invoices) # => true
p qbo_api.is_transaction_entity?(:customer) # => false
p qbo_api.is_name_list_entity?(:vendors) # => true
A quickbooks supplied PDF can be downloaded for api endpoints which offer a PDF.
qbo_api.get_pdf(:invoice, 121) # produces a pdf stream.
The PDF stream can then be saved using your preferred method.
# example using Ruby on Rails with ActiveStorage
class Invoice
has_one_attached :pdf_file
end
invoice_number = 121
pdf_data = qbo_api.get_pdf(:invoice, invoice_numer) # returns raw pdf stream
pdf_io = StringIO.new(pdf_data) # convert to a StringIO object
filename = "invoice_no_#{invoice_number}"
invoice.pdf_file.attach(
io: pdf_io,
filename: filename,
content_type: 'application/pdf'
)
# plain ruby example
invoice_number = 121
pdf_data = qbo_api.get_pdf(:invoice, invoice_numer) # returns raw pdf stream
filename = "invoice_no_#{invoice_number}.pdf"
File.write(filename, pdf_data)
Create a Intuit developer account at https://developer.intuit.com
Create an app in the intuit developer backend
Accounting
& Payments
selected.Setup the app and gather credentials
http://localhost:9393/oauth2-redirect
(or whatever PORT= is in your .env)Setup qbo_api
git clone git://github.com/minimul/qbo_api && cd qbo_api
bundle
Create a .env
file
cp .env.example_app.oauth2 .env
export QBO_API_CLIENT_ID=[YOUR CLIENT ID]
export QBO_API_CLIENT_SECRET=[YOUR CLIENT SECRET]
export QBO_API_COMPANY_ID=[YOUR COMPANY ID]
Start up the example app and test
ruby example/oauth2.rb
http://localhost:9393/oauth2
Connect to QuickBooks
button to connect to your QuickBooks sandbox, which you receive when signing up at https://developer.intuit.com.http://localhost:9393/oauth2/customer/5
Checkout example/oauth2.rb
to see what is going on under the hood.
example/app.rb
for the request handling code.See https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html for how to install ngrok and what it is.
With the example app running, run:
ngrok http 9393 -subdomain=somereasonablyuniquenamehere
Go to the Development
tab
Add a webhook, Select all triggers and enter the https url from the ngrok output
https://somereasonablyuniquenamehere/webhooks
After saving the webhook, click 'show token'. Add the token to your .env as QBO_API_VERIFIER_TOKEN
In another tab, create a customer via the API:
bundle exec ruby -rqbo_api -rdotenv -e 'Dotenv.load; p QboApi.new(access_token: ENV.fetch("QBO_API_ACCESS_TOKEN"), realm_id: ENV.fetch("QBO_API_COMPANY_ID")).create(:customer, payload: { DisplayName: "TestCustomer" })'
(You'll also need to have added the QBO_API_COMPANY_ID and QBO_API_ACCESS_TOKEN to your .env)
There could be a delay of up to a minute before the webhook fires.
It'll appear in your logs like:
{"eventNotifications"=>[{"realmId"=>"XXXX", "dataChangeEvent"=>{"entities"=>[{"name"=>"Customer", "id"=>"62", "operation"=>"Create", "lastUpdated"=>"2018-04-08T04:14:39.000Z"}]}}]}
Verified: true
"POST /webhooks HTTP/1.1" 200 - 0.0013
connection = build_connection('https://oauth.platform.intuit.com', headers: { 'Accept' => 'application/json' }) do |conn|
conn.basic_auth(client_id, client_secret)
conn.request :url_encoded # application/x-www-form-urlencoded
conn.response :json
conn.use QboApi::RaiseHttpException
end
raw_response = connection.post do |req| req.body = { grant_type: :refresh_token, refresh_token: current_refresh_token } req.url '/oauth2/v1/tokens/bearer' end
- Once your .env file is completely filled out you can use the console to play around in your sandbox
bin/console test
@qbo_api.get :customer, 1
Bug reports and pull requests are welcome on GitHub at https://github.com/minimul/qbo_api.
git clone git://github.com/minimul/qbo_api && cd qbo_api
bundle
.env
file
cp .env.test .env
bundle exec rspec spec/
The gem is available as open source under the terms of the MIT License.