qbo_api 1.8.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.env.test +0 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -3
- data/README.md +76 -96
- data/example/base.rb +2 -0
- data/example/oauth2.rb +16 -3
- data/example/public/connect-to-qbo.svg +1 -0
- data/example/views/oauth2.erb +4 -14
- data/lib/qbo_api/api_methods.rb +11 -13
- data/lib/qbo_api/attachment.rb +20 -5
- data/lib/qbo_api/configuration.rb +0 -1
- data/lib/qbo_api/connection.rb +24 -47
- data/lib/qbo_api/entity.rb +13 -2
- data/lib/qbo_api/error.rb +9 -1
- data/lib/qbo_api/raise_http_exception.rb +43 -28
- data/lib/qbo_api/supporting.rb +14 -1
- data/lib/qbo_api/util.rb +0 -1
- data/lib/qbo_api/version.rb +1 -1
- data/lib/qbo_api.rb +2 -2
- data/qbo_api.gemspec +5 -6
- metadata +19 -37
- data/.env.example_app.oauth1 +0 -2
- data/example/oauth.rb +0 -62
- data/lib/qbo_api/connection/oauth1.rb +0 -60
- data/lib/qbo_api/connection/oauth2.rb +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b9924b785fa2755f09e92bf26198280b198dee80d2621d8c86b4e4f2fdb1cc1d
|
|
4
|
+
data.tar.gz: 26b76ad24f8760ebb5c39385e9798b3799645cbe6d3d69230a6d720ee9676514
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 827f3a675049aa20889d6e7e7cf9b8e2b05df79c0410b9bbef0c9bcd2c57eb81f77f88f5e5c2b9f161318bbd899ccb94bec9a9d2e199977b8545bd16cb8163e6
|
|
7
|
+
data.tar.gz: e52c00f610340f0a0d13c711782629c56b80fcc6300b05e5f97705de63545c6cb59dbeba33fb15cbe2770628c38458c2535eabfd00e7e74a87223c267c5229e8
|
data/.env.test
CHANGED
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
sudo: false
|
|
2
2
|
language: ruby
|
|
3
3
|
rvm:
|
|
4
|
-
- 2.
|
|
5
|
-
- 2.
|
|
4
|
+
- 2.6.8
|
|
5
|
+
- 2.7.4
|
|
6
|
+
- 3.0.2
|
|
7
|
+
|
|
8
|
+
before_install:
|
|
9
|
+
- gem install bundler
|
|
6
10
|
|
|
7
11
|
before_script:
|
|
8
12
|
- cp .env.test .env
|
|
@@ -10,7 +14,6 @@ before_script:
|
|
|
10
14
|
script:
|
|
11
15
|
- bundle exec rspec spec/
|
|
12
16
|
|
|
13
|
-
|
|
14
17
|
notifications:
|
|
15
18
|
email:
|
|
16
19
|
- christian@minimul.com
|
data/README.md
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
# QboApi
|
|
2
2
|
|
|
3
3
|
Ruby client for the QuickBooks Online API version 3.
|
|
4
|
-
- Built on top of the excellent Faraday gem.
|
|
5
4
|
- JSON only support.
|
|
6
5
|
- Please don't ask about XML support. [Intuit has stated](https://github.com/ruckus/quickbooks-ruby/issues/257#issuecomment-126834454) that JSON is the primary data format for the QuickBooks API (v3 and beyond). This gem will specialize in JSON only. The [`quickbooks-ruby`](https://github.com/ruckus/quickbooks-ruby) gem has fantastic support for those who favor XML.
|
|
7
6
|
- Features specs built directly against a QuickBooks Online Sandbox via the VCR gem.
|
|
8
7
|
- Robust error handling.
|
|
9
8
|
|
|
10
|
-
## Tutorials and Screencasts
|
|
11
|
-
- <a href="http://minimul.com/introducing-a-new-ruby-quickbooks-online-client.html" target="_blank">Why qbo_api is a better choice </a> than the <a href="https://github.com/ruckus/quickbooks-ruby" target="_blank">quickbooks-ruby</a> gem.
|
|
12
|
-
- <a href="http://minimul.com/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html" target="_blank">Part 1</a>: Learn how to spin up the <a href="https://github.com/minimul/qbo_api#spin-up-an-example">example app</a>.
|
|
13
|
-
- <a href="http://minimul.com/the-modern-ruby-quickbooks-client-part-2.html" target="_blank">Part 2</a>: <a href="https://github.com/minimul/qbo_api#running-the-specs">Running the specs</a> to aid you in understanding a QuickBooks API transaction.
|
|
14
|
-
- <a href="http://minimul.com/the-modern-ruby-quickbooks-client-contributing.html" target="_blank">Part 3</a>: <a href="https://github.com/minimul/qbo_api#creating-new-specs-or-modifying-existing-spec-that-have-been-recorded-using-the-vcr-gem">Contributing to the gem</a>.
|
|
15
|
-
### Important Note: The videos are out of date.
|
|
16
|
-
If you signed up for a Intuit developer account after July 17th, 2017 then you will have to
|
|
17
|
-
follow <a href='#OAuth2-example'>OAuth2: Spin up an example</a>
|
|
18
9
|
## The Book
|
|
19
10
|
|
|
20
11
|
<a href="https://leanpub.com/minimul-qbo-guide-vol-1" target="_blank">
|
|
@@ -22,7 +13,7 @@ follow <a href='#OAuth2-example'>OAuth2: Spin up an example</a>
|
|
|
22
13
|
</a>
|
|
23
14
|
|
|
24
15
|
|
|
25
|
-
## Ruby >= 2.
|
|
16
|
+
## Ruby >= 2.6 required
|
|
26
17
|
|
|
27
18
|
## Installation
|
|
28
19
|
|
|
@@ -43,41 +34,22 @@ Or install it yourself as:
|
|
|
43
34
|
## Usage
|
|
44
35
|
|
|
45
36
|
### Initialize
|
|
46
|
-
#### OAuth
|
|
47
|
-
```ruby
|
|
48
|
-
q = account.qbo_account # or wherever you are storing the OAuth creds
|
|
49
|
-
qbo_api = QboApi.new(token: q.token,
|
|
50
|
-
token_secret: q.secret,
|
|
51
|
-
realm_id: q.companyid,
|
|
52
|
-
consumer_key: '*****',
|
|
53
|
-
consumer_secret: '********')
|
|
54
|
-
```
|
|
55
|
-
#### OAuth2
|
|
56
37
|
```ruby
|
|
57
38
|
qbo_api = QboApi.new(access_token: 'REWR342532asdfae!$4asdfa', realm_id: 32095430444)
|
|
39
|
+
- qbo_api.get :customer, 1
|
|
58
40
|
```
|
|
59
41
|
|
|
60
|
-
### Super fast way to use QboApi
|
|
42
|
+
### Super fast way to use QboApi as long as Ruby >= 2.5 is installed
|
|
61
43
|
```
|
|
62
44
|
- cd ~/<local dir>
|
|
63
45
|
- git clone git@github.com:minimul/qbo_api.git && cd qbo_api
|
|
64
46
|
- bundle
|
|
65
47
|
- bin/console
|
|
66
48
|
- QboApi.production = true
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
token_secret:"g8wcyQEtwxxxxxxm",
|
|
70
|
-
realm_id: "12314xxxxxx7",
|
|
71
|
-
consumer_key: "qyprdwzcxxxxxxbIWsIMIy9PYI",
|
|
72
|
-
consumer_secret: "CyDN4wpxxxxxxxPMv7hDhmh4")
|
|
73
|
-
- # OAuth 2
|
|
74
|
-
- qboapi = QboApi.new(access_token: "qyprd2uvCOdRq8xzoSSiiiiii", realm_id: "12314xxxxxx7")
|
|
75
|
-
- qboapi.get :customer, 1
|
|
49
|
+
- qbo_api = QboApi.new(access_token: "qyprd2uvCOdRq8xzoSSiiiiii", realm_id: "12314xxxxxx7")
|
|
50
|
+
- qbo_api.get :customer, 1
|
|
76
51
|
```
|
|
77
52
|
|
|
78
|
-
### TLS v1.2
|
|
79
|
-
Intuit will be [requiring](https://developer.intuit.com/hub/blog/2017/08/03/upgrading-your-apps-to-support-tls-1-2) API client connections to be negotiated over TLS1.2 by December 31st, 2017. Using the default HTTP client (Net::HTTP) with Faraday this is the case with QboApi, however, if you are using another HTTP client you may need to directly set the TLS version negotiation manually.
|
|
80
|
-
|
|
81
53
|
### DateTime serialization
|
|
82
54
|
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.
|
|
83
55
|
If ActiveSupport is loaded, you can achieve proper serialization with the following configuration:
|
|
@@ -119,7 +91,6 @@ QboApi.minor_version = 8
|
|
|
119
91
|
# Works with .get, .create, .update, .query methods
|
|
120
92
|
```
|
|
121
93
|
|
|
122
|
-
|
|
123
94
|
### Create
|
|
124
95
|
```ruby
|
|
125
96
|
invoice = {
|
|
@@ -191,6 +162,40 @@ QboApi.minor_version = 8
|
|
|
191
162
|
p response.size # => 28
|
|
192
163
|
```
|
|
193
164
|
|
|
165
|
+
### Import/retrieve all
|
|
166
|
+
*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.*
|
|
167
|
+
```ruby
|
|
168
|
+
# retrieves all active customers
|
|
169
|
+
qbo_api.all(:customers).each do |c|
|
|
170
|
+
p "#{c['Id']} #{c['DisplayName']}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# retrieves all active or inactive employees
|
|
174
|
+
qbo_api.all(:employees, inactive: true).each do |e|
|
|
175
|
+
p "#{e['Id']} #{e['DisplayName']}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# retrieves all vendors by groups of 5
|
|
179
|
+
qbo_api.all(:vendor, max: 5).each do |v|
|
|
180
|
+
p v['DisplayName']
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# retrieves all customers by groups of 2 using a custom select query
|
|
184
|
+
where = "WHERE Id IN ('5', '6', '7', '8', '9', '10')"
|
|
185
|
+
qbo_api.all(:customer, max: 2, select: "SELECT * FROM Customer #{where}").each do |c|
|
|
186
|
+
p c['DisplayName']
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Note: .all() returns a Ruby Enumerator
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
api.all(:clients).take(50).each { |c| p c["Id"] }
|
|
194
|
+
api.all(:clients).count
|
|
195
|
+
api.all(:clients).first
|
|
196
|
+
api.all(:clients).to_a
|
|
197
|
+
```
|
|
198
|
+
|
|
194
199
|
### Search with irregular characters
|
|
195
200
|
```ruby
|
|
196
201
|
# Use the .esc() method
|
|
@@ -201,6 +206,12 @@ QboApi.minor_version = 8
|
|
|
201
206
|
p response['Id'] # => 1
|
|
202
207
|
```
|
|
203
208
|
|
|
209
|
+
|
|
210
|
+
### Email a transaction entity
|
|
211
|
+
```ruby
|
|
212
|
+
api.send_invoice(invoice_id: 1, email_address: 'billy@joe.com')
|
|
213
|
+
```
|
|
214
|
+
|
|
204
215
|
### Uploading an attachment
|
|
205
216
|
```ruby
|
|
206
217
|
payload = {"AttachableRef":
|
|
@@ -264,7 +275,7 @@ Be aware that any errors will not raise a `QboApi::Error`, but will be returned
|
|
|
264
275
|
}
|
|
265
276
|
]
|
|
266
277
|
}
|
|
267
|
-
response =
|
|
278
|
+
response = qbo_api.batch(payload)
|
|
268
279
|
expect(response['BatchItemResponse'].size).to eq 2
|
|
269
280
|
expect(batch_response.detect{ |b| b["bId"] == "bid1" }["Vendor"]["DisplayName"]).to eq "Smith Family Store"
|
|
270
281
|
```
|
|
@@ -273,7 +284,7 @@ Be aware that any errors will not raise a `QboApi::Error`, but will be returned
|
|
|
273
284
|
|
|
274
285
|
```ruby
|
|
275
286
|
params = { start_date: '2015-01-01', end_date: '2015-07-31', customer: 1, summarize_column_by: 'Customers' }
|
|
276
|
-
response =
|
|
287
|
+
response = qbo_api.reports(name: 'ProfitAndLoss', params: params)
|
|
277
288
|
p response["Header"]["ReportName"]) #=> 'ProfitAndLoss'
|
|
278
289
|
```
|
|
279
290
|
|
|
@@ -310,31 +321,6 @@ See [docs](https://developer.intuit.com/docs/0100_quickbooks_online/0100_essenti
|
|
|
310
321
|
end
|
|
311
322
|
```
|
|
312
323
|
|
|
313
|
-
### Import/retrieve all
|
|
314
|
-
*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.*
|
|
315
|
-
```ruby
|
|
316
|
-
# retrieves all active customers
|
|
317
|
-
qbo_api.all(:customers).each do |c|
|
|
318
|
-
p "#{c['Id']} #{c['DisplayName']}"
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# retrieves all active or inactive employees
|
|
322
|
-
qbo_api.all(:employees, inactive: true).each do |e|
|
|
323
|
-
p "#{e['Id']} #{e['DisplayName']}"
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# retrieves all vendors by groups of 5
|
|
327
|
-
qbo_api.all(:vendor, max: 5).each do |v|
|
|
328
|
-
p v['DisplayName']
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# retrieves all customers by groups of 2 using a custom select query
|
|
332
|
-
where = "WHERE Id IN ('5', '6', '7', '8', '9', '10')"
|
|
333
|
-
qbo_api.all(:customer, max: 2, select: "SELECT * FROM Customer #{where}").each do |c|
|
|
334
|
-
p c['DisplayName']
|
|
335
|
-
end
|
|
336
|
-
```
|
|
337
|
-
|
|
338
324
|
### What kind of QuickBooks entity?
|
|
339
325
|
```ruby
|
|
340
326
|
p qbo_api.is_transaction_entity?(:invoice) # => true
|
|
@@ -343,9 +329,8 @@ See [docs](https://developer.intuit.com/docs/0100_quickbooks_online/0100_essenti
|
|
|
343
329
|
p qbo_api.is_transaction_entity?(:customer) # => false
|
|
344
330
|
p qbo_api.is_name_list_entity?(:vendors) # => true
|
|
345
331
|
```
|
|
346
|
-
## <a name='OAuth2-example'>
|
|
347
|
-
|
|
348
|
-
- <a href="http://minimul.com/access-the-quickbooks-online-api-with-oauth2.html" target="_blank">Check out this article on spinning up the OAuth2 example</a>.
|
|
332
|
+
## <a name='OAuth2-example'>Spin up an example</a>
|
|
333
|
+
- <a href="http://minimul.com/access-the-quickbooks-online-api-with-oauth2.html" target="_blank">Check out this article on spinning up the example</a>.
|
|
349
334
|
- `git clone git://github.com/minimul/qbo_api && cd qbo_api`
|
|
350
335
|
- `bundle`
|
|
351
336
|
- Create a `.env` file
|
|
@@ -360,7 +345,7 @@ See [docs](https://developer.intuit.com/docs/0100_quickbooks_online/0100_essenti
|
|
|
360
345
|
- Create a new Company ([from the manage sandboxes page](https://developer.intuit.com/v2/ui#/sandbox))
|
|
361
346
|
Don't use it for anything else besides testing this app.
|
|
362
347
|
-. Copy the 'Company ID' to your .env as QBO_API_COMPANY_ID
|
|
363
|
-
- Start up the example
|
|
348
|
+
- Start up the example app
|
|
364
349
|
- `ruby example/oauth2.rb`
|
|
365
350
|
- Go to `http://localhost:9393/oauth2`
|
|
366
351
|
- Use the `Connect to QuickBooks` button to connect to your QuickBooks sandbox, which you receive when signing up at [https://developer.intuit.com](https://developer.intuit.com).
|
|
@@ -370,35 +355,13 @@ See [docs](https://developer.intuit.com/docs/0100_quickbooks_online/0100_essenti
|
|
|
370
355
|
- Checkout [`example/oauth2.rb`](https://github.com/minimul/qbo_api/blob/master/example/oauth2.rb)
|
|
371
356
|
to see what is going on under the hood.
|
|
372
357
|
|
|
373
|
-
## OAuth1: Spin up an example
|
|
374
|
-
### OLD LEGACY - SEE OAUTH2 EXAMPLE ABOVE
|
|
375
|
-
- <a href="http://minimul.com/getting-started-with-the-modern-ruby-quickbooks-online-client-qbo_api-part-1.html" target="_blank">Check out this tutorial and screencast on spinning up an example</a>.
|
|
376
|
-
- `git clone git://github.com/minimul/qbo_api && cd qbo_api`
|
|
377
|
-
- `bundle`
|
|
378
|
-
- Create a `.env` file
|
|
379
|
-
- `cp .env.example_app.oauth1 .env`
|
|
380
|
-
- If needed create an account at [https://developer.intuit.com](https://developer.intuit.com)
|
|
381
|
-
- Click `Get started coding`
|
|
382
|
-
- Create an app with both the `Accounting` & `Payments` selected.
|
|
383
|
-
- Go to the `Development` tab and copy and paste the consumer key and secret into the `.env` file
|
|
384
|
-
as QBO_API_CONSUMER_KEY and QBO_API_CONSUMER_SECRET respectively.
|
|
385
|
-
- Start up the example app
|
|
386
|
-
- `ruby example/oauth.rb`
|
|
387
|
-
- Go to `http://localhost:9393`
|
|
388
|
-
- Use the `Connect to QuickBooks` button to connect to your QuickBooks sandbox, which you receive when signing up at [https://developer.intuit.com](https://developer.intuit.com).
|
|
389
|
-
- After successfully connecting to your sandbox run:
|
|
390
|
-
- `http://localhost:9393/customer/5`
|
|
391
|
-
- You should see "Dukes Basketball Camp" displayed
|
|
392
|
-
- Checkout [`example/oauth.rb`](https://github.com/minimul/qbo_api/blob/master/example/oauth.rb)
|
|
393
|
-
to see what is going on under the hood.
|
|
394
|
-
|
|
395
358
|
## Webhooks
|
|
396
359
|
- <a href="http://minimul.com/getting-started-with-quickbooks-online-webhooks.html" target="_blank">Check out this tutorial and screencast on handling a webhook request</a>. Also checkout [`example/app.rb`](https://github.com/minimul/qbo_api/blob/master/example/app.rb) for the request handling code.
|
|
397
360
|
|
|
398
361
|
See https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html
|
|
399
362
|
for how to install ngrok and what it is.
|
|
400
363
|
|
|
401
|
-
- With
|
|
364
|
+
- With the example app running, run:
|
|
402
365
|
`ngrok http 9393 -subdomain=somereasonablyuniquenamehere`
|
|
403
366
|
|
|
404
367
|
- Go to the [`Development` tab](https://developer.intuit.com/v2/ui#/app/dashboard)
|
|
@@ -408,9 +371,9 @@ for how to install ngrok and what it is.
|
|
|
408
371
|
- After saving the webhook, click 'show token'.
|
|
409
372
|
Add the token to your .env as QBO_API_VERIFIER_TOKEN
|
|
410
373
|
|
|
411
|
-
- In another tab, create a customer via the
|
|
412
|
-
`bundle exec ruby -rqbo_api -rdotenv -e 'Dotenv.load; p QboApi.new(access_token: ENV.fetch("
|
|
413
|
-
(You'll also need to have added the QBO_API_COMPANY_ID and
|
|
374
|
+
- In another tab, create a customer via the API:
|
|
375
|
+
`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" })'`
|
|
376
|
+
(You'll also need to have added the QBO_API_COMPANY_ID and QBO_API_ACCESS_TOKEN to your .env)
|
|
414
377
|
|
|
415
378
|
There could be a delay of up to a minute before the webhook fires.
|
|
416
379
|
|
|
@@ -421,6 +384,28 @@ for how to install ngrok and what it is.
|
|
|
421
384
|
"POST /webhooks HTTP/1.1" 200 - 0.0013
|
|
422
385
|
```
|
|
423
386
|
|
|
387
|
+
### Just For Hackers
|
|
388
|
+
|
|
389
|
+
- Using the build_connection method
|
|
390
|
+
```
|
|
391
|
+
connection = build_connection('https://oauth.platform.intuit.com', headers: { 'Accept' => 'application/json' }) do |conn|
|
|
392
|
+
conn.basic_auth(client_id, client_secret)
|
|
393
|
+
conn.request :url_encoded # application/x-www-form-urlencoded
|
|
394
|
+
conn.response :json
|
|
395
|
+
conn.use QboApi::RaiseHttpException
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
raw_response = connection.post do |req|
|
|
399
|
+
req.body = { grant_type: :refresh_token, refresh_token: current_refresh_token }
|
|
400
|
+
req.url '/oauth2/v1/tokens/bearer'
|
|
401
|
+
end
|
|
402
|
+
```
|
|
403
|
+
- Once your .env file is completely filled out you can use the console to play around in your sandbox
|
|
404
|
+
```
|
|
405
|
+
bin/console test
|
|
406
|
+
>> @qbo_api.get :customer, 1
|
|
407
|
+
```
|
|
408
|
+
|
|
424
409
|
## Contributing
|
|
425
410
|
|
|
426
411
|
Bug reports and pull requests are welcome on GitHub at https://github.com/minimul/qbo_api.
|
|
@@ -438,11 +423,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/minimu
|
|
|
438
423
|
- All specs that require interaction with the API must be recorded against your personal QuickBooks sandbox. More coming on how to create or modifying existing specs against your sandbox.
|
|
439
424
|
- <a href="http://minimul.com/the-modern-ruby-quickbooks-client-contributing.html" target="_blank">Check out this tutorial and screencast on contributing to qbo_api</a>.
|
|
440
425
|
|
|
441
|
-
#### Protip: Once your .env file is completely filled out you can use the console to play around in your sandbox
|
|
442
|
-
```
|
|
443
|
-
bin/console test
|
|
444
|
-
>> @qbo_api.get :customer, 1
|
|
445
|
-
```
|
|
446
426
|
|
|
447
427
|
## License
|
|
448
428
|
|
data/example/base.rb
CHANGED
|
@@ -3,6 +3,7 @@ BASE_GEMS = proc do
|
|
|
3
3
|
# This app
|
|
4
4
|
gem 'sinatra'
|
|
5
5
|
gem 'sinatra-contrib'
|
|
6
|
+
gem 'puma'
|
|
6
7
|
|
|
7
8
|
# Creds from ../.env
|
|
8
9
|
gem 'dotenv'
|
|
@@ -25,6 +26,7 @@ BASE_APP_CONFIG = proc do
|
|
|
25
26
|
configure do
|
|
26
27
|
$VERBOSE = nil # silence redefined constant warning
|
|
27
28
|
register Sinatra::Reloader
|
|
29
|
+
set :public_folder => "#{__dir__}/public"
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
set :sessions, :true
|
data/example/oauth2.rb
CHANGED
|
@@ -21,19 +21,32 @@ class OAuth2App < Sinatra::Base
|
|
|
21
21
|
|
|
22
22
|
helpers do
|
|
23
23
|
def oauth2_client
|
|
24
|
-
|
|
24
|
+
Rack::OAuth2::Client.new(
|
|
25
25
|
identifier: CLIENT_ID,
|
|
26
26
|
secret: CLIENT_SECRET,
|
|
27
|
-
redirect_uri:
|
|
27
|
+
redirect_uri: redirect_url,
|
|
28
28
|
authorization_endpoint: "https://appcenter.intuit.com/connect/oauth2",
|
|
29
29
|
token_endpoint: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
|
|
30
30
|
)
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
def authorize_url(state:)
|
|
34
|
+
oauth2_client.authorization_uri(
|
|
35
|
+
client_id: CLIENT_ID,
|
|
36
|
+
scope: "com.intuit.quickbooks.accounting",
|
|
37
|
+
redirect_uri: redirect_url,
|
|
38
|
+
response_type: "code",
|
|
39
|
+
state: state
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def redirect_url
|
|
44
|
+
"http://localhost:#{PORT}/oauth2-redirect"
|
|
45
|
+
end
|
|
32
46
|
end
|
|
33
47
|
|
|
34
48
|
get '/oauth2' do
|
|
35
49
|
session[:state] = SecureRandom.uuid
|
|
36
|
-
@client = oauth2_client
|
|
37
50
|
erb :oauth2
|
|
38
51
|
end
|
|
39
52
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" style="isolation:isolate" viewBox="0 0 775.39 132"><defs><clipPath id="a"><path d="M0 0h775.39v132H0z"/></clipPath></defs><g clip-path="url(#a)"><path d="M775.395 124c0 4.4-3.6 8-8 8H8c-4.4 0-8-3.6-8-8V8c0-4.4 3.6-8 8-8h759.395c4.4 0 8 3.6 8 8v116z" fill="#328538"/><path d="M775.395 124c0 4.4-3.6 8-8 8H8c-4.4 0-8-3.6-8-8V8c0-4.4 3.6-8 8-8h759.395c4.4 0 8 3.6 8 8v116z" fill="none" vector-effect="non-scaling-stroke" stroke-width="4" stroke="#328538" stroke-miterlimit="10"/><path d="M178.168 56.127c-1.033-1.4-2.38-2.424-4.039-3.07-1.66-.645-3.284-.968-4.869-.968-2.028 0-3.872.368-5.532 1.106a13.005 13.005 0 0 0-4.287 3.042c-1.198 1.292-2.121 2.804-2.766 4.538-.646 1.733-.968 3.614-.968 5.642 0 2.14.313 4.093.941 5.864.627 1.771 1.521 3.291 2.683 4.563a12.012 12.012 0 0 0 4.176 2.96c1.622.703 3.448 1.052 5.477 1.052 2.103 0 3.964-.415 5.588-1.244 1.623-.831 2.932-1.928 3.928-3.292l5.587 3.926a17.878 17.878 0 0 1-6.362 5.063c-2.509 1.198-5.441 1.799-8.796 1.799-3.062 0-5.873-.508-8.437-1.523-2.564-1.014-4.767-2.433-6.611-4.258-1.844-1.826-3.282-4.002-4.314-6.53-1.034-2.524-1.55-5.318-1.55-8.38 0-3.134.544-5.965 1.632-8.492 1.088-2.527 2.582-4.674 4.482-6.445 1.898-1.77 4.14-3.135 6.721-4.094 2.581-.958 5.384-1.437 8.408-1.437 1.255 0 2.564.119 3.928.359 1.365.24 2.675.608 3.928 1.105a19.141 19.141 0 0 1 3.541 1.855 11.42 11.42 0 0 1 2.82 2.654l-5.309 4.205m13.056 16.597c0 1.032.156 2.065.471 3.097a8.468 8.468 0 0 0 1.41 2.767 7.381 7.381 0 0 0 2.378 1.991c.96.517 2.102.774 3.43.774 1.328 0 2.471-.257 3.43-.774a7.42 7.42 0 0 0 2.38-1.991 8.566 8.566 0 0 0 1.41-2.767 10.66 10.66 0 0 0 .47-3.097c0-1.033-.158-2.056-.47-3.071a8.637 8.637 0 0 0-1.41-2.738 7.1 7.1 0 0 0-2.38-1.963c-.959-.5-2.102-.748-3.43-.748-1.328 0-2.47.248-3.43.748a7.063 7.063 0 0 0-2.378 1.963 8.536 8.536 0 0 0-1.41 2.738 10.303 10.303 0 0 0-.471 3.071zm-6.749 0c0-2.103.377-4.011 1.134-5.726.755-1.714 1.779-3.181 3.07-4.398 1.29-1.217 2.821-2.157 4.592-2.821 1.77-.664 3.65-.996 5.642-.996 1.992 0 3.872.332 5.643.996 1.771.664 3.3 1.604 4.592 2.821 1.291 1.217 2.313 2.684 3.071 4.398.754 1.715 1.133 3.623 1.133 5.726 0 2.103-.379 4.02-1.133 5.753-.758 1.733-1.78 3.218-3.071 4.454-1.292 1.236-2.821 2.203-4.592 2.903-1.771.7-3.651 1.052-5.643 1.052s-3.872-.352-5.642-1.052c-1.771-.7-3.302-1.667-4.592-2.903-1.291-1.236-2.315-2.721-3.07-4.454-.757-1.733-1.134-3.65-1.134-5.753m33.91-13.167h6.307v4.26h.111c.589-1.328 1.612-2.498 3.069-3.513 1.457-1.015 3.181-1.521 5.173-1.521 1.734 0 3.218.303 4.454.912 1.234.609 2.249 1.412 3.042 2.408a9.715 9.715 0 0 1 1.742 3.429 14.43 14.43 0 0 1 .554 3.983v16.597h-6.638V71.396c0-.775-.056-1.586-.167-2.435a6.59 6.59 0 0 0-.719-2.294 4.775 4.775 0 0 0-1.521-1.688c-.647-.443-1.504-.664-2.572-.664-1.071 0-1.992.212-2.767.637a5.872 5.872 0 0 0-1.908 1.631 7.188 7.188 0 0 0-1.134 2.296 9.124 9.124 0 0 0-.387 2.628v14.605h-6.639V59.557m30.814 0h6.306v4.26h.111c.588-1.328 1.612-2.498 3.069-3.513 1.458-1.015 3.182-1.521 5.174-1.521 1.732 0 3.217.303 4.452.912 1.236.609 2.25 1.412 3.044 2.408a9.713 9.713 0 0 1 1.741 3.429c.369 1.292.553 2.619.553 3.983v16.597h-6.638V71.396c0-.775-.055-1.586-.166-2.435a6.569 6.569 0 0 0-.718-2.294 4.788 4.788 0 0 0-1.522-1.688c-.646-.443-1.504-.664-2.572-.664-1.07 0-1.992.212-2.766.637a5.851 5.851 0 0 0-1.908 1.631 7.169 7.169 0 0 0-1.135 2.296 9.124 9.124 0 0 0-.387 2.628v14.605h-6.638V59.557m49.51 10.622c0-.848-.12-1.66-.358-2.435a5.614 5.614 0 0 0-1.135-2.047c-.516-.588-1.171-1.06-1.964-1.411-.793-.349-1.724-.525-2.793-.525-1.992 0-3.679.6-5.063 1.799-1.383 1.198-2.148 2.737-2.295 4.619h13.608zm6.639 2.988v.885c0 .294-.019.589-.055.884h-20.192c.072.96.322 1.836.747 2.628a6.69 6.69 0 0 0 1.687 2.048 8.437 8.437 0 0 0 2.378 1.355 7.86 7.86 0 0 0 2.767.497c1.659 0 3.06-.304 4.204-.912a7.856 7.856 0 0 0 2.821-2.518l4.426 3.541c-2.619 3.541-6.418 5.311-11.396 5.311-2.066 0-3.964-.323-5.698-.969-1.734-.644-3.236-1.557-4.509-2.738-1.272-1.179-2.268-2.627-2.987-4.342-.72-1.716-1.078-3.661-1.078-5.837 0-2.139.358-4.084 1.078-5.836.719-1.752 1.706-3.245 2.96-4.481 1.254-1.235 2.738-2.194 4.452-2.878 1.716-.681 3.57-1.022 5.56-1.022 1.844 0 3.55.303 5.118.912a11.122 11.122 0 0 1 4.066 2.71c1.143 1.2 2.038 2.693 2.683 4.483.645 1.788.968 3.881.968 6.279m23.179-6.252c-.518-.738-1.291-1.355-2.324-1.852-1.032-.5-2.084-.748-3.152-.748-1.218 0-2.288.248-3.21.748a6.992 6.992 0 0 0-2.296 1.962 8.446 8.446 0 0 0-1.354 2.739 10.907 10.907 0 0 0-.443 3.07c0 1.033.156 2.057.471 3.07a8.538 8.538 0 0 0 1.409 2.739 7.183 7.183 0 0 0 2.352 1.964c.94.498 2.037.746 3.292.746a8.386 8.386 0 0 0 3.097-.608c1.032-.405 1.862-.996 2.49-1.77l4.149 4.204c-1.107 1.181-2.517 2.094-4.232 2.738-1.715.646-3.568.969-5.56.969-1.956 0-3.808-.314-5.56-.941-1.752-.626-3.283-1.549-4.592-2.766-1.309-1.218-2.341-2.691-3.097-4.426-.756-1.733-1.134-3.707-1.134-5.919 0-2.138.378-4.074 1.134-5.809.756-1.733 1.778-3.208 3.07-4.425a13.728 13.728 0 0 1 4.508-2.821c1.715-.664 3.55-.996 5.506-.996 1.954 0 3.844.369 5.67 1.106 1.826.738 3.272 1.752 4.342 3.043l-4.536 3.983m6.417-2.047v-5.311h4.647v-7.689h6.528v7.689h6.638v5.311h-6.638v12.336c0 1.181.212 2.157.636 2.932.424.774 1.373 1.161 2.849 1.161.443 0 .921-.045 1.439-.137a5.718 5.718 0 0 0 1.382-.415l.222 5.2c-.591.222-1.291.395-2.103.526-.811.129-1.585.194-2.323.194-1.77 0-3.209-.25-4.314-.748-1.107-.497-1.984-1.18-2.628-2.047-.646-.865-1.088-1.862-1.328-2.986a17.355 17.355 0 0 1-.36-3.624V64.868h-4.647m29.872 0v-5.311h4.648v-7.689h6.527v7.689h6.638v5.311h-6.638v12.336c0 1.181.212 2.157.637 2.932.423.774 1.373 1.161 2.849 1.161.443 0 .92-.045 1.438-.137a5.704 5.704 0 0 0 1.382-.415l.222 5.2c-.591.222-1.291.395-2.102.526-.812.129-1.586.194-2.324.194-1.77 0-3.208-.25-4.314-.748-1.107-.497-1.983-1.18-2.628-2.047-.646-.865-1.088-1.862-1.328-2.986a17.356 17.356 0 0 1-.359-3.624V64.868h-4.648m26.444 7.856c0 1.032.156 2.065.469 3.097a8.512 8.512 0 0 0 1.412 2.767 7.381 7.381 0 0 0 2.378 1.991c.958.517 2.102.774 3.43.774 1.328 0 2.471-.257 3.43-.774a7.392 7.392 0 0 0 2.378-1.991 8.531 8.531 0 0 0 1.412-2.767 10.66 10.66 0 0 0 .47-3.097c0-1.033-.158-2.056-.47-3.071a8.601 8.601 0 0 0-1.412-2.738 7.072 7.072 0 0 0-2.378-1.963c-.959-.5-2.102-.748-3.43-.748-1.328 0-2.472.248-3.43.748a7.063 7.063 0 0 0-2.378 1.963 8.58 8.58 0 0 0-1.412 2.738 10.365 10.365 0 0 0-.469 3.071zm-6.749 0c0-2.103.377-4.011 1.133-5.726.756-1.714 1.779-3.181 3.071-4.398 1.29-1.217 2.821-2.157 4.592-2.821 1.77-.664 3.65-.996 5.642-.996 1.992 0 3.872.332 5.643.996 1.771.664 3.3 1.604 4.592 2.821 1.291 1.217 2.313 2.684 3.071 4.398.754 1.715 1.133 3.623 1.133 5.726 0 2.103-.379 4.02-1.133 5.753-.758 1.733-1.78 3.218-3.071 4.454-1.292 1.236-2.821 2.203-4.592 2.903-1.771.7-3.651 1.052-5.643 1.052s-3.872-.352-5.642-1.052c-1.771-.7-3.302-1.667-4.592-2.903-1.292-1.236-2.315-2.721-3.071-4.454-.756-1.733-1.133-3.65-1.133-5.753" fill="#FFF"/><path d="M449.899 52.2c-1.882 0-3.643.351-5.283 1.051-1.641.701-3.053 1.658-4.233 2.876-1.18 1.217-2.111 2.674-2.794 4.371-.682 1.697-1.024 3.541-1.024 5.531 0 2.103.323 4.011.968 5.727.646 1.715 1.551 3.18 2.712 4.397 1.162 1.217 2.554 2.167 4.176 2.85 1.623.682 3.412 1.022 5.367 1.022 1.917 0 3.696-.34 5.339-1.022 1.64-.683 3.05-1.633 4.232-2.85 1.178-1.217 2.101-2.682 2.765-4.397.664-1.716.996-3.624.996-5.727 0-1.99-.313-3.834-.94-5.531-.628-1.697-1.521-3.154-2.683-4.371-1.162-1.218-2.554-2.175-4.177-2.876-1.623-.7-3.429-1.051-5.421-1.051zm23.51 33.912h-23.621c-3.136 0-5.984-.452-8.548-1.356-2.563-.904-4.757-2.213-6.583-3.928-1.825-1.715-3.236-3.818-4.232-6.307-.996-2.489-1.493-5.318-1.493-8.492 0-2.986.525-5.707 1.576-8.16 1.052-2.452 2.517-4.564 4.399-6.333 1.88-1.771 4.101-3.144 6.665-4.123 2.564-.976 5.339-1.464 8.327-1.464 2.986 0 5.753.488 8.298 1.464 2.544.979 4.748 2.352 6.611 4.123 1.861 1.769 3.309 3.881 4.341 6.333 1.034 2.453 1.551 5.174 1.551 8.16 0 1.771-.204 3.393-.609 4.868-.407 1.476-.932 2.813-1.578 4.011a15.475 15.475 0 0 1-2.212 3.155 16.224 16.224 0 0 1-2.573 2.294v.111h9.681v5.644m27.439 0h-6.307v-4.261h-.11c-.591 1.328-1.614 2.498-3.07 3.513-1.457 1.015-3.181 1.522-5.173 1.522-1.733 0-3.217-.305-4.453-.913-1.236-.609-2.25-1.41-3.042-2.408a9.677 9.677 0 0 1-1.744-3.429 14.461 14.461 0 0 1-.553-3.983V59.557h6.639v14.716c0 .773.056 1.586.166 2.434.111.849.351 1.613.719 2.296a4.767 4.767 0 0 0 1.521 1.686c.646.443 1.503.664 2.572.664 1.034 0 1.946-.212 2.74-.636.792-.424 1.438-.968 1.936-1.632a7.23 7.23 0 0 0 1.134-2.296 9.19 9.19 0 0 0 .386-2.626V59.557h6.639v26.555m6.693-26.555h6.639v26.555h-6.639V59.557zm-.941-9.017c0-1.069.397-2 1.189-2.795.794-.792 1.799-1.188 3.016-1.188 1.218 0 2.24.379 3.07 1.134.83.757 1.245 1.705 1.245 2.849 0 1.143-.415 2.093-1.245 2.849-.83.756-1.852 1.133-3.07 1.133-1.217 0-2.222-.395-3.016-1.188-.792-.793-1.189-1.724-1.189-2.794zm32.419 16.375c-.518-.738-1.291-1.355-2.324-1.852-1.032-.5-2.084-.748-3.154-.748-1.217 0-2.286.248-3.208.748a6.962 6.962 0 0 0-2.296 1.962 8.47 8.47 0 0 0-1.356 2.739 10.978 10.978 0 0 0-.442 3.07c0 1.033.157 2.057.47 3.07a8.583 8.583 0 0 0 1.411 2.739 7.183 7.183 0 0 0 2.352 1.964c.94.498 2.036.746 3.291.746a8.395 8.395 0 0 0 3.098-.608c1.032-.405 1.862-.996 2.49-1.77l4.149 4.204c-1.107 1.181-2.517 2.094-4.232 2.738-1.716.646-3.568.969-5.56.969-1.956 0-3.809-.314-5.56-.941-1.752-.626-3.283-1.549-4.592-2.766-1.309-1.218-2.343-2.691-3.097-4.426-.758-1.733-1.135-3.707-1.135-5.919 0-2.138.377-4.074 1.135-5.809.754-1.733 1.778-3.208 3.07-4.425a13.718 13.718 0 0 1 4.508-2.821c1.715-.664 3.55-.996 5.504-.996 1.955 0 3.846.369 5.671 1.106 1.825.738 3.272 1.752 4.343 3.043l-4.536 3.983m7.578-22.627h6.639v26.444h.165l10.07-11.175h8.518l-11.506 12.006 12.225 14.549h-8.795l-10.512-13.609h-.165v13.609h-6.639V44.288zm35.847 35.958h6.252c.885 0 1.843-.063 2.876-.193a9.396 9.396 0 0 0 2.849-.802 5.593 5.593 0 0 0 2.158-1.77c.572-.774.857-1.808.857-3.097 0-2.065-.701-3.504-2.101-4.316-1.403-.811-3.523-1.217-6.363-1.217h-6.528v11.395zm0-17.37h6.196c2.324 0 4.093-.46 5.311-1.383 1.217-.921 1.826-2.23 1.826-3.928 0-1.77-.628-3.022-1.881-3.761-1.255-.739-3.209-1.107-5.864-1.107h-5.588v10.179zm-6.969-15.932h15.268c1.474 0 2.932.175 4.37.525 1.439.351 2.719.923 3.844 1.715 1.126.793 2.038 1.817 2.739 3.071.7 1.253 1.052 2.765 1.052 4.536 0 2.213-.628 4.038-1.881 5.477-1.255 1.439-2.896 2.471-4.924 3.097v.111c2.47.332 4.5 1.292 6.085 2.877 1.585 1.587 2.379 3.707 2.379 6.363 0 2.139-.424 3.936-1.272 5.393-.85 1.456-1.964 2.627-3.348 3.512-1.383.886-2.968 1.522-4.756 1.91a25.792 25.792 0 0 1-5.45.581h-14.106V46.944m39.277 25.78c0 1.032.156 2.065.469 3.097a8.49 8.49 0 0 0 1.411 2.767 7.395 7.395 0 0 0 2.379 1.991c.958.517 2.102.774 3.43.774 1.327 0 2.47-.257 3.43-.774a7.413 7.413 0 0 0 2.378-1.991 8.51 8.51 0 0 0 1.411-2.767c.313-1.032.471-2.065.471-3.097 0-1.033-.158-2.056-.471-3.071a8.578 8.578 0 0 0-1.411-2.738 7.092 7.092 0 0 0-2.378-1.963c-.96-.5-2.103-.748-3.43-.748-1.328 0-2.472.248-3.43.748a7.076 7.076 0 0 0-2.379 1.963 8.558 8.558 0 0 0-1.411 2.738 10.365 10.365 0 0 0-.469 3.071zm-6.749 0c0-2.103.377-4.011 1.133-5.726.756-1.714 1.779-3.181 3.071-4.398 1.29-1.217 2.821-2.157 4.59-2.821 1.771-.664 3.652-.996 5.644-.996 1.991 0 3.872.332 5.643.996 1.769.664 3.3 1.604 4.591 2.821 1.29 1.217 2.314 2.684 3.07 4.398.756 1.715 1.135 3.623 1.135 5.726 0 2.103-.379 4.02-1.135 5.753-.756 1.733-1.78 3.218-3.07 4.454-1.291 1.236-2.822 2.203-4.591 2.903-1.771.7-3.652 1.052-5.643 1.052-1.992 0-3.873-.352-5.644-1.052-1.769-.7-3.3-1.667-4.59-2.903-1.292-1.236-2.315-2.721-3.071-4.454-.756-1.733-1.133-3.65-1.133-5.753m39.11 0c0 1.032.156 2.065.471 3.097a8.47 8.47 0 0 0 1.411 2.767 7.367 7.367 0 0 0 2.377 1.991c.96.517 2.103.774 3.431.774 1.328 0 2.47-.257 3.429-.774a7.41 7.41 0 0 0 2.38-1.991 8.588 8.588 0 0 0 1.411-2.767c.312-1.032.469-2.065.469-3.097 0-1.033-.157-2.056-.469-3.071a8.66 8.66 0 0 0-1.411-2.738 7.09 7.09 0 0 0-2.38-1.963c-.959-.5-2.101-.748-3.429-.748-1.328 0-2.471.248-3.431.748a7.05 7.05 0 0 0-2.377 1.963 8.538 8.538 0 0 0-1.411 2.738 10.303 10.303 0 0 0-.471 3.071zm-6.749 0c0-2.103.377-4.011 1.135-5.726.754-1.714 1.778-3.181 3.069-4.398 1.291-1.217 2.821-2.157 4.592-2.821 1.771-.664 3.651-.996 5.643-.996s3.872.332 5.642.996c1.771.664 3.3 1.604 4.592 2.821 1.291 1.217 2.314 2.684 3.071 4.398.755 1.715 1.133 3.623 1.133 5.726 0 2.103-.378 4.02-1.133 5.753-.757 1.733-1.78 3.218-3.071 4.454-1.292 1.236-2.821 2.203-4.592 2.903-1.77.7-3.65 1.052-5.642 1.052-1.992 0-3.872-.352-5.643-1.052-1.771-.7-3.301-1.667-4.592-2.903-1.291-1.236-2.315-2.721-3.069-4.454-.758-1.733-1.135-3.65-1.135-5.753m34.021-28.436h6.638v26.444h.166l10.068-11.175h8.52l-11.506 12.006 12.226 14.549H691.7l-10.511-13.609h-.166v13.609h-6.638V44.288z" fill="#F4F5F5"/><path d="M717.149 66.639c-.589-.775-1.402-1.448-2.433-2.02a6.867 6.867 0 0 0-3.376-.858c-1.069 0-2.047.222-2.932.664-.885.443-1.328 1.182-1.328 2.214 0 1.033.489 1.759 1.467 2.185.976.424 2.405.856 4.288 1.298.996.222 2 .518 3.014.886 1.014.369 1.936.857 2.767 1.467a7.23 7.23 0 0 1 2.019 2.268c.516.903.773 2.001.773 3.291 0 1.623-.303 2.997-.912 4.122a7.773 7.773 0 0 1-2.433 2.739c-1.015.7-2.196 1.208-3.542 1.52a18.318 18.318 0 0 1-4.177.471c-2.064 0-4.075-.379-6.029-1.134-1.956-.756-3.579-1.835-4.868-3.237l4.37-4.094c.738.96 1.695 1.752 2.876 2.379 1.18.628 2.49.94 3.928.94.479 0 .968-.055 1.466-.165.498-.111.96-.286 1.384-.526.424-.24.764-.561 1.024-.968.256-.406.386-.904.386-1.493 0-1.107-.508-1.9-1.521-2.379-1.016-.48-2.536-.96-4.564-1.438a21.777 21.777 0 0 1-2.904-.859 9.374 9.374 0 0 1-2.517-1.383 6.382 6.382 0 0 1-1.771-2.129c-.443-.848-.664-1.899-.664-3.154 0-1.474.304-2.746.912-3.817a7.867 7.867 0 0 1 2.408-2.628c.995-.682 2.12-1.188 3.373-1.52a15.082 15.082 0 0 1 3.874-.498c1.917 0 3.789.332 5.614.996 1.827.664 3.274 1.677 4.343 3.042l-4.315 3.818" fill="#F4F5F5"/><path d="M95.897 66.634c0-9.662-7.862-17.524-17.525-17.524h-1.604v6.157h1.604c6.269 0 11.368 5.1 11.368 11.367 0 6.27-5.099 11.37-11.368 11.37h-5.507v-37.37h-6.157V84.16h11.664c9.663 0 17.525-7.862 17.525-17.526zM62.793 49.11H51.13c-9.663 0-17.525 7.862-17.525 17.524 0 9.664 7.862 17.526 17.525 17.526h1.604v-6.156H51.13c-6.269 0-11.369-5.1-11.369-11.37 0-6.267 5.1-11.367 11.369-11.367h5.506v37.367h6.157V49.11zm45.991 17.557c0 24.301-19.7 44-44 44-24.301 0-44-19.699-44-44 0-24.3 19.699-44 44-44 24.3 0 44 19.7 44 44z" fill="#FFF"/></g></svg>
|
data/example/views/oauth2.erb
CHANGED
|
@@ -3,20 +3,10 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<title>QBO Connect via OAuth2</title>
|
|
6
|
-
|
|
7
|
-
<script type="text/javascript" src="<%= QboApi::APP_CENTER_BASE %>/Content/IA/intuit.ipp.anywhere-1.3.1.js"></script>
|
|
8
|
-
<script>
|
|
9
|
-
intuit.ipp.anywhere.setup({
|
|
10
|
-
grantUrl: "<%= @client.authorization_uri(scope: 'com.intuit.quickbooks.accounting', state: session[:state]) %>",
|
|
11
|
-
datasources: {
|
|
12
|
-
quickbooks : true,
|
|
13
|
-
payments : true
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
</script>
|
|
17
6
|
</head>
|
|
18
|
-
<body>
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
<body style="padding:15px;">
|
|
8
|
+
<a href="<%= authorize_url(state: session[:state]) %>">
|
|
9
|
+
<img src="connect-to-qbo.svg" alt="Connect to QuickBooks Online Button" width="400">
|
|
10
|
+
</a>
|
|
21
11
|
</body>
|
|
22
12
|
</html>
|
data/lib/qbo_api/api_methods.rb
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
class QboApi
|
|
2
2
|
module ApiMethods
|
|
3
|
-
|
|
4
3
|
def all(entity, max: 1000, select: nil, inactive: false, params: nil, &block)
|
|
5
|
-
enumerator = create_all_enumerator(entity, max: max, select: select,
|
|
6
|
-
|
|
4
|
+
enumerator = create_all_enumerator(entity, max: max, select: select,
|
|
5
|
+
inactive: inactive, params: params)
|
|
7
6
|
if block_given?
|
|
8
7
|
enumerator.each(&block)
|
|
9
8
|
else
|
|
@@ -17,9 +16,6 @@ class QboApi
|
|
|
17
16
|
request(:get, entity: entity, path: path, params: params)
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
# @example
|
|
21
|
-
# get(:customer, 5)
|
|
22
|
-
# @see #get_by_query_filter
|
|
23
19
|
def get(entity, id_or_query_filter_args, params: nil)
|
|
24
20
|
if id_or_query_filter_args.is_a?(Array)
|
|
25
21
|
get_by_query_filter(entity, id_or_query_filter_args, params: params)
|
|
@@ -29,11 +25,6 @@ class QboApi
|
|
|
29
25
|
end
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
# @example
|
|
33
|
-
# get_by_query_filter(:customer, ["DisplayName", "Dukes Basketball Camp"])
|
|
34
|
-
# get_by_query_filter(:customer, ["DisplayName", "LIKE", "Dukes%"])
|
|
35
|
-
# get_by_query_filter(:vendor, ["DisplayName", "IN", "(true, false)"])
|
|
36
|
-
# get_by_query_filter(:customer, ["DisplayName", "Amy's Bird Sanctuary"])
|
|
37
28
|
def get_by_query_filter(entity, query_filter_args, params: nil)
|
|
38
29
|
query_str = get_query_str(entity, query_filter_args)
|
|
39
30
|
if resp = query(query_str, params: params)
|
|
@@ -67,6 +58,14 @@ class QboApi
|
|
|
67
58
|
request(:post, entity: entity, path: entity_path(entity), payload: payload)
|
|
68
59
|
end
|
|
69
60
|
|
|
61
|
+
def void(entity, id:)
|
|
62
|
+
err_msg = "Void is only for voidable transaction entities. Use .delete or .deactivate instead"
|
|
63
|
+
raise QboApi::NotImplementedError.new, err_msg unless is_voidable_transaction_entity?(entity)
|
|
64
|
+
path = add_params_to_path(path: entity_path(entity), params: { operation: :void })
|
|
65
|
+
payload = set_update(entity, id)
|
|
66
|
+
request(:post, entity: entity, path: path, payload: payload)
|
|
67
|
+
end
|
|
68
|
+
|
|
70
69
|
private
|
|
71
70
|
|
|
72
71
|
def get_query_str(entity, query_filter_args)
|
|
@@ -128,7 +127,7 @@ class QboApi
|
|
|
128
127
|
payload = build_update(resp).merge('sparse': true, 'Active': false)
|
|
129
128
|
|
|
130
129
|
case singular(entity)
|
|
131
|
-
when 'Account'
|
|
130
|
+
when 'Account', 'Class'
|
|
132
131
|
payload['Name'] = resp['Name']
|
|
133
132
|
end
|
|
134
133
|
payload
|
|
@@ -143,6 +142,5 @@ class QboApi
|
|
|
143
142
|
resp = get(entity, id)
|
|
144
143
|
build_deactivate(entity, resp)
|
|
145
144
|
end
|
|
146
|
-
|
|
147
145
|
end
|
|
148
146
|
end
|
data/lib/qbo_api/attachment.rb
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
class QboApi
|
|
2
2
|
module Attachment
|
|
3
|
+
def read_attachment(id:)
|
|
4
|
+
raw_response = connection.get do |request|
|
|
5
|
+
request.url "#{realm_id}/attachable/#{id}"
|
|
6
|
+
end
|
|
7
|
+
response(raw_response, entity: :attachable)
|
|
8
|
+
end
|
|
3
9
|
|
|
4
10
|
def upload_attachment(payload:, attachment:)
|
|
5
11
|
content_type = payload['ContentType'] || payload[:ContentType]
|
|
@@ -7,19 +13,28 @@ class QboApi
|
|
|
7
13
|
raw_response = attachment_connection.post do |request|
|
|
8
14
|
request.url "#{realm_id}/upload"
|
|
9
15
|
request.body = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
'file_metadata_01':
|
|
17
|
+
Faraday::UploadIO.new(StringIO.new(payload.to_json), 'application/json', 'attachment.json'),
|
|
18
|
+
'file_content_01':
|
|
19
|
+
Faraday::UploadIO.new(attachment, content_type, file_name)
|
|
14
20
|
}
|
|
15
21
|
end
|
|
16
22
|
response(raw_response, entity: :attachable)
|
|
17
23
|
end
|
|
18
24
|
|
|
25
|
+
# The `attachable` must be the full payload returned in the read response
|
|
26
|
+
def delete_attachment(attachable:)
|
|
27
|
+
raw_response = connection.post do |request|
|
|
28
|
+
request.url "#{realm_id}/attachable?operation=delete"
|
|
29
|
+
request.body = attachable.to_json
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
response(raw_response, entity: :attachable)
|
|
33
|
+
end
|
|
34
|
+
|
|
19
35
|
def attachment_connection
|
|
20
36
|
@attachment_connection ||= authorized_multipart_connection(endpoint_url)
|
|
21
37
|
end
|
|
22
|
-
|
|
23
38
|
end
|
|
24
39
|
end
|
|
25
40
|
|
data/lib/qbo_api/connection.rb
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
require 'faraday'
|
|
2
|
-
require '
|
|
2
|
+
require 'faraday/response/json'
|
|
3
|
+
require 'qbo_api/raise_http_exception'
|
|
3
4
|
require 'faraday/detailed_logger'
|
|
5
|
+
require 'faraday/multipart'
|
|
4
6
|
|
|
5
7
|
class QboApi
|
|
6
8
|
module Connection
|
|
7
|
-
AUTHORIZATION_MIDDLEWARES = []
|
|
8
|
-
|
|
9
|
-
def Connection.add_authorization_middleware(strategy_name)
|
|
10
|
-
Connection::AUTHORIZATION_MIDDLEWARES << strategy_name
|
|
11
|
-
end
|
|
12
|
-
|
|
13
9
|
def authorized_json_connection(url, headers: nil)
|
|
14
10
|
headers ||= {}
|
|
15
|
-
headers['Accept'] ||= 'application/json'
|
|
16
|
-
headers['Content-Type'] ||= 'application/json;charset=UTF-8'
|
|
11
|
+
headers['Accept'] ||= 'application/json'
|
|
12
|
+
headers['Content-Type'] ||= 'application/json;charset=UTF-8'
|
|
17
13
|
build_connection(url, headers: headers) do |conn|
|
|
14
|
+
conn.response :json
|
|
18
15
|
add_authorization_middleware(conn)
|
|
19
|
-
add_exception_middleware(conn)
|
|
20
16
|
conn.request :url_encoded
|
|
17
|
+
add_exception_middleware(conn)
|
|
21
18
|
add_connection_adapter(conn)
|
|
22
19
|
end
|
|
23
20
|
end
|
|
24
21
|
|
|
25
22
|
def authorized_multipart_connection(url)
|
|
26
|
-
headers = {
|
|
23
|
+
headers = {
|
|
24
|
+
'Content-Type' => 'multipart/form-data',
|
|
25
|
+
'Accept' => 'application/json'
|
|
26
|
+
}
|
|
27
27
|
build_connection(url, headers: headers) do |conn|
|
|
28
|
+
conn.response :json
|
|
28
29
|
add_authorization_middleware(conn)
|
|
29
30
|
add_exception_middleware(conn)
|
|
30
31
|
conn.request :multipart
|
|
@@ -32,17 +33,6 @@ class QboApi
|
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
# @example
|
|
36
|
-
# connection = build_connection('https://oauth.platform.intuit.com', headers: { 'Accept' => 'application/json' }) do |conn|
|
|
37
|
-
# conn.basic_auth(client_id, client_secret)
|
|
38
|
-
# conn.request :url_encoded # application/x-www-form-urlencoded
|
|
39
|
-
# conn.use FaradayMiddleware::ParseJson, parser_options: { symbolize_names: true }
|
|
40
|
-
# conn.use Faraday::Response::RaiseError
|
|
41
|
-
# end
|
|
42
|
-
# raw_response = connection.post {|req|
|
|
43
|
-
# req.body = { grant_type: :refresh_token, refresh_token: current_refresh_token }
|
|
44
|
-
# req.url '/oauth2/v1/tokens/bearer'
|
|
45
|
-
# }
|
|
46
36
|
def build_connection(url, headers: nil)
|
|
47
37
|
Faraday.new(url: url) { |conn|
|
|
48
38
|
conn.response :detailed_logger, QboApi.logger, LOG_TAG if QboApi.log
|
|
@@ -51,14 +41,17 @@ class QboApi
|
|
|
51
41
|
}
|
|
52
42
|
end
|
|
53
43
|
|
|
54
|
-
def request(method, path:, entity: nil, payload: nil, params: nil)
|
|
55
|
-
raw_response = raw_request(method, conn: connection, path: path, params: params, payload: payload)
|
|
44
|
+
def request(method, path:, entity: nil, payload: nil, params: nil, headers: nil)
|
|
45
|
+
raw_response = raw_request(method, conn: connection, path: path, params: params, payload: payload, headers: headers)
|
|
56
46
|
response(raw_response, entity: entity)
|
|
57
47
|
end
|
|
58
48
|
|
|
59
|
-
def raw_request(method, conn:, path:, payload: nil, params: nil)
|
|
49
|
+
def raw_request(method, conn:, path:, payload: nil, params: nil, headers: nil)
|
|
60
50
|
path = finalize_path(path, method: method, params: params)
|
|
51
|
+
|
|
61
52
|
conn.public_send(method) do |req|
|
|
53
|
+
req.headers = headers if headers
|
|
54
|
+
|
|
62
55
|
case method
|
|
63
56
|
when :get, :delete
|
|
64
57
|
req.url path
|
|
@@ -71,21 +64,13 @@ class QboApi
|
|
|
71
64
|
end
|
|
72
65
|
|
|
73
66
|
def response(resp, entity: nil)
|
|
74
|
-
data =
|
|
67
|
+
data = resp.body
|
|
75
68
|
entity ? entity_response(data, entity) : data
|
|
76
69
|
rescue => e
|
|
77
70
|
QboApi.logger.debug { "#{LOG_TAG} response parsing error: entity=#{entity.inspect} body=#{resp.body.inspect} exception=#{e.inspect}" }
|
|
78
71
|
data
|
|
79
72
|
end
|
|
80
73
|
|
|
81
|
-
def parse_response_body(resp)
|
|
82
|
-
body = resp.body
|
|
83
|
-
case resp.headers['Content-Type']
|
|
84
|
-
when /json/ then JSON.parse(body)
|
|
85
|
-
else body
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
74
|
private
|
|
90
75
|
|
|
91
76
|
def entity_response(data, entity)
|
|
@@ -112,26 +97,18 @@ class QboApi
|
|
|
112
97
|
end
|
|
113
98
|
|
|
114
99
|
def add_exception_middleware(conn)
|
|
115
|
-
conn.use
|
|
100
|
+
conn.use QboApi::RaiseHttpException
|
|
116
101
|
end
|
|
117
102
|
|
|
103
|
+
# Faraday 2 deprecated the FaradayMiddleware gem. Middleware is
|
|
104
|
+
# now part of Faraday itself, and :authorization can be used to pass
|
|
105
|
+
# the Bearer token.
|
|
118
106
|
def add_authorization_middleware(conn)
|
|
119
|
-
|
|
120
|
-
raise QboApi::Error.new error_body: 'Add a configured authorization_middleware'
|
|
121
|
-
end) do |strategy_name|
|
|
122
|
-
next unless public_send("use_#{strategy_name}_middleware?")
|
|
123
|
-
public_send("add_#{strategy_name}_authorization_middleware", conn)
|
|
124
|
-
true
|
|
125
|
-
end
|
|
107
|
+
conn.request :authorization, 'Bearer', access_token
|
|
126
108
|
end
|
|
127
109
|
|
|
128
110
|
def entity_name(entity)
|
|
129
111
|
singular(entity)
|
|
130
112
|
end
|
|
131
|
-
|
|
132
|
-
require_relative 'connection/oauth1'
|
|
133
|
-
include OAuth1
|
|
134
|
-
require_relative 'connection/oauth2'
|
|
135
|
-
include OAuth2
|
|
136
113
|
end
|
|
137
114
|
end
|
data/lib/qbo_api/entity.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
class QboApi
|
|
2
2
|
module Entity
|
|
3
|
-
|
|
4
3
|
def singular(entity)
|
|
5
4
|
e = snake_to_camel(entity)
|
|
6
5
|
case e
|
|
@@ -21,6 +20,19 @@ class QboApi
|
|
|
21
20
|
sym.to_s.split('_').collect(&:capitalize).join
|
|
22
21
|
end
|
|
23
22
|
|
|
23
|
+
def is_voidable_transaction_entity?(entity)
|
|
24
|
+
voidable_transaction_entities.include?(singular(entity))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def voidable_transaction_entities
|
|
28
|
+
%w{
|
|
29
|
+
BillPayment
|
|
30
|
+
Invoice
|
|
31
|
+
Payment
|
|
32
|
+
SalesReceipt
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
24
36
|
def is_transaction_entity?(entity)
|
|
25
37
|
transaction_entities.include?(singular(entity))
|
|
26
38
|
end
|
|
@@ -96,6 +108,5 @@ class QboApi
|
|
|
96
108
|
.tr("-", "_")
|
|
97
109
|
.downcase
|
|
98
110
|
end
|
|
99
|
-
|
|
100
111
|
end
|
|
101
112
|
end
|
data/lib/qbo_api/error.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
#200 OK The request succeeded. However, the response body may contain a <Fault> element, indicating an error.
|
|
3
2
|
#400 Bad request Generally, the request cannot be fulfilled due to bad syntax. In some cases, this response code is returned for a request with bad authorization data.
|
|
4
3
|
#401 Unauthorized Authentication or authorization has failed.
|
|
@@ -6,11 +5,14 @@
|
|
|
6
5
|
#404 Not Found The resource is not found.
|
|
7
6
|
#429 Too Many Requests API Throttling/ Rate limiting
|
|
8
7
|
#500 Internal Server Error An error occurred on the server while processing the request. Resubmit request once; if it persists, contact developer support.
|
|
8
|
+
#502 Bad Gateway The server, while acting as a gateway or proxy, received an invalid response from an inbound server it accessed while attempting to fulfill the request.
|
|
9
9
|
#503 Service Unavailable The service is temporarily unavailable.
|
|
10
|
+
#504 Gateway Timeout
|
|
10
11
|
# Custom error class for rescuing from all QuickBooks Online errors
|
|
11
12
|
class QboApi
|
|
12
13
|
class Error < StandardError
|
|
13
14
|
attr_reader :fault
|
|
15
|
+
|
|
14
16
|
def initialize(errors = nil)
|
|
15
17
|
if errors
|
|
16
18
|
@fault = errors
|
|
@@ -40,6 +42,12 @@ class QboApi
|
|
|
40
42
|
# Raised when QuickBooks Online returns the HTTP status code 500
|
|
41
43
|
class InternalServerError < Error; end
|
|
42
44
|
|
|
45
|
+
# Raised when QuickBooks Online returns the HTTP status code 502
|
|
46
|
+
class BadGateway < Error; end
|
|
47
|
+
|
|
43
48
|
# Raised when QuickBooks Online returns the HTTP status code 503
|
|
44
49
|
class ServiceUnavailable < Error; end
|
|
50
|
+
|
|
51
|
+
# Raised when QuickBooks Online returns the HTTP status code 504
|
|
52
|
+
class GatewayTimeout < Error; end
|
|
45
53
|
end
|
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
require 'faraday'
|
|
2
|
+
require 'faraday/response'
|
|
2
3
|
require 'nokogiri'
|
|
3
|
-
|
|
4
4
|
# @private
|
|
5
|
-
|
|
5
|
+
class QboApi
|
|
6
6
|
# @private
|
|
7
|
-
class RaiseHttpException < Faraday::
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
class RaiseHttpException < Faraday::Response::RaiseError
|
|
8
|
+
def on_complete(env)
|
|
9
|
+
case env[:status]
|
|
10
|
+
when 400
|
|
11
|
+
raise QboApi::BadRequest.new(error_message(env))
|
|
12
|
+
when 401
|
|
13
|
+
raise QboApi::Unauthorized.new(error_message(env))
|
|
14
|
+
when 403
|
|
15
|
+
raise QboApi::Forbidden.new(error_message(env))
|
|
16
|
+
when 404
|
|
17
|
+
raise QboApi::NotFound.new(error_message(env))
|
|
18
|
+
when 407
|
|
19
|
+
# mimic the behavior that we get with proxy requests with HTTPS
|
|
20
|
+
msg = %(407 "Proxy Authentication Required")
|
|
21
|
+
raise Faraday::ProxyAuthError.new(msg, response_values(env))
|
|
22
|
+
when 409
|
|
23
|
+
raise Faraday::ConflictError, response_values(env)
|
|
24
|
+
when 422
|
|
25
|
+
raise Faraday::UnprocessableEntityError, response_values(env)
|
|
26
|
+
when 429
|
|
27
|
+
raise QboApi::TooManyRequests.new(error_message(env))
|
|
28
|
+
when 500
|
|
29
|
+
raise QboApi::InternalServerError.new(error_message(env))
|
|
30
|
+
when 502
|
|
31
|
+
raise QboApi::BadGateway.new({ error_body: env.reason_phrase })
|
|
32
|
+
when 503
|
|
33
|
+
raise QboApi::ServiceUnavailable.new(error_message(env))
|
|
34
|
+
when 504
|
|
35
|
+
raise QboApi::GatewayTimeout.new(error_message(env))
|
|
36
|
+
when ClientErrorStatuses
|
|
37
|
+
raise Faraday::ClientError, response_values(env)
|
|
38
|
+
when ServerErrorStatuses
|
|
39
|
+
raise Faraday::ServerError, response_values(env)
|
|
40
|
+
when nil
|
|
41
|
+
raise Faraday::NilStatusError, response_values(env)
|
|
27
42
|
end
|
|
28
43
|
end
|
|
29
44
|
|
|
@@ -33,12 +48,13 @@ module FaradayMiddleware
|
|
|
33
48
|
|
|
34
49
|
private
|
|
35
50
|
|
|
36
|
-
def error_message(
|
|
51
|
+
def error_message(env)
|
|
37
52
|
{
|
|
38
|
-
method:
|
|
39
|
-
url:
|
|
40
|
-
status:
|
|
41
|
-
error_body: error_body(
|
|
53
|
+
method: env.method,
|
|
54
|
+
url: env.url,
|
|
55
|
+
status: env.status,
|
|
56
|
+
error_body: error_body(env.body),
|
|
57
|
+
intuit_tid: env[:response_headers]['intuit_tid']
|
|
42
58
|
}
|
|
43
59
|
end
|
|
44
60
|
|
|
@@ -76,6 +92,5 @@ module FaradayMiddleware
|
|
|
76
92
|
}
|
|
77
93
|
end
|
|
78
94
|
end
|
|
79
|
-
|
|
80
95
|
end
|
|
81
96
|
end
|
data/lib/qbo_api/supporting.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
class QboApi
|
|
2
2
|
module Supporting
|
|
3
|
-
|
|
4
3
|
def cdc(entities:, changed_since:)
|
|
5
4
|
path = "#{realm_id}/cdc"
|
|
6
5
|
path = add_params_to_path(path: path, params: { entities: entities, changedSince: cdc_time(changed_since) })
|
|
@@ -18,5 +17,19 @@ class QboApi
|
|
|
18
17
|
request(:get, path: path)
|
|
19
18
|
end
|
|
20
19
|
|
|
20
|
+
def deliver(entity, entity_id:, email_address: nil)
|
|
21
|
+
valid_entities = %i(invoice estimate purchaseorder creditmemo salesreceipt refundreceipt)
|
|
22
|
+
unless valid_entities.include?(entity.to_sym)
|
|
23
|
+
raise ArgumentError, "Invalid entity type '#{entity}'. Must be one of: #{valid_entities.join(', ')}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
path = "#{realm_id}/#{entity}/#{entity_id}/send"
|
|
27
|
+
unless email_address.nil?
|
|
28
|
+
params = { minorversion: 63, sendTo: email_address }
|
|
29
|
+
path = add_params_to_path(path: path, params: params)
|
|
30
|
+
end
|
|
31
|
+
headers = { 'Content-Type' => 'application/octet-stream' }
|
|
32
|
+
request(:post, path: path, headers: headers)
|
|
33
|
+
end
|
|
21
34
|
end
|
|
22
35
|
end
|
data/lib/qbo_api/util.rb
CHANGED
data/lib/qbo_api/version.rb
CHANGED
data/lib/qbo_api.rb
CHANGED
|
@@ -22,15 +22,15 @@ class QboApi
|
|
|
22
22
|
include Attachment
|
|
23
23
|
include ApiMethods
|
|
24
24
|
|
|
25
|
-
attr_accessor :realm_id
|
|
25
|
+
attr_accessor :access_token, :realm_id
|
|
26
26
|
attr_accessor :endpoint
|
|
27
27
|
|
|
28
28
|
V3_ENDPOINT_BASE_URL = 'https://sandbox-quickbooks.api.intuit.com/v3/company/'
|
|
29
29
|
PAYMENTS_API_BASE_URL = 'https://sandbox.api.intuit.com/quickbooks/v4/payments'
|
|
30
30
|
LOG_TAG = "[QuickBooks]"
|
|
31
31
|
|
|
32
|
-
# @param attributes [Hash<Symbol,String>]
|
|
33
32
|
def initialize(attributes = {})
|
|
33
|
+
raise ArgumentError, "missing keyword: access_token" unless attributes.key?(:access_token)
|
|
34
34
|
raise ArgumentError, "missing keyword: realm_id" unless attributes.key?(:realm_id)
|
|
35
35
|
attributes = default_attributes.merge!(attributes)
|
|
36
36
|
attributes.each do |attribute, value|
|
data/qbo_api.gemspec
CHANGED
|
@@ -18,16 +18,15 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
19
|
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
spec.add_development_dependency "bundler"
|
|
22
|
-
spec.add_development_dependency "rake"
|
|
21
|
+
spec.add_development_dependency "bundler"
|
|
22
|
+
spec.add_development_dependency "rake"
|
|
23
23
|
spec.add_development_dependency "rspec"
|
|
24
24
|
spec.add_development_dependency 'webmock'
|
|
25
|
-
spec.add_development_dependency 'simple_oauth'
|
|
26
25
|
spec.add_development_dependency 'dotenv'
|
|
27
26
|
spec.add_development_dependency 'vcr'
|
|
28
|
-
spec.add_development_dependency '
|
|
29
|
-
spec.add_runtime_dependency 'faraday'
|
|
30
|
-
spec.add_runtime_dependency 'faraday_middleware'
|
|
27
|
+
spec.add_development_dependency 'amazing_print'
|
|
28
|
+
spec.add_runtime_dependency 'faraday', '>= 1.10.0'
|
|
31
29
|
spec.add_runtime_dependency 'faraday-detailed_logger'
|
|
30
|
+
spec.add_runtime_dependency 'faraday-multipart'
|
|
32
31
|
spec.add_runtime_dependency 'nokogiri'
|
|
33
32
|
end
|
metadata
CHANGED
|
@@ -1,45 +1,31 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qbo_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christian Pelczarski
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-01-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rake
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '10.0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '10.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rspec
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
44
30
|
requirements:
|
|
45
31
|
- - ">="
|
|
@@ -53,7 +39,7 @@ dependencies:
|
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
40
|
version: '0'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
42
|
+
name: rspec
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
45
|
- - ">="
|
|
@@ -67,7 +53,7 @@ dependencies:
|
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
54
|
version: '0'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
56
|
+
name: webmock
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
72
58
|
requirements:
|
|
73
59
|
- - ">="
|
|
@@ -109,7 +95,7 @@ dependencies:
|
|
|
109
95
|
- !ruby/object:Gem::Version
|
|
110
96
|
version: '0'
|
|
111
97
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
98
|
+
name: amazing_print
|
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
|
114
100
|
requirements:
|
|
115
101
|
- - ">="
|
|
@@ -128,16 +114,16 @@ dependencies:
|
|
|
128
114
|
requirements:
|
|
129
115
|
- - ">="
|
|
130
116
|
- !ruby/object:Gem::Version
|
|
131
|
-
version:
|
|
117
|
+
version: 1.10.0
|
|
132
118
|
type: :runtime
|
|
133
119
|
prerelease: false
|
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
121
|
requirements:
|
|
136
122
|
- - ">="
|
|
137
123
|
- !ruby/object:Gem::Version
|
|
138
|
-
version:
|
|
124
|
+
version: 1.10.0
|
|
139
125
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
126
|
+
name: faraday-detailed_logger
|
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
|
142
128
|
requirements:
|
|
143
129
|
- - ">="
|
|
@@ -151,7 +137,7 @@ dependencies:
|
|
|
151
137
|
- !ruby/object:Gem::Version
|
|
152
138
|
version: '0'
|
|
153
139
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name: faraday-
|
|
140
|
+
name: faraday-multipart
|
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
|
156
142
|
requirements:
|
|
157
143
|
- - ">="
|
|
@@ -178,14 +164,13 @@ dependencies:
|
|
|
178
164
|
- - ">="
|
|
179
165
|
- !ruby/object:Gem::Version
|
|
180
166
|
version: '0'
|
|
181
|
-
description:
|
|
167
|
+
description:
|
|
182
168
|
email:
|
|
183
169
|
- christian@minimul.com
|
|
184
170
|
executables: []
|
|
185
171
|
extensions: []
|
|
186
172
|
extra_rdoc_files: []
|
|
187
173
|
files:
|
|
188
|
-
- ".env.example_app.oauth1"
|
|
189
174
|
- ".env.example_app.oauth2"
|
|
190
175
|
- ".env.test"
|
|
191
176
|
- ".gitignore"
|
|
@@ -197,8 +182,8 @@ files:
|
|
|
197
182
|
- bin/console
|
|
198
183
|
- bin/setup
|
|
199
184
|
- example/base.rb
|
|
200
|
-
- example/oauth.rb
|
|
201
185
|
- example/oauth2.rb
|
|
186
|
+
- example/public/connect-to-qbo.svg
|
|
202
187
|
- example/views/customer.erb
|
|
203
188
|
- example/views/index.erb
|
|
204
189
|
- example/views/oauth2.erb
|
|
@@ -208,8 +193,6 @@ files:
|
|
|
208
193
|
- lib/qbo_api/attachment.rb
|
|
209
194
|
- lib/qbo_api/configuration.rb
|
|
210
195
|
- lib/qbo_api/connection.rb
|
|
211
|
-
- lib/qbo_api/connection/oauth1.rb
|
|
212
|
-
- lib/qbo_api/connection/oauth2.rb
|
|
213
196
|
- lib/qbo_api/entity.rb
|
|
214
197
|
- lib/qbo_api/error.rb
|
|
215
198
|
- lib/qbo_api/raise_http_exception.rb
|
|
@@ -221,7 +204,7 @@ homepage: https://github.com/minimul/qbo_api
|
|
|
221
204
|
licenses:
|
|
222
205
|
- MIT
|
|
223
206
|
metadata: {}
|
|
224
|
-
post_install_message:
|
|
207
|
+
post_install_message:
|
|
225
208
|
rdoc_options: []
|
|
226
209
|
require_paths:
|
|
227
210
|
- lib
|
|
@@ -236,9 +219,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
236
219
|
- !ruby/object:Gem::Version
|
|
237
220
|
version: '0'
|
|
238
221
|
requirements: []
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
signing_key:
|
|
222
|
+
rubygems_version: 3.2.22
|
|
223
|
+
signing_key:
|
|
242
224
|
specification_version: 4
|
|
243
225
|
summary: Ruby JSON-only client for QuickBooks Online API v3. Built on top of the Faraday
|
|
244
226
|
gem.
|
data/.env.example_app.oauth1
DELETED
data/example/oauth.rb
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
require 'bundler/inline'
|
|
2
|
-
|
|
3
|
-
require File.expand_path(File.join('..', 'base'), __FILE__)
|
|
4
|
-
|
|
5
|
-
install_gems = true
|
|
6
|
-
gemfile(install_gems) do
|
|
7
|
-
source 'https://rubygems.org'
|
|
8
|
-
|
|
9
|
-
instance_eval(&BASE_GEMS)
|
|
10
|
-
|
|
11
|
-
gem 'omniauth'
|
|
12
|
-
gem 'omniauth-quickbooks'
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
instance_eval(&BASE_SETUP)
|
|
16
|
-
|
|
17
|
-
class OAuthApp < Sinatra::Base
|
|
18
|
-
instance_eval(&BASE_APP_CONFIG)
|
|
19
|
-
|
|
20
|
-
CONSUMER_KEY = ENV['QBO_API_CONSUMER_KEY']
|
|
21
|
-
CONSUMER_SECRET = ENV['QBO_API_CONSUMER_SECRET']
|
|
22
|
-
|
|
23
|
-
use Rack::Session::Cookie, secret: '34233adasf/qewrq453agqr9(lasfa)'
|
|
24
|
-
use OmniAuth::Builder do
|
|
25
|
-
provider :quickbooks, CONSUMER_KEY, CONSUMER_SECRET
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
get '/' do
|
|
29
|
-
@app_center = QboApi::APP_CENTER_BASE
|
|
30
|
-
@auth_data = oauth_data
|
|
31
|
-
@port = PORT
|
|
32
|
-
erb :index
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
get '/customer/:id' do
|
|
36
|
-
if session[:token]
|
|
37
|
-
api = QboApi.new(oauth_data)
|
|
38
|
-
@resp = api.get :customer, params[:id]
|
|
39
|
-
end
|
|
40
|
-
erb :customer
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
get '/auth/quickbooks/callback' do
|
|
44
|
-
auth = env["omniauth.auth"][:credentials]
|
|
45
|
-
session[:token] = auth[:token]
|
|
46
|
-
session[:secret] = auth[:secret]
|
|
47
|
-
session[:realm_id] = params['realmId']
|
|
48
|
-
'<!DOCTYPE html><html lang="en"><head></head><body><script>window.opener.location.reload(); window.close();</script></body></html>'
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def oauth_data
|
|
52
|
-
{
|
|
53
|
-
consumer_key: CONSUMER_KEY,
|
|
54
|
-
consumer_secret: CONSUMER_SECRET,
|
|
55
|
-
token: session[:token],
|
|
56
|
-
token_secret: session[:secret],
|
|
57
|
-
realm_id: session[:realm_id]
|
|
58
|
-
}
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
OAuthApp.run!
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
class QboApi
|
|
2
|
-
APP_CENTER_BASE = 'https://appcenter.intuit.com'
|
|
3
|
-
APP_CENTER_URL = APP_CENTER_BASE + '/Connect/Begin?oauth_token='
|
|
4
|
-
APP_CONNECTION_URL = APP_CENTER_BASE + '/api/v1/connection'
|
|
5
|
-
|
|
6
|
-
attr_accessor :token, :token_secret
|
|
7
|
-
attr_accessor :consumer_key, :consumer_secret
|
|
8
|
-
|
|
9
|
-
module Connection::OAuth1
|
|
10
|
-
|
|
11
|
-
def self.included(*)
|
|
12
|
-
QboApi::Connection.add_authorization_middleware :oauth1
|
|
13
|
-
super
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def default_attributes
|
|
17
|
-
super.merge!(
|
|
18
|
-
token: nil, token_secret: nil,
|
|
19
|
-
consumer_key: defined?(CONSUMER_KEY) ? CONSUMER_KEY : nil,
|
|
20
|
-
consumer_secret: defined?(CONSUMER_SECRET) ? CONSUMER_SECRET : nil,
|
|
21
|
-
)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def add_oauth1_authorization_middleware(conn)
|
|
25
|
-
gem 'simple_oauth'
|
|
26
|
-
require 'simple_oauth'
|
|
27
|
-
conn.request :oauth, oauth_data
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def use_oauth1_middleware?
|
|
31
|
-
token != nil
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# https://developer.intuit.com/docs/0100_quickbooks_online/0100_essentials/0085_develop_quickbooks_apps/0004_authentication_and_authorization/oauth_management_api#/Reconnect
|
|
35
|
-
def disconnect
|
|
36
|
-
path = "#{APP_CONNECTION_URL}/disconnect"
|
|
37
|
-
request(:get, path: path)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# https://developer.intuit.com/docs/0100_quickbooks_online/0100_essentials/0085_develop_quickbooks_apps/0004_authentication_and_authorization/oauth_management_api#/Reconnect
|
|
41
|
-
def reconnect
|
|
42
|
-
path = "#{APP_CONNECTION_URL}/reconnect"
|
|
43
|
-
request(:get, path: path)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
# Use with simple_oauth OAuth1 middleware
|
|
49
|
-
# @see #add_authorization_middleware
|
|
50
|
-
def oauth_data
|
|
51
|
-
{
|
|
52
|
-
consumer_key: @consumer_key,
|
|
53
|
-
consumer_secret: @consumer_secret,
|
|
54
|
-
token: @token,
|
|
55
|
-
token_secret: @token_secret
|
|
56
|
-
}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
class QboApi
|
|
2
|
-
attr_accessor :access_token
|
|
3
|
-
|
|
4
|
-
module Connection::OAuth2
|
|
5
|
-
|
|
6
|
-
def self.included(*)
|
|
7
|
-
QboApi::Connection.add_authorization_middleware :oauth2
|
|
8
|
-
super
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def default_attributes
|
|
12
|
-
super.merge!(
|
|
13
|
-
access_token: nil
|
|
14
|
-
)
|
|
15
|
-
end
|
|
16
|
-
def add_oauth2_authorization_middleware(conn)
|
|
17
|
-
conn.request :oauth2, access_token, token_type: 'bearer'
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def use_oauth2_middleware?
|
|
21
|
-
access_token != nil
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|