xero-api 1.0.0
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 +7 -0
- data/.env.example_app.oauth1 +4 -0
- data/.env.example_app.oauth2 +0 -0
- data/.gitignore +23 -0
- data/.travis.yml +15 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +214 -0
- data/Rakefile +6 -0
- data/bin/console +20 -0
- data/bin/setup +7 -0
- data/example/base.rb +36 -0
- data/example/oauth.rb +67 -0
- data/example/public/connect_xero_button_blue_2x.png +0 -0
- data/example/views/callback.erb +20 -0
- data/example/views/customers.erb +20 -0
- data/example/views/index.erb +20 -0
- data/lib/xero/api.rb +55 -0
- data/lib/xero/api/attachment.rb +26 -0
- data/lib/xero/api/configuration.rb +21 -0
- data/lib/xero/api/connection.rb +114 -0
- data/lib/xero/api/connection/oauth1.rb +51 -0
- data/lib/xero/api/connection/oauth2.rb +24 -0
- data/lib/xero/api/error.rb +33 -0
- data/lib/xero/api/methods.rb +67 -0
- data/lib/xero/api/raise_http_exception.rb +42 -0
- data/lib/xero/api/util.rb +54 -0
- data/lib/xero/api/version.rb +5 -0
- data/xero-api.gemspec +33 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f58c896bb749d9a48423d4af4b1a209b8ef3cce9aa94459b317e44b7b649f378
|
4
|
+
data.tar.gz: f738a3619bc044690bb01582849065b7ec253c183d03719a879af99952d555da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b8e83ddc7cb421258f60fe62c1adf38febf43383317ec0994219906414de8106a320527c7478d7d8b0e71f4322326a80f8aeef6d5b263146b0ac62ed2b78611
|
7
|
+
data.tar.gz: 2187ac8e2deb3ad9cbd54104e99b4e0673e9676102eb677e916c03071cbc95ed7693e5c8c32c30c3e955bc7311ee57a5146b3ad1df887b81c994a0acfb056049
|
File without changes
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
.env
|
11
|
+
.ruby-version
|
12
|
+
.ruby-gemset
|
13
|
+
.byebug_history
|
14
|
+
*.gem
|
15
|
+
.rspec
|
16
|
+
todo.txt
|
17
|
+
*.sess
|
18
|
+
*.log
|
19
|
+
spec/temp/spec_status.txt
|
20
|
+
*.swp
|
21
|
+
scrap.txt
|
22
|
+
README.md.html
|
23
|
+
.DS_Store
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Christian Pelczarski
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# The xero-api gem
|
2
|
+
|
3
|
+
### Ruby client for the Xero API version 2.
|
4
|
+
- **Pure JSON-in / JSON-out.** No XML support.
|
5
|
+
- 4 main methods: **.get, .create, .update, and .delete**
|
6
|
+
- No validation rules built into the gem. **Validation comes from API only**.
|
7
|
+
- Close to the metal experience.
|
8
|
+
- First class logging.
|
9
|
+
- Robust error handling.
|
10
|
+
- Specs are built using real requests run directly against the Xero Demo Company. Thanks [VCR](https://github.com/vcr/vcr).
|
11
|
+
- Built leveraging [Faraday](https://github.com/lostisland/faraday).
|
12
|
+
- Built knowing that OAuth2 might be in the not-to-distant future.
|
13
|
+
|
14
|
+
## Why another library when there are other more mature, established Ruby Xero libraries?
|
15
|
+
|
16
|
+
Both of the current de facto Ruby Xero client gems were built 6+ years ago when the Xero API was XML only, therefore, they are loaded with *XML cruft*.
|
17
|
+
For example, here are the total code line counts (of `.rb` files):
|
18
|
+
|
19
|
+
- Total LOC count of :
|
20
|
+
- **minimul/xero-api** => **910!** 🌈
|
21
|
+
- waynerobinson/xeroizer => 6019
|
22
|
+
- xero-gateway/xero_gateway => 5545
|
23
|
+
|
24
|
+
## Ruby >= 2.4.0 required
|
25
|
+
|
26
|
+
## Current Limitations
|
27
|
+
|
28
|
+
- Accounting API only.
|
29
|
+
- Only for public and partner Xero apps.
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
Add this line to your application's Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'xero_api'
|
37
|
+
```
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
|
41
|
+
$ bundle
|
42
|
+
|
43
|
+
Or install it yourself as:
|
44
|
+
|
45
|
+
$ gem install xero-api
|
46
|
+
|
47
|
+
|
48
|
+
## Initialize
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
creds = account.xero_account # or wherever you are storing the OAuth creds
|
52
|
+
xero_api = Xero::Api.new(token: creds.token,
|
53
|
+
token_secret: creds.secret,
|
54
|
+
consumer_key: '*****',
|
55
|
+
consumer_secret: '********')
|
56
|
+
```
|
57
|
+
|
58
|
+
## .get
|
59
|
+
|
60
|
+
### Important
|
61
|
+
One queries the Xero API [mostly using URL parameters](https://developer.xero.com/documentation/api/requests-and-responses) so `xero-api` doesn't do a lot of "hand holding" and has the `params` argument enabling you to craft your queries as you see fit. Likewise, the `path` argument allows you to forge the URL path you desire. The `params` and `path` arguments are available on `.create`, `.update`, `delete`, and `.upload_attachment` as well.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# Basic get - retrieves first 100 contacts
|
65
|
+
resp = api.get(:contacts)
|
66
|
+
# Retrieves all contacts - returns Enumerator so you can do cool stuff
|
67
|
+
resp = api.get(:contacts, all: true)
|
68
|
+
p resp.count #=> 109
|
69
|
+
# Retrieves all contacts modified after a certain date
|
70
|
+
resp = api.get(:items, all: true, modified_since: Time.utc(2014, 01, 01))
|
71
|
+
# Retrieves only customers
|
72
|
+
resp = api.get(:contacts, params: { where: 'IsCustomer=true' })
|
73
|
+
# Retrieve by id
|
74
|
+
resp = api.get(:contacts, id: '323-43fss-4234dfa-3432233')
|
75
|
+
# Retrieve with custom path
|
76
|
+
resp = api.get(:users, path: '3138017f-8ddc-420e-a159-e7e1cf9e643d/History')
|
77
|
+
```
|
78
|
+
|
79
|
+
See all the arguments for the [`.get` method](https://github.com/minimul/xero-api/blob/447170ff1035103ed251bf203cf95450bda0f377/lib/xero/api/methods.rb#L4).
|
80
|
+
|
81
|
+
## .create
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
payload = {
|
85
|
+
"Type": "ACCREC",
|
86
|
+
"Contact": {
|
87
|
+
"ContactID": "f477ad8d-44f2-4bb7-a99b-04f28681e849"
|
88
|
+
},
|
89
|
+
"DateString": api.standard_date(Time.utc(2009, 05, 27)),
|
90
|
+
"DueDateString": api.standard_date(Time.utc(2009, 06, 06)),
|
91
|
+
"LineAmountTypes": "Exclusive",
|
92
|
+
"LineItems": [
|
93
|
+
{
|
94
|
+
"Description": "Consulting services as agreed (20% off standard rate)",
|
95
|
+
"Quantity": "10",
|
96
|
+
"UnitAmount": "100.00",
|
97
|
+
"AccountCode": "200",
|
98
|
+
"DiscountRate": "20"
|
99
|
+
}
|
100
|
+
]
|
101
|
+
}
|
102
|
+
response = api.create(:invoice, payload: payload)
|
103
|
+
inv_num = response.dig("Invoices", 0, "InvoiceNumber")
|
104
|
+
p inv_num #=> 'INV-0041'
|
105
|
+
```
|
106
|
+
|
107
|
+
##### bulk .create
|
108
|
+
```ruby
|
109
|
+
payload = { "Contacts": [] }
|
110
|
+
60.times do
|
111
|
+
payload[:Contacts] << { "Name": Faker::Name.unique.name, "IsCustomer": true }
|
112
|
+
end
|
113
|
+
resp = api.create(:contacts, payload: payload, params: { summarizeErrors: false })
|
114
|
+
p resp.dig("Contacts").size #=> 60
|
115
|
+
```
|
116
|
+
|
117
|
+
See all the arguments for the [`.create` method](https://github.com/minimul/xero-api/blob/447170ff1035103ed251bf203cf95450bda0f377/lib/xero/api/methods.rb#L14).
|
118
|
+
|
119
|
+
## .update
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
payload = {
|
123
|
+
"InvoiceNumber": 'INV-0038',
|
124
|
+
"Status": 'VOIDED'
|
125
|
+
}
|
126
|
+
response = api.update(:invoices, id: 'INV-0038', payload: payload)
|
127
|
+
p response.dig("Invoices", 0, "Status") #=> VOIDED
|
128
|
+
```
|
129
|
+
|
130
|
+
See all the arguments for the [`.update` method](https://github.com/minimul/xero-api/blob/447170ff1035103ed251bf203cf95450bda0f377/lib/xero/api/methods.rb#L19).
|
131
|
+
|
132
|
+
## .delete
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
api.delete(:items, id: "e1d100f5-a602-4f0e-94b7-dc12e97b9bc2")
|
136
|
+
```
|
137
|
+
See all the arguments for the [`.delete` method](https://github.com/minimul/xero-api/blob/447170ff1035103ed251bf203cf95450bda0f377/lib/xero/api/methods.rb#L25).
|
138
|
+
|
139
|
+
## Configuration options
|
140
|
+
```
|
141
|
+
- Logging:
|
142
|
+
```ruby
|
143
|
+
Xero::Api.log = true
|
144
|
+
```
|
145
|
+
- To change logging target from `$stdout` e.g.
|
146
|
+
```ruby
|
147
|
+
Xero::Api.logger = Rails.logger
|
148
|
+
```
|
149
|
+
|
150
|
+
## Other stuff
|
151
|
+
|
152
|
+
### .upload_attachment
|
153
|
+
```ruby
|
154
|
+
file_name = 'connect_xero_button_blue_2x.png'
|
155
|
+
resp = api.upload_attachment(:invoices, id: '9eb7b996-4ac6-4cf8-8ee8-eb30d6e572e3',
|
156
|
+
file_name: file_name, content_type: 'image/png',
|
157
|
+
attachment: "#{__dir__}/../../../example/public/#{file_name}")
|
158
|
+
```
|
159
|
+
|
160
|
+
### Respond to an error
|
161
|
+
```ruby
|
162
|
+
customer = { Name: 'Already Exists', EmailAddress: 'newone@already.com' }
|
163
|
+
begin
|
164
|
+
response = api.create(:contacts, payload: customer)
|
165
|
+
rescue Xero::Api::BadRequest => e
|
166
|
+
if e.message =~ /already exists/
|
167
|
+
# Query for Id using Name
|
168
|
+
resp = api.get(:contacts, params: { where: "Name='Already Exists'" })
|
169
|
+
# Do an update instead
|
170
|
+
up_resp = api.update(:contacts, id: resp["Id"], payload: payload)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
### Spin up an example
|
176
|
+
|
177
|
+
1. Follow and do all in Step 1 from the [Getting Started Guide](https://developer.xero.com/documentation/getting-started/getting-started-guide).
|
178
|
+
1. `git clone git://github.com/minimul/xero-api && cd xero-api`
|
179
|
+
1. `bundle`
|
180
|
+
1. Create a `.env` file
|
181
|
+
1. `cp .env.example_app.oauth1 .env`
|
182
|
+
1. Edit the `.env` file values with `consumer_key` and `consumer_secret`.
|
183
|
+
1. Start up the example app => `ruby example/oauth.rb`
|
184
|
+
1. In browser go to `http://localhost:9393`.
|
185
|
+
1. Use the `Connect to Xero` button to connect to your Xero account.
|
186
|
+
1. After successfully connecting click on the displayed link => `View All Customers`
|
187
|
+
1. Checkout [`example/oauth.rb`](https://github.com/minimul/xero-api/blob/master/example/oauth.rb)
|
188
|
+
to see what is going on under the hood.
|
189
|
+
- **Important:** In the [`/auth/xero/callback`](https://github.com/minimul/xero-api/blob/master/example/oauth.rb) route there is code there that will automatically update your `.env` file.
|
190
|
+
|
191
|
+
### Protip: Once your .env file is completely filled out you can use the console to play around in your sandbox
|
192
|
+
```
|
193
|
+
bin/console test
|
194
|
+
>> @xero_api.get :contacts, id: '5345-as543-4-afgafadsafsad-45334'
|
195
|
+
```
|
196
|
+
|
197
|
+
## Contributing
|
198
|
+
|
199
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/minimul/xero-api.
|
200
|
+
|
201
|
+
#### Running the specs
|
202
|
+
- `git clone git://github.com/minimul/xero-api && cd xero-api`
|
203
|
+
- `bundle`
|
204
|
+
- Create a `.env` file
|
205
|
+
- `cp .env.example_app.oauth1 .env`
|
206
|
+
- `bundle exec rake`
|
207
|
+
|
208
|
+
#### Creating new specs or modifying existing spec that have been recorded using the VCR gem.
|
209
|
+
- All specs that require interaction with the API must be recorded against the Xero Demo Company.
|
210
|
+
|
211
|
+
## License
|
212
|
+
|
213
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
214
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require_relative '../lib/xero/api'
|
5
|
+
|
6
|
+
if ARGV[0] == "test"
|
7
|
+
require_relative '../spec/support/credentials'
|
8
|
+
ARGV[0] = nil # needed to avoid irb error
|
9
|
+
instance_variable_set(:@xero_api, Xero::Api.new(creds.to_h))
|
10
|
+
end
|
11
|
+
|
12
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
13
|
+
# with your gem easier. You can also use a different console, if you like.
|
14
|
+
|
15
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
16
|
+
# require "pry"
|
17
|
+
# Pry.start
|
18
|
+
|
19
|
+
require "irb"
|
20
|
+
IRB.start
|
data/bin/setup
ADDED
data/example/base.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
BASE_GEMS = proc do
|
2
|
+
gem 'xero-api', path: '.'
|
3
|
+
# This app
|
4
|
+
gem 'sinatra'
|
5
|
+
gem 'sinatra-contrib'
|
6
|
+
|
7
|
+
# Creds from ../.env
|
8
|
+
gem 'dotenv'
|
9
|
+
end
|
10
|
+
|
11
|
+
BASE_SETUP = proc do
|
12
|
+
# Webhook support
|
13
|
+
require 'json'
|
14
|
+
require 'openssl'
|
15
|
+
require 'base64'
|
16
|
+
|
17
|
+
Dotenv.load "#{__dir__}/../.env"
|
18
|
+
end
|
19
|
+
|
20
|
+
BASE_APP_CONFIG = proc do
|
21
|
+
PORT = ENV.fetch("PORT", 9393)
|
22
|
+
|
23
|
+
configure do
|
24
|
+
$VERBOSE = nil # silence redefined constant warning
|
25
|
+
register Sinatra::Reloader
|
26
|
+
end
|
27
|
+
|
28
|
+
set :port, PORT
|
29
|
+
|
30
|
+
helpers do
|
31
|
+
def base_url
|
32
|
+
"http://localhost:#{PORT}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/example/oauth.rb
ADDED
@@ -0,0 +1,67 @@
|
|
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 'simple_oauth'
|
12
|
+
gem 'omniauth'
|
13
|
+
gem 'omniauth-xero'
|
14
|
+
end
|
15
|
+
|
16
|
+
instance_eval(&BASE_SETUP)
|
17
|
+
|
18
|
+
class OAuthApp < Sinatra::Base
|
19
|
+
instance_eval(&BASE_APP_CONFIG)
|
20
|
+
|
21
|
+
CONSUMER_KEY = ENV['XERO_API_CONSUMER_KEY']
|
22
|
+
CONSUMER_SECRET = ENV['XERO_API_CONSUMER_SECRET']
|
23
|
+
|
24
|
+
use Rack::Session::Cookie, secret: '34233adasfqewrq453agqr9lasfa'
|
25
|
+
use OmniAuth::Builder do
|
26
|
+
provider :xero, CONSUMER_KEY, CONSUMER_SECRET
|
27
|
+
end
|
28
|
+
|
29
|
+
get '/' do
|
30
|
+
@auth_data = oauth_data
|
31
|
+
@port = PORT
|
32
|
+
erb :index
|
33
|
+
end
|
34
|
+
|
35
|
+
get '/customers' do
|
36
|
+
if session[:token]
|
37
|
+
api = Xero::Api.new(oauth_data)
|
38
|
+
@resp = api.get :contacts, all: true, params: { where: 'isCustomer=true' }
|
39
|
+
end
|
40
|
+
erb :customers
|
41
|
+
end
|
42
|
+
|
43
|
+
get '/auth/xero/callback' do
|
44
|
+
auth = env["omniauth.auth"][:credentials]
|
45
|
+
session[:token] = auth[:token]
|
46
|
+
session[:secret] = auth[:secret]
|
47
|
+
file_name = "#{__dir__}/../.env"
|
48
|
+
if env = File.read(file_name)
|
49
|
+
res = env.sub(/(XERO_API_ACCESS_TOKEN=)(.*)/, '\1' + session[:token])
|
50
|
+
res = res.sub(/(XERO_API_ACCESS_TOKEN_SECRET=)(.*)/, '\1' + session[:secret])
|
51
|
+
File.open(file_name, "w") {|file| file.puts res }
|
52
|
+
end
|
53
|
+
@url = base_url
|
54
|
+
erb :callback
|
55
|
+
end
|
56
|
+
|
57
|
+
def oauth_data
|
58
|
+
{
|
59
|
+
consumer_key: CONSUMER_KEY,
|
60
|
+
consumer_secret: CONSUMER_SECRET,
|
61
|
+
token: session[:token],
|
62
|
+
token_secret: session[:secret]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
OAuthApp.run!
|
Binary file
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>Callback page</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h3>Redirecting ...</h3>
|
8
|
+
<script>
|
9
|
+
setTimeout(function(){
|
10
|
+
var url = '<%= @url %>';
|
11
|
+
if(window.opener == null){
|
12
|
+
window.location = url;
|
13
|
+
}else{
|
14
|
+
window.opener.location = url;
|
15
|
+
window.close();
|
16
|
+
}
|
17
|
+
}, 5000);
|
18
|
+
</script>
|
19
|
+
</body>
|
20
|
+
</html>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>My Xero Customers</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h2>Here's the code that is powering this view.</h2>
|
9
|
+
<pre>
|
10
|
+
api = Xero::Api.new(oauth_data)
|
11
|
+
@resp = api.get :customers, all: true, params: { where: 'isCustomer=true' }
|
12
|
+
</pre>
|
13
|
+
<h1>Customers within your Xero Account</h1>
|
14
|
+
<ul>
|
15
|
+
<% @resp.each do |c, index| %>
|
16
|
+
<li><strong><%= c['Name'] %></strong></li>
|
17
|
+
<% end %>
|
18
|
+
</ul>
|
19
|
+
</body>
|
20
|
+
</html>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Xero Connect</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<a href="<%= base_url %>/auth/xero">
|
9
|
+
<img src="/connect_xero_button_blue_2x.png" alt="Xero Blue Connect Button" />
|
10
|
+
</a>
|
11
|
+
<% if session[:token] %>
|
12
|
+
<ul>
|
13
|
+
<li>Token: <input type="text" value="<%= session[:token] %>" size="100" /></li>
|
14
|
+
<li>Secret: <input type="text" value="<%= session[:secret] %>" size="100" /></li>
|
15
|
+
<li><strong><a href="/customers">View All Customers</a></strong></li>
|
16
|
+
</ul>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
</body>
|
20
|
+
</html>
|
data/lib/xero/api.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'logger'
|
4
|
+
require_relative 'api/version'
|
5
|
+
require_relative 'api/configuration'
|
6
|
+
require_relative 'api/connection'
|
7
|
+
require_relative 'api/error'
|
8
|
+
require_relative 'api/raise_http_exception'
|
9
|
+
require_relative 'api/util'
|
10
|
+
require_relative 'api/attachment'
|
11
|
+
require_relative 'api/methods'
|
12
|
+
|
13
|
+
module Xero
|
14
|
+
class Api
|
15
|
+
extend Configuration
|
16
|
+
include Connection
|
17
|
+
include Util
|
18
|
+
include Attachment
|
19
|
+
include Methods
|
20
|
+
|
21
|
+
attr_accessor :endpoint
|
22
|
+
|
23
|
+
V2_ENDPOINT_BASE_URL = 'https://api.xero.com/api.xro/2.0/'
|
24
|
+
LOG_TAG = "[xero-api gem]"
|
25
|
+
|
26
|
+
def initialize(attributes = {})
|
27
|
+
raise Xero::Api::Error, "missing or blank keyword: token" unless attributes.key?(:token) and !attributes[:token].nil?
|
28
|
+
attributes = default_attributes.merge!(attributes)
|
29
|
+
attributes.each do |attribute, value|
|
30
|
+
public_send("#{attribute}=", value)
|
31
|
+
end
|
32
|
+
@endpoint_url = get_endpoint
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_attributes
|
36
|
+
{
|
37
|
+
endpoint: :accounting
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def connection(url: endpoint_url)
|
42
|
+
@connection ||= authorized_json_connection(url)
|
43
|
+
end
|
44
|
+
|
45
|
+
def endpoint_url
|
46
|
+
@endpoint_url.dup
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_endpoint
|
52
|
+
V2_ENDPOINT_BASE_URL
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Xero::Api
|
2
|
+
module Attachment
|
3
|
+
|
4
|
+
def upload_attachment(entity, id:, file_name:, content_type:, attachment:, include_online: false)
|
5
|
+
url = "#{entity_handler(entity)}/#{id}/Attachments/#{file_name}"
|
6
|
+
url += "?IncludeOnline=true" if include_online
|
7
|
+
headers = { 'Content-Type' => content_type, 'Accept' => 'application/json' }
|
8
|
+
raw_response = attachment_connection(headers: headers).post do |request|
|
9
|
+
request.url url
|
10
|
+
request.body = Faraday::UploadIO.new(attachment, content_type, file_name)
|
11
|
+
end
|
12
|
+
response(raw_response, entity: entity)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attachment_connection(headers:)
|
16
|
+
build_connection(endpoint_url, headers: headers) do |conn|
|
17
|
+
add_authorization_middleware(conn)
|
18
|
+
add_exception_middleware(conn)
|
19
|
+
conn.request :url_encoded
|
20
|
+
add_connection_adapter(conn)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Xero::Api
|
2
|
+
module Configuration
|
3
|
+
|
4
|
+
def logger
|
5
|
+
@logger ||= ::Logger.new($stdout)
|
6
|
+
end
|
7
|
+
|
8
|
+
def logger=(logger)
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def log
|
13
|
+
@log ||= false
|
14
|
+
end
|
15
|
+
|
16
|
+
def log=(value)
|
17
|
+
@log = value
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'faraday/detailed_logger'
|
4
|
+
|
5
|
+
class Xero::Api
|
6
|
+
module Connection
|
7
|
+
AUTHORIZATION_MIDDLEWARES = []
|
8
|
+
|
9
|
+
def Connection.add_authorization_middleware(strategy_name)
|
10
|
+
Connection::AUTHORIZATION_MIDDLEWARES << strategy_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def authorized_json_connection(url, headers: nil)
|
14
|
+
headers ||= {}
|
15
|
+
headers['Accept'] ||= 'application/json' # required "we'll only accept JSON". Can be changed to any `+json` media type.
|
16
|
+
headers['Content-Type'] ||= 'application/json;charset=UTF-8' # required when request has a body, else harmless
|
17
|
+
build_connection(url, headers: headers) do |conn|
|
18
|
+
add_authorization_middleware(conn)
|
19
|
+
add_exception_middleware(conn)
|
20
|
+
conn.request :url_encoded
|
21
|
+
add_connection_adapter(conn)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def authorized_multipart_connection(url)
|
26
|
+
headers = { 'Content-Type' => 'multipart/form-data' }
|
27
|
+
build_connection(url, headers: headers) do |conn|
|
28
|
+
add_authorization_middleware(conn)
|
29
|
+
add_exception_middleware(conn)
|
30
|
+
conn.request :multipart
|
31
|
+
add_connection_adapter(conn)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_connection(url, headers: nil)
|
36
|
+
Faraday.new(url: url) { |conn|
|
37
|
+
conn.response :detailed_logger, Xero::Api.logger, LOG_TAG if Xero::Api.log
|
38
|
+
conn.headers.update(headers) if headers
|
39
|
+
yield conn if block_given?
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def request(method, path:, entity: nil, payload: nil, headers: nil, parse_entity: false)
|
44
|
+
raw_response = raw_request(method, conn: connection, path: path, payload: payload, headers: headers)
|
45
|
+
response(raw_response, entity: entity, parse_entity: parse_entity)
|
46
|
+
end
|
47
|
+
|
48
|
+
def raw_request(method, conn:, path:, payload: nil, headers: nil)
|
49
|
+
conn.public_send(method) do |req|
|
50
|
+
req.headers.update(headers) if headers
|
51
|
+
case method
|
52
|
+
when :get, :delete
|
53
|
+
req.url path
|
54
|
+
when :post, :put
|
55
|
+
req.url path
|
56
|
+
req.body = payload.to_json
|
57
|
+
else raise Xero::Api::Error, "Unhandled request method '#{method.inspect}'"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def response(resp, entity: nil, parse_entity: false)
|
63
|
+
data = parse_response_body(resp)
|
64
|
+
parse_entity && entity ? entity_response(data, entity) : data
|
65
|
+
rescue => e
|
66
|
+
msg = "#{LOG_TAG} response parsing error: entity=#{entity.inspect} body=#{resp.body.inspect} exception=#{e.inspect}"
|
67
|
+
Xero::Api.logger.debug { msg }
|
68
|
+
data
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_response_body(resp)
|
72
|
+
body = resp.body
|
73
|
+
case resp.headers['Content-Type']
|
74
|
+
when /json/ then JSON.parse(body)
|
75
|
+
else body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def entity_response(data, entity)
|
82
|
+
entity_name = entity_handler(entity)
|
83
|
+
entity_body = data
|
84
|
+
entity_body.fetch(entity_name) do
|
85
|
+
msg = "#{LOG_TAG} entity name not in that top-level of the response body: entity_name=#{entity_name}"
|
86
|
+
Xero::Api.logger.debug { msg }
|
87
|
+
data
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def add_connection_adapter(conn)
|
92
|
+
conn.adapter Faraday.default_adapter
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_exception_middleware(conn)
|
96
|
+
conn.use FaradayMiddleware::RaiseHttpException
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_authorization_middleware(conn)
|
100
|
+
Connection::AUTHORIZATION_MIDDLEWARES.find(proc do
|
101
|
+
raise Xero::Api::Error, 'Add a configured authorization_middleware'
|
102
|
+
end) do |strategy_name|
|
103
|
+
next unless public_send("use_#{strategy_name}_middleware?")
|
104
|
+
public_send("add_#{strategy_name}_authorization_middleware", conn)
|
105
|
+
true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
require_relative 'connection/oauth1'
|
110
|
+
include OAuth1
|
111
|
+
require_relative 'connection/oauth2'
|
112
|
+
include OAuth2
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Xero
|
2
|
+
class Api
|
3
|
+
OAUTH1_BASE = 'https://api.xero.com/oauth'
|
4
|
+
OAUTH1_UNAUTHORIZED = OAUTH1_BASE + '/RequestToken'
|
5
|
+
OAUTH1_REDIRECT = OAUTH1_BASE + '/Authorize'
|
6
|
+
OAUTH1_ACCESS_TOKEN = OAUTH1_BASE + '/AccessToken'
|
7
|
+
|
8
|
+
attr_accessor :token, :token_secret
|
9
|
+
attr_accessor :consumer_key, :consumer_secret
|
10
|
+
|
11
|
+
module Connection::OAuth1
|
12
|
+
|
13
|
+
def self.included(*)
|
14
|
+
Xero::Api::Connection.add_authorization_middleware :oauth1
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_attributes
|
19
|
+
super.merge!(
|
20
|
+
token: nil, token_secret: nil,
|
21
|
+
consumer_key: defined?(CONSUMER_KEY) ? CONSUMER_KEY : nil,
|
22
|
+
consumer_secret: defined?(CONSUMER_SECRET) ? CONSUMER_SECRET : nil,
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_oauth1_authorization_middleware(conn)
|
27
|
+
gem 'simple_oauth'
|
28
|
+
require 'simple_oauth'
|
29
|
+
conn.request :oauth, oauth_data
|
30
|
+
end
|
31
|
+
|
32
|
+
def use_oauth1_middleware?
|
33
|
+
token != nil
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Use with simple_oauth OAuth1 middleware
|
39
|
+
# @see #add_authorization_middleware
|
40
|
+
def oauth_data
|
41
|
+
{
|
42
|
+
consumer_key: @consumer_key,
|
43
|
+
consumer_secret: @consumer_secret,
|
44
|
+
token: @token,
|
45
|
+
token_secret: @token_secret
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Xero::Api
|
2
|
+
attr_accessor :access_token
|
3
|
+
|
4
|
+
module Connection::OAuth2
|
5
|
+
|
6
|
+
def self.included(*)
|
7
|
+
Xero::Api::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
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
class Xero::Api
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :fault
|
5
|
+
def initialize(errors = nil)
|
6
|
+
if errors
|
7
|
+
@fault = errors
|
8
|
+
super(errors)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raised on HTTP status code 400
|
14
|
+
class BadRequest < Error; end
|
15
|
+
|
16
|
+
# Raised on HTTP status code 401
|
17
|
+
class Unauthorized < Error; end
|
18
|
+
|
19
|
+
# Raised on HTTP status code 404
|
20
|
+
class NotFound < Error; end
|
21
|
+
|
22
|
+
# Raised on HTTP status code 412
|
23
|
+
class PreconditionFailed < Error; end
|
24
|
+
|
25
|
+
# Raised on HTTP status code 500
|
26
|
+
class InternalError < Error; end
|
27
|
+
|
28
|
+
# Raised on HTTP status code 501
|
29
|
+
class NotImplemented < Error; end
|
30
|
+
|
31
|
+
# Raised on HTTP status code 503
|
32
|
+
class ServiceUnavailable < Error; end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Xero::Api
|
2
|
+
module Methods
|
3
|
+
|
4
|
+
def get(entity, all: false, id: nil, params: nil, headers: nil, path: nil, modified_since: nil, parse_entity: true)
|
5
|
+
route = build_resource(entity, id: id, params: params, path: path)
|
6
|
+
final_headers = handle_headers(headers, modified_since)
|
7
|
+
if all
|
8
|
+
enumerator = get_all(entity, path: route, headers: final_headers, parse_entity: parse_entity)
|
9
|
+
else
|
10
|
+
request(:get, path: route, entity: entity, headers: final_headers, parse_entity: parse_entity)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(entity, payload:, params: nil, path: nil)
|
15
|
+
route = build_resource(entity, params: params, path: path)
|
16
|
+
request(:put, path: route, entity: entity, payload: payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(entity, id:, payload:, params: nil, path: nil)
|
20
|
+
route = build_resource(entity, id: id, params: params, path: path)
|
21
|
+
payload.merge!({ "Id": id })
|
22
|
+
request(:post, path: route, entity: entity, payload: payload)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(entity, id:, params: nil, path: nil)
|
26
|
+
route = build_resource(entity, id: id, path: path)
|
27
|
+
request(:delete, path: route, entity: entity)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_resource(entity, id: nil, params: nil, path: nil)
|
33
|
+
route = entity_handler(entity)
|
34
|
+
route = "#{route}/#{id}" if id
|
35
|
+
route = "#{route}/#{path}" if path
|
36
|
+
route = add_params(route: route, params: params) if params
|
37
|
+
route
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle_headers(headers, modified_since)
|
41
|
+
h = {}
|
42
|
+
h.merge!(headers) if headers
|
43
|
+
h.merge!(if_modified_hash(modified_since)) if modified_since
|
44
|
+
h
|
45
|
+
end
|
46
|
+
|
47
|
+
def if_modified_hash(modified_since)
|
48
|
+
{ 'If-Modified-Since' => standard_date(modified_since) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_all(entity, path:, headers:, parse_entity:)
|
52
|
+
max = 100
|
53
|
+
Enumerator.new do |enum_yielder|
|
54
|
+
number = 0
|
55
|
+
begin
|
56
|
+
number += 1
|
57
|
+
paged_path = add_params(route: path, params: { page: number })
|
58
|
+
results = request(:get, path: paged_path, entity: entity, headers: headers, parse_entity: parse_entity)
|
59
|
+
results.each do |result|
|
60
|
+
enum_yielder.yield(result)
|
61
|
+
end if results
|
62
|
+
end while (results ? results.size == max : false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
# @private
|
4
|
+
module FaradayMiddleware
|
5
|
+
# @private
|
6
|
+
class RaiseHttpException < Faraday::Middleware
|
7
|
+
def call(env)
|
8
|
+
@app.call(env).on_complete do |response|
|
9
|
+
case response.status
|
10
|
+
when 200
|
11
|
+
when 204
|
12
|
+
when 400
|
13
|
+
raise Xero::Api::BadRequest.new(error_message(response))
|
14
|
+
when 401
|
15
|
+
raise Xero::Api::Unauthorized.new(error_message(response))
|
16
|
+
when 404
|
17
|
+
raise Xero::Api::NotFound.new(error_message(response))
|
18
|
+
when 412
|
19
|
+
raise Xero::Api::PreconditionFailed.new(error_message(response))
|
20
|
+
when 500
|
21
|
+
raise Xero::Api::InternalError.new(error_message(response))
|
22
|
+
when 501
|
23
|
+
raise Xero::Api::NotImplemented.new(error_message(response))
|
24
|
+
when 503
|
25
|
+
raise Xero::Api::ServiceUnavailable.new(error_message(response))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(app)
|
31
|
+
super app
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def error_message(response)
|
37
|
+
error = ::JSON.parse(response.body)
|
38
|
+
rescue => e
|
39
|
+
response.body
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Xero::Api
|
2
|
+
module Util
|
3
|
+
|
4
|
+
def add_params(route:, params:)
|
5
|
+
uri = URI.parse(route)
|
6
|
+
params.each do |p|
|
7
|
+
new_query_ar = URI.decode_www_form(uri.query || '') << p.to_a
|
8
|
+
uri.query = URI.encode_www_form(new_query_ar)
|
9
|
+
end
|
10
|
+
uri.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def standard_date(date)
|
14
|
+
date.strftime('%Y-%m-%dT%H:%M:%S')
|
15
|
+
rescue => e
|
16
|
+
raise Xero::Api::Error, date_method_error_msg(e)
|
17
|
+
end
|
18
|
+
|
19
|
+
def json_date(date)
|
20
|
+
date.strftime("/Date(%s%L)/")
|
21
|
+
rescue => e
|
22
|
+
raise Xero::Api::Error, date_method_error_msg(e)
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_json_date(datestring)
|
26
|
+
seconds_since_epoch = datestring.scan(/[0-9]+/)[0].to_i / 1000.0
|
27
|
+
Time.at(seconds_since_epoch)
|
28
|
+
end
|
29
|
+
|
30
|
+
def entity_handler(entity)
|
31
|
+
if entity.is_a?(Symbol)
|
32
|
+
snake_to_camel(entity)
|
33
|
+
else
|
34
|
+
entity
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def snake_to_camel(sym)
|
39
|
+
sym.to_s.split('_').collect(&:capitalize).join
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def date_method_error_msg(e)
|
45
|
+
if e.message =~ /undefined method \`strftime/
|
46
|
+
"The argument needs to be an instance of Date|Time|DateTime"
|
47
|
+
else
|
48
|
+
e.message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
data/xero-api.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'xero/api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "xero-api"
|
8
|
+
spec.version = Xero::Api::VERSION
|
9
|
+
spec.authors = ["Christian Pelczarski"]
|
10
|
+
spec.email = ["christian@minimul.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Ruby JSON-only client for Xero API. }
|
13
|
+
spec.homepage = "https://github.com/minimul/xero-api"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency 'webmock'
|
25
|
+
spec.add_development_dependency 'faker'
|
26
|
+
spec.add_development_dependency 'simple_oauth'
|
27
|
+
spec.add_development_dependency 'dotenv'
|
28
|
+
spec.add_development_dependency 'vcr'
|
29
|
+
spec.add_development_dependency 'awesome_print'
|
30
|
+
spec.add_runtime_dependency 'faraday'
|
31
|
+
spec.add_runtime_dependency 'faraday_middleware'
|
32
|
+
spec.add_runtime_dependency 'faraday-detailed_logger'
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xero-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christian Pelczarski
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
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
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webmock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: faker
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simple_oauth
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: dotenv
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: vcr
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: awesome_print
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: faraday
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: faraday_middleware
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: faraday-detailed_logger
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description:
|
182
|
+
email:
|
183
|
+
- christian@minimul.com
|
184
|
+
executables: []
|
185
|
+
extensions: []
|
186
|
+
extra_rdoc_files: []
|
187
|
+
files:
|
188
|
+
- ".env.example_app.oauth1"
|
189
|
+
- ".env.example_app.oauth2"
|
190
|
+
- ".gitignore"
|
191
|
+
- ".travis.yml"
|
192
|
+
- Gemfile
|
193
|
+
- LICENSE.txt
|
194
|
+
- README.md
|
195
|
+
- Rakefile
|
196
|
+
- bin/console
|
197
|
+
- bin/setup
|
198
|
+
- example/base.rb
|
199
|
+
- example/oauth.rb
|
200
|
+
- example/public/connect_xero_button_blue_2x.png
|
201
|
+
- example/views/callback.erb
|
202
|
+
- example/views/customers.erb
|
203
|
+
- example/views/index.erb
|
204
|
+
- lib/xero/api.rb
|
205
|
+
- lib/xero/api/attachment.rb
|
206
|
+
- lib/xero/api/configuration.rb
|
207
|
+
- lib/xero/api/connection.rb
|
208
|
+
- lib/xero/api/connection/oauth1.rb
|
209
|
+
- lib/xero/api/connection/oauth2.rb
|
210
|
+
- lib/xero/api/error.rb
|
211
|
+
- lib/xero/api/methods.rb
|
212
|
+
- lib/xero/api/raise_http_exception.rb
|
213
|
+
- lib/xero/api/util.rb
|
214
|
+
- lib/xero/api/version.rb
|
215
|
+
- xero-api.gemspec
|
216
|
+
homepage: https://github.com/minimul/xero-api
|
217
|
+
licenses:
|
218
|
+
- MIT
|
219
|
+
metadata: {}
|
220
|
+
post_install_message:
|
221
|
+
rdoc_options: []
|
222
|
+
require_paths:
|
223
|
+
- lib
|
224
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ">="
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
229
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
230
|
+
requirements:
|
231
|
+
- - ">="
|
232
|
+
- !ruby/object:Gem::Version
|
233
|
+
version: '0'
|
234
|
+
requirements: []
|
235
|
+
rubyforge_project:
|
236
|
+
rubygems_version: 2.7.7
|
237
|
+
signing_key:
|
238
|
+
specification_version: 4
|
239
|
+
summary: Ruby JSON-only client for Xero API.
|
240
|
+
test_files: []
|