xeroizer 2.17.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +246 -213
- data/lib/xeroizer/connection.rb +49 -0
- data/lib/xeroizer/exceptions.rb +4 -0
- data/lib/xeroizer/generic_application.rb +13 -5
- data/lib/xeroizer/http.rb +7 -80
- data/lib/xeroizer/http_response.rb +154 -0
- data/lib/xeroizer/models/bank_account.rb +1 -0
- data/lib/xeroizer/models/bank_transaction.rb +1 -0
- data/lib/xeroizer/models/batch_payment.rb +27 -0
- data/lib/xeroizer/models/branding_theme.rb +49 -9
- data/lib/xeroizer/models/contact.rb +12 -6
- data/lib/xeroizer/models/contact_group.rb +45 -0
- data/lib/xeroizer/models/credit_note.rb +24 -22
- data/lib/xeroizer/models/currency.rb +14 -2
- data/lib/xeroizer/models/from_bank_account.rb +1 -0
- data/lib/xeroizer/models/history_record.rb +72 -0
- data/lib/xeroizer/models/invoice.rb +17 -3
- data/lib/xeroizer/models/item.rb +2 -1
- data/lib/xeroizer/models/item_purchase_details.rb +1 -1
- data/lib/xeroizer/models/line_item.rb +17 -5
- data/lib/xeroizer/models/manual_journal.rb +2 -1
- data/lib/xeroizer/models/online_invoice.rb +37 -0
- data/lib/xeroizer/models/option.rb +1 -1
- data/lib/xeroizer/models/organisation.rb +2 -0
- data/lib/xeroizer/models/payment_service.rb +22 -0
- data/lib/xeroizer/models/payroll/address.rb +53 -0
- data/lib/xeroizer/models/payroll/bank_account.rb +18 -6
- data/lib/xeroizer/models/payroll/benefit_line.rb +26 -0
- data/lib/xeroizer/models/payroll/benefit_type.rb +45 -0
- data/lib/xeroizer/models/payroll/deduction_line.rb +32 -0
- data/lib/xeroizer/models/payroll/deduction_type.rb +49 -0
- data/lib/xeroizer/models/payroll/earnings_line.rb +39 -0
- data/lib/xeroizer/models/payroll/earnings_type.rb +53 -0
- data/lib/xeroizer/models/payroll/employee.rb +30 -8
- data/lib/xeroizer/models/payroll/leave_application.rb +27 -0
- data/lib/xeroizer/models/payroll/leave_line.rb +30 -0
- data/lib/xeroizer/models/payroll/leave_period.rb +15 -0
- data/lib/xeroizer/models/payroll/pay_items.rb +22 -0
- data/lib/xeroizer/models/payroll/pay_run.rb +33 -0
- data/lib/xeroizer/models/payroll/pay_schedule.rb +40 -0
- data/lib/xeroizer/models/payroll/pay_template.rb +24 -0
- data/lib/xeroizer/models/payroll/payment_method.rb +24 -0
- data/lib/xeroizer/models/payroll/paystub.rb +44 -0
- data/lib/xeroizer/models/payroll/reimbursement_line.rb +21 -0
- data/lib/xeroizer/models/payroll/reimbursement_type.rb +22 -0
- data/lib/xeroizer/models/payroll/salary_and_wage.rb +29 -0
- data/lib/xeroizer/models/payroll/super_line.rb +40 -0
- data/lib/xeroizer/models/payroll/tax_declaration.rb +50 -0
- data/lib/xeroizer/models/payroll/time_off_line.rb +20 -0
- data/lib/xeroizer/models/payroll/time_off_type.rb +32 -0
- data/lib/xeroizer/models/payroll/work_location.rb +25 -0
- data/lib/xeroizer/models/prepayment.rb +1 -0
- data/lib/xeroizer/models/purchase_order.rb +6 -6
- data/lib/xeroizer/models/quote.rb +76 -0
- data/lib/xeroizer/models/schedule.rb +1 -0
- data/lib/xeroizer/models/tax_component.rb +1 -0
- data/lib/xeroizer/models/to_bank_account.rb +1 -0
- data/lib/xeroizer/oauth.rb +12 -1
- data/lib/xeroizer/oauth2.rb +82 -0
- data/lib/xeroizer/oauth2_application.rb +49 -0
- data/lib/xeroizer/payroll_application.rb +8 -3
- data/lib/xeroizer/record/base.rb +11 -2
- data/lib/xeroizer/record/base_model.rb +1 -1
- data/lib/xeroizer/record/base_model_http_proxy.rb +37 -17
- data/lib/xeroizer/record/model_definition_helper.rb +1 -1
- data/lib/xeroizer/record/payroll_base.rb +4 -0
- data/lib/xeroizer/record/record_association_helper.rb +4 -4
- data/lib/xeroizer/record/validators/associated_validator.rb +1 -0
- data/lib/xeroizer/record/xml_helper.rb +18 -18
- data/lib/xeroizer/report/aged_receivables_by_contact.rb +1 -1
- data/lib/xeroizer/report/cell_xml_helper.rb +13 -13
- data/lib/xeroizer/response.rb +22 -17
- data/lib/xeroizer/version.rb +1 -1
- data/lib/xeroizer.rb +34 -4
- data/test/acceptance/about_creating_bank_transactions_test.rb +89 -81
- data/test/acceptance/about_creating_prepayment_test.rb +25 -30
- data/test/acceptance/about_fetching_bank_transactions_test.rb +12 -12
- data/test/acceptance/about_online_invoice_test.rb +25 -0
- data/test/acceptance/acceptance_test.rb +28 -26
- data/test/acceptance/bank_transfer_test.rb +12 -17
- data/test/acceptance/bulk_operations_test.rb +18 -16
- data/test/acceptance/connections_test.rb +11 -0
- data/test/stub_responses/bad_request.json +6 -0
- data/test/stub_responses/connections.json +16 -0
- data/test/stub_responses/expired_oauth2_token.json +6 -0
- data/test/stub_responses/generic_response_error.json +6 -0
- data/test/stub_responses/invalid_oauth2_request_token.json +6 -0
- data/test/stub_responses/invalid_tenant_header.json +6 -0
- data/test/stub_responses/object_not_found.json +6 -0
- data/test/stub_responses/organisations.xml +10 -0
- data/test/stub_responses/payment_service.xml +15 -0
- data/test/test_helper.rb +17 -12
- data/test/unit/generic_application_test.rb +21 -10
- data/test/unit/http_test.rb +282 -10
- data/test/unit/models/address_test.rb +2 -2
- data/test/unit/models/bank_transaction_model_parsing_test.rb +2 -2
- data/test/unit/models/bank_transaction_test.rb +1 -1
- data/test/unit/models/bank_transaction_validation_test.rb +1 -1
- data/test/unit/models/contact_test.rb +20 -11
- data/test/unit/models/credit_note_test.rb +8 -8
- data/test/unit/models/employee_test.rb +4 -4
- data/test/unit/models/invoice_test.rb +12 -12
- data/test/unit/models/journal_line_test.rb +6 -6
- data/test/unit/models/journal_test.rb +4 -4
- data/test/unit/models/line_item_sum_test.rb +1 -1
- data/test/unit/models/line_item_test.rb +29 -37
- data/test/unit/models/manual_journal_test.rb +3 -3
- data/test/unit/models/organisation_test.rb +16 -2
- data/test/unit/models/payment_service_test.rb +29 -0
- data/test/unit/models/phone_test.rb +7 -7
- data/test/unit/models/prepayment_test.rb +4 -4
- data/test/unit/models/repeating_invoice_test.rb +3 -3
- data/test/unit/models/tax_rate_test.rb +2 -2
- data/test/unit/oauth2_test.rb +171 -0
- data/test/unit/oauth_config_test.rb +1 -1
- data/test/unit/record/base_model_test.rb +13 -13
- data/test/unit/record/base_test.rb +73 -4
- data/test/unit/record/block_validator_test.rb +1 -1
- data/test/unit/record/connection_test.rb +60 -0
- data/test/unit/record/model_definition_test.rb +36 -36
- data/test/unit/record/parse_params_test.rb +59 -0
- data/test/unit/record/parse_where_hash_test.rb +13 -13
- data/test/unit/record/record_association_test.rb +14 -14
- data/test/unit/record/validators_test.rb +43 -43
- data/test/unit/record_definition_test.rb +7 -7
- data/test/unit/report_definition_test.rb +7 -7
- data/test/unit/report_test.rb +20 -20
- data/test/unit_test_helper.rb +16 -0
- metadata +117 -27
- data/lib/xeroizer/models/payroll/home_address.rb +0 -24
- data/lib/xeroizer/partner_application.rb +0 -51
- data/lib/xeroizer/private_application.rb +0 -25
- data/lib/xeroizer/public_application.rb +0 -21
- data/test/unit/oauth_test.rb +0 -118
- data/test/unit/private_application_test.rb +0 -20
data/README.md
CHANGED
@@ -1,13 +1,20 @@
|
|
1
|
-
Xeroizer API Library
|
1
|
+
Xeroizer API Library
|
2
2
|
====================
|
3
3
|
|
4
|
-
**Homepage**: [http://waynerobinson.github.com/xeroizer](http://waynerobinson.github.com/xeroizer)
|
5
|
-
|
6
|
-
**
|
7
|
-
|
8
|
-
**
|
4
|
+
**Homepage**: [http://waynerobinson.github.com/xeroizer](http://waynerobinson.github.com/xeroizer)
|
5
|
+
|
6
|
+
**Git**: [git://github.com/waynerobinson/xeroizer.git](git://github.com/waynerobinson/xeroizer.git)
|
7
|
+
|
8
|
+
**Github**: [https://github.com/waynerobinson/xeroizer](https://github.com/waynerobinson/xeroizer)
|
9
|
+
|
10
|
+
**Author**: Wayne Robinson [http://www.wayne-robinson.com](http://www.wayne-robinson.com)
|
11
|
+
|
12
|
+
**Contributors**: See Contributors section below
|
13
|
+
|
9
14
|
**Copyright**: 2007-2013
|
10
|
-
|
15
|
+
|
16
|
+
**License**: MIT License
|
17
|
+
|
11
18
|
|
12
19
|
Introduction
|
13
20
|
------------
|
@@ -29,232 +36,184 @@ require 'rubygems'
|
|
29
36
|
require 'xeroizer'
|
30
37
|
|
31
38
|
# Create client (used to communicate with the API).
|
32
|
-
client = Xeroizer::
|
39
|
+
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID, YOUR_OAUTH2_CLIENT_SECRET)
|
33
40
|
|
34
41
|
# Retrieve list of contacts (note: all communication must be made through the client).
|
35
42
|
contacts = client.Contact.all(:order => 'Name')
|
36
43
|
```
|
37
|
-
|
44
|
+
|
38
45
|
Authentication
|
39
46
|
--------------
|
40
47
|
|
41
|
-
Xero uses OAuth to authenticate API clients. The OAuth gem (with minor modification) by John Nunemaker ([http://github.com/jnunemaker/twitter](http://github.com/jnunemaker/twitter)) is used in this library. If you've used this before, things will all seem very familar.
|
42
|
-
|
43
|
-
There are three methods of authentication detailed below:
|
44
|
-
|
45
|
-
### All: Consumer Key/Secret
|
46
|
-
|
47
|
-
All methods of authentication require your OAuth consumer key and secret. This can be found for your application
|
48
|
-
in the API management console at [http://api.xero.com](http://api.xero.com).
|
49
|
-
|
50
|
-
### Public Applications
|
51
|
-
|
52
|
-
Public applications use a 3-legged authorisation process. A user will need to authorise your
|
53
|
-
application against each organisation that you want access to. Your application can have access
|
54
|
-
to many organisations at once by going through the authorisation process for each organisation.
|
55
|
-
|
56
|
-
The access token received will expire after 30 minutes. If you want access for longer you will need
|
57
|
-
the user to re-authorise your application.
|
58
|
-
|
59
|
-
Authentication occurs in 3 steps:
|
60
|
-
|
61
|
-
```ruby
|
62
|
-
client = Xeroizer::PublicApplication.new(YOUR_OAUTH_CONSUMER_KEY, YOUR_OAUTH_CONSUMER_SECRET)
|
63
|
-
|
64
|
-
# 1. Get a RequestToken from Xero. :oauth_callback is the URL the user will be redirected to
|
65
|
-
# after they have authenticated your application.
|
66
|
-
#
|
67
|
-
# Note: The callback URL's domain must match that listed for your application in http://api.xero.com
|
68
|
-
# otherwise the user will not be redirected and only be shown the authentication code.
|
69
|
-
request_token = client.request_token(:oauth_callback => 'http://yourapp.com/oauth/callback')
|
70
|
-
|
71
|
-
# 2. Redirect the user to the URL specified by the RequestToken.
|
72
|
-
#
|
73
|
-
# Note: example uses redirect_to method defined in Rails controllers.
|
74
|
-
redirect_to request_token.authorize_url
|
75
|
-
|
76
|
-
# 3. Exchange RequestToken for AccessToken.
|
77
|
-
# This access token will be used for all subsequent requests but it is stored within the client
|
78
|
-
# application so you don't have to record it.
|
79
|
-
#
|
80
|
-
# Note: This example assumes the callback URL is a Rails action.
|
81
|
-
client.authorize_from_request(request_token.token, request_token.secret, :oauth_verifier => params[:oauth_verifier])
|
82
|
-
```
|
83
|
-
|
84
|
-
You can now use the client to access the Xero API methods, e.g.
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
contacts = client.Contact.all
|
88
|
-
```
|
89
|
-
|
90
48
|
#### Example Rails Controller
|
91
49
|
|
92
50
|
```ruby
|
93
51
|
class XeroSessionController < ApplicationController
|
94
52
|
|
95
53
|
before_filter :get_xero_client
|
96
|
-
|
54
|
+
|
97
55
|
public
|
98
|
-
|
56
|
+
|
99
57
|
def new
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
58
|
+
url = @xero_client.authorize_url(
|
59
|
+
# The URL's domain must match that listed for your application
|
60
|
+
# otherwise the user will see an invalid redirect_uri error
|
61
|
+
redirect_uri: YOUR_CALLBACK_URL,
|
62
|
+
# space separated, see all scopes at https://developer.xero.com/documentation/oauth2/scopes.
|
63
|
+
# note that `offline_access` is required to get a refresh token, otherwise the access only lasts for 30 mins and cannot be refreshed.
|
64
|
+
scope: "accounting.settings.read offline_access"
|
65
|
+
)
|
66
|
+
|
67
|
+
redirect_to url
|
105
68
|
end
|
106
|
-
|
69
|
+
|
107
70
|
def create
|
108
|
-
@xero_client.
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
71
|
+
token = @xero_client.authorize_from_code(
|
72
|
+
params[:code],
|
73
|
+
redirect_uri: YOUR_CALLBACK_URL
|
74
|
+
)
|
75
|
+
|
76
|
+
connections = @xero_client.current_connections
|
77
|
+
|
113
78
|
session[:xero_auth] = {
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
79
|
+
:access_token => token[:access_token],
|
80
|
+
:refresh_token => token[:refresh_token],
|
81
|
+
:tenant_id => connections[1][:tenant_id]
|
82
|
+
}
|
83
|
+
|
119
84
|
end
|
120
|
-
|
85
|
+
|
121
86
|
def destroy
|
122
87
|
session.data.delete(:xero_auth)
|
123
88
|
end
|
124
|
-
|
89
|
+
|
125
90
|
private
|
126
|
-
|
91
|
+
|
127
92
|
def get_xero_client
|
128
|
-
@xero_client = Xeroizer::
|
129
|
-
|
93
|
+
@xero_client = Xeroizer::OAuth2Application.new(
|
94
|
+
YOUR_OAUTH2_CLIENT_ID,
|
95
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
96
|
+
)
|
97
|
+
|
130
98
|
# Add AccessToken if authorised previously.
|
131
99
|
if session[:xero_auth]
|
132
|
-
@xero_client.
|
133
|
-
|
134
|
-
|
100
|
+
@xero_client.tenant_id = session[:xero_auth][:tenant_id]
|
101
|
+
|
102
|
+
@xero_client.authorize_from_access(session[:xero_auth][:acesss_token])
|
135
103
|
end
|
136
104
|
end
|
137
105
|
end
|
138
106
|
```
|
139
|
-
|
140
|
-
#### Storing AccessToken
|
141
107
|
|
142
|
-
|
143
|
-
tokens are only valid for 30 minutes and will raise a `Xeroizer::OAuth::TokenExpired` exception if you try to access
|
144
|
-
the API beyond the token's expiry time.
|
108
|
+
### OAuth2 Applications
|
145
109
|
|
146
|
-
|
110
|
+
For more details, checkout Xero's [documentation](https://developer.xero.com/documentation/oauth2/auth-flow)
|
147
111
|
|
112
|
+
1. Generate the authorization url and redirect the user to authenticate
|
148
113
|
```ruby
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
Private applications require a private RSA keypair which is used to sign each request to the API. You can
|
162
|
-
generate this keypair on Mac OSX or Linux with OpenSSL. For example:
|
163
|
-
|
164
|
-
openssl genrsa -out privatekey.pem 1024
|
165
|
-
openssl req -newkey rsa:1024 -x509 -key privatekey.pem -out publickey.cer -days 365
|
166
|
-
openssl pkcs12 -export -out public_privatekey.pfx -inkey privatekey.pem -in publickey.cer
|
114
|
+
client = Xeroizer::OAuth2Application.new(
|
115
|
+
YOUR_OAUTH2_CLIENT_ID,
|
116
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
117
|
+
)
|
118
|
+
url = client.authorize_url(
|
119
|
+
# The URL's domain must match that listed for your application
|
120
|
+
# otherwise the user will see an invalid redirect_uri error
|
121
|
+
redirect_uri: YOUR_CALLBACK_URL,
|
122
|
+
# space separated, see all scopes at https://developer.xero.com/documentation/oauth2/scopes.
|
123
|
+
# note that `offline_access` is required to get a refresh token, otherwise the access only lasts for 30 mins and cannot be refreshed.
|
124
|
+
scope: "accounting.settings.read offline_access"
|
125
|
+
)
|
167
126
|
|
168
|
-
|
127
|
+
# Rails as an example
|
128
|
+
redirect_to url
|
129
|
+
```
|
169
130
|
|
170
|
-
|
131
|
+
2. In the callback route, use the provided code to retrieve an access token.
|
171
132
|
|
172
133
|
```ruby
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
134
|
+
token = client.authorize_from_code(
|
135
|
+
params[:code],
|
136
|
+
redirect_uri: YOUR_CALLBACK_URL
|
137
|
+
)
|
138
|
+
token.to_hash
|
139
|
+
# {
|
140
|
+
# "token_type"=>"Bearer",
|
141
|
+
# "scope"=>"accounting.transactions.read accounting.settings.read",
|
142
|
+
# :access_token=>"...",
|
143
|
+
# :refresh_token=>nil,
|
144
|
+
# :expires_at=>1615220292
|
145
|
+
# }
|
178
146
|
|
179
|
-
|
147
|
+
# Save the access_token, refresh_token...
|
148
|
+
```
|
180
149
|
|
181
|
-
|
150
|
+
3. Retrieve the tenant ids.
|
151
|
+
```ruby
|
152
|
+
connections = client.current_connections
|
153
|
+
# returns Xeroizer::Connection instances
|
182
154
|
|
183
|
-
|
184
|
-
access the partner application in a similar way to public applications.
|
155
|
+
# Save the tenant ids
|
185
156
|
|
186
|
-
|
157
|
+
```
|
187
158
|
|
159
|
+
4. Use access token and tenant ids to retrieve data.
|
188
160
|
```ruby
|
189
|
-
client = Xeroizer::
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
# 2. Redirect the user to the URL specified by the RequestToken.
|
203
|
-
#
|
204
|
-
# Note: example uses redirect_to method defined in Rails controllers.
|
205
|
-
redirect_to request_token.authorize_url
|
161
|
+
client = Xeroizer::OAuth2Application.new(
|
162
|
+
YOUR_OAUTH2_CLIENT_ID,
|
163
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
164
|
+
access_token: access_token,
|
165
|
+
tenant_id: tenant_id
|
166
|
+
)
|
167
|
+
# OR
|
168
|
+
client = Xeroizer::OAuth2Application.new(
|
169
|
+
YOUR_OAUTH2_CLIENT_ID,
|
170
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
171
|
+
tenant_id: tenant_id
|
172
|
+
).authorize_from_access(access_token)
|
206
173
|
|
207
|
-
#
|
208
|
-
|
209
|
-
# application so you don't have to record it.
|
210
|
-
#
|
211
|
-
# Note: This example assumes the callback URL is a Rails action.
|
212
|
-
client.authorize_from_request(request_token.token, request_token.secret, :oauth_verifier => params[:oauth_verifier])
|
174
|
+
# use the client
|
175
|
+
client.Organisation.first
|
213
176
|
```
|
214
177
|
|
215
|
-
|
216
|
-
|
217
|
-
AccessToken:
|
178
|
+
#### AccessToken Renewal
|
179
|
+
Renewal of an access token requires the refresh token generated for this organisation. To renew:
|
218
180
|
|
219
181
|
```ruby
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
> **`#expires_at`**: Time this AccessToken will expire (usually 30 minutes into the future).
|
228
|
-
> **`#authorization_expires_at`**: How long this organisation has authorised you to access their data (usually 365 days into the future).
|
182
|
+
client = Xeroizer::OAuth2Application.new(
|
183
|
+
YOUR_OAUTH2_CLIENT_ID,
|
184
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
185
|
+
access_token: access_token,
|
186
|
+
refresh_token: refresh_token,
|
187
|
+
tenant_id: tenant_id
|
188
|
+
)
|
229
189
|
|
230
|
-
|
190
|
+
client.renew_access_token
|
191
|
+
```
|
192
|
+
If you lose these details at any stage you can always reauthorise by redirecting the user back to the Xero OAuth gateway.
|
231
193
|
|
232
|
-
|
194
|
+
#### Custom Connections
|
195
|
+
Custom Connections are a paid-for option for private M2M applications. The generated token expires and needs recreating if expired.
|
233
196
|
|
234
197
|
```ruby
|
235
|
-
|
236
|
-
|
198
|
+
client = Xeroizer::OAuth2Application.new(
|
199
|
+
YOUR_OAUTH2_CLIENT_ID,
|
200
|
+
YOUR_OAUTH2_CLIENT_SECRET
|
201
|
+
)
|
237
202
|
|
238
|
-
|
239
|
-
client.renew_access_token(access_token, access_secret, session_handle)
|
203
|
+
token = client.authorize_from_client_credentials
|
240
204
|
```
|
205
|
+
You can check the status of the token with the `expires?` and `expired?` methods.
|
241
206
|
|
242
|
-
This will invalidate the previous token and refresh the `access_key` and `access_secret` as specified in the
|
243
|
-
initial authorisation process. You must always know the previous token's details to renew access to this
|
244
|
-
session.
|
245
|
-
|
246
|
-
If you lose these details at any stage you can always reauthorise by redirecting the user back to the Xero OAuth gateway.
|
247
207
|
|
248
208
|
Retrieving Data
|
249
209
|
---------------
|
250
210
|
|
251
211
|
Each of the below record types is implemented within this library. To allow for multiple access tokens to be used at the same
|
252
|
-
time in a single application, the model classes are accessed from the instance of
|
253
|
-
or PartnerApplication. All class-level operations occur on this singleton. For example:
|
212
|
+
time in a single application, the model classes are accessed from the instance of OAuth2Application. All class-level operations occur on this singleton. For example:
|
254
213
|
|
255
214
|
```ruby
|
256
|
-
xero = Xeroizer::
|
257
|
-
xero.authorize_from_access(session[:xero_auth][:access_token]
|
215
|
+
xero = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID, YOUR_OAUTH2_CLIENT_SECRET, tenant_id: tenant_id)
|
216
|
+
xero.authorize_from_access(session[:xero_auth][:access_token])
|
258
217
|
|
259
218
|
contacts = xero.Contact.all(:order => 'Name')
|
260
219
|
|
@@ -264,12 +223,12 @@ saved = new_contact.save
|
|
264
223
|
|
265
224
|
### \#all([options])
|
266
225
|
|
267
|
-
Retrieves list of all records with matching options.
|
226
|
+
Retrieves list of all records with matching options.
|
268
227
|
|
269
228
|
**Note:** Some records (Invoice, CreditNote) only return summary information for the contact and no line items
|
270
|
-
when returning them this list operation. This library takes care of automatically retrieving the
|
229
|
+
when returning them this list operation. This library takes care of automatically retrieving the
|
271
230
|
contact and line items from Xero on first access however, this first access has a large performance penalty
|
272
|
-
and will count as an extra query towards your
|
231
|
+
and will count as an extra query towards your 5,000/day and 60/minute request per organisation limit.
|
273
232
|
|
274
233
|
Valid options are:
|
275
234
|
|
@@ -281,6 +240,18 @@ Valid options are:
|
|
281
240
|
|
282
241
|
> Field to order by. Should be formatted as Xero-based field (e.g. 'Name', 'ContactID', etc)
|
283
242
|
|
243
|
+
> **:status**
|
244
|
+
|
245
|
+
> Status field for PurchaseOrder. Should be a valid Xero purchase order status.
|
246
|
+
|
247
|
+
> **:date_from**
|
248
|
+
|
249
|
+
> DateFrom field for PurchaseOrder. Should be in YYYY-MM-DD format.
|
250
|
+
|
251
|
+
> **:date_to**
|
252
|
+
|
253
|
+
> DateTo field for PurchaseOrder. Should be in YYYY-MM-DD format.
|
254
|
+
|
284
255
|
> **:where**
|
285
256
|
|
286
257
|
> __See *Where Filters* section below.__
|
@@ -305,11 +276,11 @@ You can specify find filters by providing the :where option with a hash. For exa
|
|
305
276
|
```ruby
|
306
277
|
invoices = Xero.Invoice.all(:where => {:type => 'ACCREC', :amount_due_is_not => 0})
|
307
278
|
```
|
308
|
-
|
279
|
+
|
309
280
|
will automatically create the Xero string:
|
310
281
|
|
311
282
|
Type=="ACCREC"&&AmountDue<>0
|
312
|
-
|
283
|
+
|
313
284
|
The default method for filtering is the equality '==' operator however, these can be overridden
|
314
285
|
by modifying the postfix of the attribute name (as you can see for the :amount\_due field above).
|
315
286
|
|
@@ -318,9 +289,9 @@ by modifying the postfix of the attribute name (as you can see for the :amount\_
|
|
318
289
|
\{attribute_name}_is_greater_than_or_equal_to will use '>='
|
319
290
|
\{attribute_name}_is_less_than will use '<'
|
320
291
|
\{attribute_name}_is_less_than_or_equal_to will use '<='
|
321
|
-
|
292
|
+
|
322
293
|
The default is '=='
|
323
|
-
|
294
|
+
|
324
295
|
**Note:** Currently, the hash-conversion library only allows for AND-based criteria and doesn't
|
325
296
|
take into account associations. For these, please use the custom filter method below.
|
326
297
|
|
@@ -332,15 +303,15 @@ in the resulting response, including all nested XML elements.
|
|
332
303
|
**Example 1: Retrieve all invoices for a specific contact ID:**
|
333
304
|
|
334
305
|
invoices = xero.Invoice.all(:where => 'Contact.ContactID.ToString()=="cd09aa49-134d-40fb-a52b-b63c6a91d712"')
|
335
|
-
|
306
|
+
|
336
307
|
**Example 2: Retrieve all unpaid ACCREC Invoices against a particular Contact Name:**
|
337
|
-
|
308
|
+
|
338
309
|
invoices = xero.Invoice.all(:where => 'Contact.Name=="Basket Case" && Type=="ACCREC" && AmountDue<>0')
|
339
|
-
|
310
|
+
|
340
311
|
**Example 3: Retrieve all Invoices PAID between certain dates**
|
341
|
-
|
312
|
+
|
342
313
|
invoices = xero.Invoice.all(:where => 'FullyPaidOnDate>=DateTime.Parse("2010-01-01T00:00:00")&&FullyPaidOnDate<=DateTime.Parse("2010-01-08T00:00:00")')
|
343
|
-
|
314
|
+
|
344
315
|
**Example 4: Retrieve all Invoices using Paging (batches of 100)**
|
345
316
|
|
346
317
|
invoices = xero.Invoice.find_in_batches({page_number: 1}) do |invoice_batch|
|
@@ -348,15 +319,15 @@ in the resulting response, including all nested XML elements.
|
|
348
319
|
...
|
349
320
|
end
|
350
321
|
end
|
351
|
-
|
322
|
+
|
352
323
|
**Example 5: Retrieve all Bank Accounts:**
|
353
|
-
|
324
|
+
|
354
325
|
accounts = xero.Account.all(:where => 'Type=="BANK"')
|
355
|
-
|
326
|
+
|
356
327
|
**Example 6: Retrieve all DELETED or VOIDED Invoices:**
|
357
|
-
|
328
|
+
|
358
329
|
invoices = xero.Invoice.all(:where => 'Status=="VOIDED" OR Status=="DELETED"')
|
359
|
-
|
330
|
+
|
360
331
|
**Example 7: Retrieve all contacts with specific text in the contact name:**
|
361
332
|
|
362
333
|
contacts = xero.Contact.all(:where => 'Name.Contains("Peter")')
|
@@ -376,7 +347,7 @@ invoice.line_items.each do | line_item |
|
|
376
347
|
puts "Line Description: #{line_item.description}"
|
377
348
|
end
|
378
349
|
```
|
379
|
-
|
350
|
+
|
380
351
|
**belongs\_to example:**
|
381
352
|
|
382
353
|
```ruby
|
@@ -430,21 +401,21 @@ contact.add_phone(:type => 'DEFAULT', :area_code => '07', :number => '3033 1234'
|
|
430
401
|
contact.add_phone(:type => 'MOBILE', :number => '0412 123 456')
|
431
402
|
contact.save
|
432
403
|
```
|
433
|
-
|
404
|
+
|
434
405
|
To add to a `has_many` association use the `add_{association}` method. For example:
|
435
406
|
|
436
407
|
```ruby
|
437
408
|
contact.add_address(:type => 'STREET', :line1 => '12 Testing Lane', :city => 'Brisbane')
|
438
409
|
```
|
439
|
-
|
410
|
+
|
440
411
|
To add to a `belongs_to` association use the `build_{association}` method. For example:
|
441
|
-
|
412
|
+
|
442
413
|
```ruby
|
443
414
|
invoice.build_contact(:name => 'ABC Company')
|
444
415
|
```
|
445
416
|
|
446
417
|
### Updating
|
447
|
-
|
418
|
+
|
448
419
|
If the primary GUID for the record is present, the library will attempt to update the record instead of
|
449
420
|
creating it. It is important that this record is downloaded from the Xero API first before attempting
|
450
421
|
an update. For example:
|
@@ -454,13 +425,20 @@ contact = xero.Contact.find("cd09aa49-134d-40fb-a52b-b63c6a91d712")
|
|
454
425
|
contact.name = "Another Name Change"
|
455
426
|
contact.save
|
456
427
|
```
|
457
|
-
|
458
|
-
Have a look at the models in `lib/xeroizer/models/` to see the valid attributes, associations and
|
428
|
+
|
429
|
+
Have a look at the models in `lib/xeroizer/models/` to see the valid attributes, associations and
|
459
430
|
minimum validation requirements for each of the record types.
|
460
431
|
|
432
|
+
Some Xero endpoints, such as Payment, will only accept specific attributes for updates. Because the library does not have this knowledge encoded (and doesn't do dirty tracking of attributes), it's necessary to construct new objects instead of using the ones retrieved from Xero:
|
433
|
+
|
434
|
+
```ruby
|
435
|
+
delete_payment = gateway.Payment.build(id: payment.id, status: 'DELETED')
|
436
|
+
delete_payment.save
|
437
|
+
```
|
438
|
+
|
461
439
|
### Bulk Creates & Updates
|
462
440
|
|
463
|
-
Xero has a hard daily limit on the number of API requests you can make (currently
|
441
|
+
Xero has a hard daily limit on the number of API requests you can make (currently 5,000 requests
|
464
442
|
per account per day). To save on requests, you can batch creates and updates into a single PUT or
|
465
443
|
POST call, like so:
|
466
444
|
|
@@ -501,7 +479,7 @@ saved = contact.save
|
|
501
479
|
|
502
480
|
# contact.errors will contain [[:name, "can't be blank"]]
|
503
481
|
```
|
504
|
-
|
482
|
+
|
505
483
|
\#errors\_for(:attribute\_name) is a helper method to return just the errors associated with
|
506
484
|
that attribute. For example:
|
507
485
|
|
@@ -512,6 +490,43 @@ contact.errors_for(:name) # will contain ["can't be blank"]
|
|
512
490
|
If something goes really wrong and the particular validation isn't handled by the internal
|
513
491
|
validators then the library may raise a `Xeroizer::ApiException`.
|
514
492
|
|
493
|
+
Example Use Cases
|
494
|
+
-------
|
495
|
+
|
496
|
+
Creating & Paying an invoice:
|
497
|
+
|
498
|
+
```ruby
|
499
|
+
contact = xero.Contact.first
|
500
|
+
|
501
|
+
# Build the Invoice, add a LineItem and save it
|
502
|
+
invoice = xero.Invoice.build(:type => "ACCREC", :contact => contact, :date => DateTime.new(2017,10,19), :due_date => DateTime.new(2017,11,19))
|
503
|
+
|
504
|
+
invoice.add_line_item(:description => 'test', :unit_amount => '200.00', :quantity => '1', :account_code => '200')
|
505
|
+
|
506
|
+
invoice.save
|
507
|
+
|
508
|
+
# An invoice created without a status will default to 'DRAFT'
|
509
|
+
invoice.approved?
|
510
|
+
|
511
|
+
# Payments can only be created against 'AUTHORISED' invoices
|
512
|
+
invoice.approve!
|
513
|
+
|
514
|
+
# Find the first bank account
|
515
|
+
bank_account = xero.Account.first(:where => {:type => 'BANK'})
|
516
|
+
|
517
|
+
# Create & save the payment
|
518
|
+
payment = xero.Payment.build(:invoice => invoice, :account => bank_account, :amount => '220.00')
|
519
|
+
payment.save
|
520
|
+
|
521
|
+
# Reload the invoice from the Xero API
|
522
|
+
invoice = xero.Invoice.find(invoice.id)
|
523
|
+
|
524
|
+
# Invoice status is now "PAID" & Payment details have been returned as well
|
525
|
+
invoice.status
|
526
|
+
invoice.payments.first
|
527
|
+
invoice.payments.first.date
|
528
|
+
```
|
529
|
+
|
515
530
|
Reports
|
516
531
|
-------
|
517
532
|
|
@@ -526,6 +541,8 @@ Reports are accessed like the following example:
|
|
526
541
|
```ruby
|
527
542
|
trial_balance = xero.TrialBalance.get(:date => DateTime.new(2011,3,21))
|
528
543
|
|
544
|
+
profit_and_loss = xero.ProfitAndLoss.get(fromDate: Date.new(2019,4,1), toDate: Date.new(2019,5,1))
|
545
|
+
|
529
546
|
# Array containing report headings.
|
530
547
|
trial_balance.header.cells.map { | cell | cell.value }
|
531
548
|
|
@@ -545,16 +562,16 @@ trial_balance.rows.each do | row |
|
|
545
562
|
case row
|
546
563
|
when Xeroizer::Report::HeaderRow
|
547
564
|
# do something with header
|
548
|
-
|
565
|
+
|
549
566
|
when Xeroizer::Report::SectionRow
|
550
567
|
# do something with section, will need to step into the rows for this section
|
551
|
-
|
568
|
+
|
552
569
|
when Xeroizer::Report::Row
|
553
570
|
# do something for standard report rows
|
554
|
-
|
571
|
+
|
555
572
|
when Xeroizer::Report::SummaryRow
|
556
573
|
# do something for summary rows
|
557
|
-
|
574
|
+
|
558
575
|
end
|
559
576
|
end
|
560
577
|
```
|
@@ -576,8 +593,8 @@ You can set this option when initializing an application:
|
|
576
593
|
|
577
594
|
```ruby
|
578
595
|
# Sleep for 2 seconds every time the rate limit is exceeded.
|
579
|
-
client = Xeroizer::
|
580
|
-
|
596
|
+
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
|
597
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
581
598
|
:rate_limit_sleep => 2)
|
582
599
|
```
|
583
600
|
|
@@ -593,9 +610,9 @@ If required, the library can handle these exceptions internally by sleeping 1 se
|
|
593
610
|
You can set this option when initializing an application:
|
594
611
|
|
595
612
|
```ruby
|
596
|
-
# Sleep for
|
597
|
-
client = Xeroizer::
|
598
|
-
|
613
|
+
# Sleep for 1 second and retry up to 3 times when Xero claims the nonce was used.
|
614
|
+
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
|
615
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
599
616
|
:nonce_used_max_attempts => 3)
|
600
617
|
```
|
601
618
|
|
@@ -607,8 +624,8 @@ You can add an optional parameter to the Xeroizer Application initialization, to
|
|
607
624
|
|
608
625
|
```ruby
|
609
626
|
XeroLogger = Logger.new('log/xero.log', 'weekly')
|
610
|
-
client = Xeroizer::
|
611
|
-
|
627
|
+
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
|
628
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
612
629
|
:logger => XeroLogger)
|
613
630
|
```
|
614
631
|
|
@@ -620,10 +637,10 @@ time Xeroizer makes an HTTP request, which is potentially useful for both
|
|
620
637
|
throttling and logging:
|
621
638
|
|
622
639
|
```ruby
|
623
|
-
Xeroizer::
|
640
|
+
Xeroizer::OAuth2Application.new(
|
624
641
|
credentials[:key], credentials[:secret],
|
625
642
|
before_request: ->(request) { puts "Hitting this URL: #{request.url}" },
|
626
|
-
after_request: ->(request, response) { puts "Got this response: #{response.code}" }
|
643
|
+
after_request: ->(request, response) { puts "Got this response: #{response.code}" },
|
627
644
|
around_request: -> (request, &block) { puts "About to send request"; block.call; puts "After request"}
|
628
645
|
)
|
629
646
|
```
|
@@ -639,14 +656,30 @@ By default, the API accepts unit prices (UnitAmount) to two decimals places. If
|
|
639
656
|
|
640
657
|
|
641
658
|
```ruby
|
642
|
-
client = Xeroizer::
|
643
|
-
|
659
|
+
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
|
660
|
+
YOUR_OAUTH2_CLIENT_SECRET,
|
644
661
|
:unitdp => 4)
|
645
662
|
```
|
646
663
|
|
647
664
|
This option adds the unitdp=4 query string parameter to all requests for models with line items - invoices, credit notes, bank transactions and receipts.
|
648
665
|
|
666
|
+
Tests
|
667
|
+
-----
|
668
|
+
|
669
|
+
OAuth2 Tests
|
670
|
+
|
671
|
+
The tests within the repository can be run by setting up a [OAuth2 App](https://developer.xero.com/documentation/guides/oauth2/auth-flow/). You can create a Private App in the [developer portal](https://developer.xero.com/myapps/), it's suggested that you create it against the [Demo Company (AU)](https://developer.xero.com/documentation/getting-started/development-accounts). Demo Company expires after 28 days, so you will need to reset it and re-connect to it if your Demo Company has expired. Make sure you create the Demo Company in Australia region.
|
672
|
+
|
673
|
+
```
|
674
|
+
export XERO_CLIENT_ID="asd"
|
675
|
+
export XERO_CLIENT_SECRET="asdfg"
|
676
|
+
export XERO_ACCESS_TOKEN="sadfsdf"
|
677
|
+
export XERO_TENANT_ID="asdfasdfasdfasd"
|
678
|
+
|
679
|
+
rake test
|
680
|
+
```
|
681
|
+
|
649
682
|
### Contributors
|
650
|
-
Xeroizer was inspired by the https://github.com/tlconnor/xero_gateway gem created by Tim Connor
|
651
|
-
and Nik Wakelin and portions of the networking and authentication code are based completely off
|
683
|
+
Xeroizer was inspired by the https://github.com/tlconnor/xero_gateway gem created by Tim Connor
|
684
|
+
and Nik Wakelin and portions of the networking and authentication code are based completely off
|
652
685
|
this project. Copyright for these components remains held in the name of Tim Connor.
|