xeroizer 2.16.5 → 2.17.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 +4 -4
- data/README.md +21 -27
- data/lib/class_level_inheritable_attributes.rb +3 -0
- data/lib/xeroizer.rb +13 -1
- data/lib/xeroizer/exceptions.rb +1 -1
- data/lib/xeroizer/generic_application.rb +13 -1
- data/lib/xeroizer/http.rb +128 -120
- data/lib/xeroizer/models/account.rb +1 -0
- data/lib/xeroizer/models/address.rb +18 -9
- data/lib/xeroizer/models/attachment.rb +1 -1
- data/lib/xeroizer/models/balances.rb +1 -0
- data/lib/xeroizer/models/bank_transaction.rb +2 -2
- data/lib/xeroizer/models/bank_transfer.rb +28 -0
- data/lib/xeroizer/models/contact.rb +11 -4
- data/lib/xeroizer/models/credit_note.rb +9 -4
- data/lib/xeroizer/models/from_bank_account.rb +12 -0
- data/lib/xeroizer/models/invoice.rb +11 -3
- data/lib/xeroizer/models/invoice_reminder.rb +19 -0
- data/lib/xeroizer/models/line_item.rb +21 -9
- data/lib/xeroizer/models/manual_journal.rb +6 -0
- data/lib/xeroizer/models/organisation.rb +4 -0
- data/lib/xeroizer/models/overpayment.rb +40 -0
- data/lib/xeroizer/models/payroll/bank_account.rb +23 -0
- data/lib/xeroizer/models/payroll/employee.rb +45 -0
- data/lib/xeroizer/models/payroll/home_address.rb +24 -0
- data/lib/xeroizer/models/prepayment.rb +1 -0
- data/lib/xeroizer/models/tax_rate.rb +5 -4
- data/lib/xeroizer/models/to_bank_account.rb +12 -0
- data/lib/xeroizer/oauth.rb +8 -8
- data/lib/xeroizer/partner_application.rb +4 -8
- data/lib/xeroizer/payroll_application.rb +28 -0
- data/lib/xeroizer/private_application.rb +2 -4
- data/lib/xeroizer/record/base_model.rb +137 -122
- data/lib/xeroizer/record/base_model_http_proxy.rb +1 -1
- data/lib/xeroizer/record/payroll_base.rb +25 -0
- data/lib/xeroizer/record/payroll_base_model.rb +29 -0
- data/lib/xeroizer/record/record_association_helper.rb +13 -14
- data/lib/xeroizer/record/validation_helper.rb +1 -1
- data/lib/xeroizer/record/xml_helper.rb +10 -8
- data/lib/xeroizer/report/aged_receivables_by_contact.rb +0 -1
- data/lib/xeroizer/report/base.rb +0 -1
- data/lib/xeroizer/report/factory.rb +3 -3
- data/lib/xeroizer/report/row/header.rb +4 -4
- data/lib/xeroizer/report/row/xml_helper.rb +2 -2
- data/lib/xeroizer/version.rb +1 -1
- data/test/acceptance/about_creating_prepayment_test.rb +46 -0
- data/test/acceptance/about_fetching_bank_transactions_test.rb +21 -21
- data/test/acceptance/acceptance_test.rb +4 -4
- data/test/acceptance/bank_transaction_reference_data.rb +3 -1
- data/test/acceptance/bank_transfer_test.rb +26 -0
- data/test/test_helper.rb +6 -4
- data/test/unit/models/address_test.rb +92 -0
- data/test/unit/models/bank_transaction_validation_test.rb +1 -1
- data/test/unit/models/contact_test.rb +20 -4
- data/test/unit/models/line_item_test.rb +20 -6
- data/test/unit/models/tax_rate_test.rb +52 -1
- data/test/unit/record/base_model_test.rb +1 -1
- data/test/unit/record/base_test.rb +9 -6
- data/test/unit/record/model_definition_test.rb +2 -2
- data/test/unit/report_test.rb +19 -21
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b4149d832f1cb36887a460f7f8258d049fdd991
|
4
|
+
data.tar.gz: c8ea7a95afb25ee8f41f7a1f15f9f6ce4ae3914e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbaef5a37d2b986f9e81f7b2cd229f501d711563f5634f2cc50125f541cca510386e5110dae6a483fd4782db4213641cc827ab6a6cab43b33bc43deaeb68c6ea
|
7
|
+
data.tar.gz: e37566c54ffddbc031712a08ff0c3179ea26f8def7f75c709d9430ed01a185f70f639976741599bef4307ac9846034c533c060e982129311f23f7f0aaf60b884
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Xeroizer API Library 
|
1
|
+
Xeroizer API Library  [](https://travis-ci.org/waynerobinson/xeroizer)
|
2
2
|
====================
|
3
3
|
|
4
4
|
**Homepage**: [http://waynerobinson.github.com/xeroizer](http://waynerobinson.github.com/xeroizer)
|
@@ -114,8 +114,8 @@ class XeroSessionController < ApplicationController
|
|
114
114
|
:access_token => @xero_client.access_token.token,
|
115
115
|
:access_key => @xero_client.access_token.secret }
|
116
116
|
|
117
|
-
session
|
118
|
-
|
117
|
+
session.data.delete(:request_token)
|
118
|
+
session.data.delete(:request_secret)
|
119
119
|
end
|
120
120
|
|
121
121
|
def destroy
|
@@ -176,22 +176,9 @@ contacts = client.Contact.all
|
|
176
176
|
|
177
177
|
### Partner Applications
|
178
178
|
|
179
|
-
Partner applications use a combination of 3-legged authorisation
|
180
|
-
certificate signing.
|
179
|
+
Partner applications use a combination of 3-legged authorisation and private key message signing.
|
181
180
|
|
182
|
-
|
183
|
-
get permission to create a partner application and for them to send you information on obtaining your client-side SSL
|
184
|
-
certificate.
|
185
|
-
|
186
|
-
Ruby's OpenSSL library requires the certificate and private key to be extracted from the `entrust-client.p12` file
|
187
|
-
downloaded via Xero's instructions. To extract:
|
188
|
-
|
189
|
-
openssl pkcs12 -in entrust-client.p12 -clcerts -nokeys -out entrust-cert.pem
|
190
|
-
openssl pkcs12 -in entrust-client.p12 -nocerts -out entrust-private.pem
|
191
|
-
openssl rsa -in entrust-private.pem -out entrust-private-nopass.pem
|
192
|
-
|
193
|
-
# This last step removes the password that you added to the private key
|
194
|
-
# when it was exported.
|
181
|
+
You will need to contact Xero (network@xero.com) to get permission to create a partner application.
|
195
182
|
|
196
183
|
After you have followed the instructions provided by Xero for partner applications and uploaded your certificate you can
|
197
184
|
access the partner application in a similar way to public applications.
|
@@ -202,9 +189,7 @@ Authentication occcurs in 3 steps:
|
|
202
189
|
client = Xeroizer::PartnerApplication.new(
|
203
190
|
YOUR_OAUTH_CONSUMER_KEY,
|
204
191
|
YOUR_OAUTH_CONSUMER_SECRET,
|
205
|
-
"/path/to/privatekey.pem"
|
206
|
-
"/path/to/entrust-cert.pem",
|
207
|
-
"/path/to/entrust-private-nopass.pem"
|
192
|
+
"/path/to/privatekey.pem"
|
208
193
|
)
|
209
194
|
|
210
195
|
# 1. Get a RequestToken from Xero. :oauth_callback is the URL the user will be redirected to
|
@@ -356,15 +341,23 @@ in the resulting response, including all nested XML elements.
|
|
356
341
|
|
357
342
|
invoices = xero.Invoice.all(:where => 'FullyPaidOnDate>=DateTime.Parse("2010-01-01T00:00:00")&&FullyPaidOnDate<=DateTime.Parse("2010-01-08T00:00:00")')
|
358
343
|
|
359
|
-
**Example 4: Retrieve all
|
344
|
+
**Example 4: Retrieve all Invoices using Paging (batches of 100)**
|
345
|
+
|
346
|
+
invoices = xero.Invoice.find_in_batches({page_number: 1}) do |invoice_batch|
|
347
|
+
invoice_batch.each do |invoice|
|
348
|
+
...
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
**Example 5: Retrieve all Bank Accounts:**
|
360
353
|
|
361
354
|
accounts = xero.Account.all(:where => 'Type=="BANK"')
|
362
355
|
|
363
|
-
**Example
|
356
|
+
**Example 6: Retrieve all DELETED or VOIDED Invoices:**
|
364
357
|
|
365
358
|
invoices = xero.Invoice.all(:where => 'Status=="VOIDED" OR Status=="DELETED"')
|
366
359
|
|
367
|
-
**Example
|
360
|
+
**Example 7: Retrieve all contacts with specific text in the contact name:**
|
368
361
|
|
369
362
|
contacts = xero.Contact.all(:where => 'Name.Contains("Peter")')
|
370
363
|
contacts = xero.Contact.all(:where => 'Name.StartsWith("Pet")')
|
@@ -481,7 +474,7 @@ end
|
|
481
474
|
```
|
482
475
|
|
483
476
|
`batch_save` will issue one PUT request for every 2,000 unsaved records built within its block, and one
|
484
|
-
POST request for
|
477
|
+
POST request for every 2,000 existing records that have been altered within its block. If any of the
|
485
478
|
unsaved records aren't valid, it'll return `false` before sending anything across the wire;
|
486
479
|
otherwise, it returns `true`. `batch_save` takes one optional argument: the number of records to
|
487
480
|
create/update per request. (Defaults to 2,000.)
|
@@ -572,7 +565,7 @@ Xero API Rate Limits
|
|
572
565
|
The Xero API imposes the following limits on calls per organisation:
|
573
566
|
|
574
567
|
* A limit of 60 API calls in any 60 second period
|
575
|
-
* A limit of
|
568
|
+
* A limit of 5000 API calls in any 24 hour period
|
576
569
|
|
577
570
|
By default, the library will raise a `Xeroizer::OAuth::RateLimitExceeded`
|
578
571
|
exception when one of these limits is exceeded.
|
@@ -622,7 +615,7 @@ client = Xeroizer::PublicApplication.new(YOUR_OAUTH_CONSUMER_KEY,
|
|
622
615
|
HTTP Callbacks
|
623
616
|
--------------------
|
624
617
|
|
625
|
-
You can provide "before" and "
|
618
|
+
You can provide "before", "after" and "around" callbacks which will be invoked every
|
626
619
|
time Xeroizer makes an HTTP request, which is potentially useful for both
|
627
620
|
throttling and logging:
|
628
621
|
|
@@ -631,6 +624,7 @@ Xeroizer::PublicApplication.new(
|
|
631
624
|
credentials[:key], credentials[:secret],
|
632
625
|
before_request: ->(request) { puts "Hitting this URL: #{request.url}" },
|
633
626
|
after_request: ->(request, response) { puts "Got this response: #{response.code}" }
|
627
|
+
around_request: -> (request, &block) { puts "About to send request"; block.call; puts "After request"}
|
634
628
|
)
|
635
629
|
```
|
636
630
|
|
@@ -16,10 +16,13 @@ module ClassLevelInheritableAttributes
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def inherited(subclass)
|
19
|
+
original_verbose, $VERBOSE = $VERBOSE, nil
|
19
20
|
@xeroizer_inheritable_attributes.each do |inheritable_attribute|
|
20
21
|
instance_var = "@#{inheritable_attribute}"
|
21
22
|
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
22
23
|
end
|
24
|
+
$VERBOSE = original_verbose
|
25
|
+
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
data/lib/xeroizer.rb
CHANGED
@@ -15,13 +15,15 @@ require 'cgi'
|
|
15
15
|
$: << File.expand_path(File.dirname(__FILE__))
|
16
16
|
|
17
17
|
require 'class_level_inheritable_attributes'
|
18
|
+
require 'xeroizer/exceptions'
|
18
19
|
require 'xeroizer/oauth'
|
19
20
|
require 'xeroizer/http_encoding_helper'
|
20
21
|
require 'xeroizer/http'
|
21
|
-
require 'xeroizer/exceptions'
|
22
22
|
|
23
23
|
require 'xeroizer/record/base_model'
|
24
|
+
require 'xeroizer/record/payroll_base_model'
|
24
25
|
require 'xeroizer/record/base'
|
26
|
+
require 'xeroizer/record/payroll_base'
|
25
27
|
require 'xeroizer/configuration'
|
26
28
|
|
27
29
|
# Include models
|
@@ -31,6 +33,9 @@ require 'xeroizer/models/allocation'
|
|
31
33
|
require 'xeroizer/models/branding_theme'
|
32
34
|
require 'xeroizer/models/bank_transaction'
|
33
35
|
require 'xeroizer/models/bank_account'
|
36
|
+
require 'xeroizer/models/from_bank_account'
|
37
|
+
require 'xeroizer/models/to_bank_account'
|
38
|
+
require 'xeroizer/models/bank_transfer'
|
34
39
|
require 'xeroizer/models/contact'
|
35
40
|
require 'xeroizer/models/contact_group'
|
36
41
|
require 'xeroizer/models/credit_note'
|
@@ -38,6 +43,7 @@ require 'xeroizer/models/currency'
|
|
38
43
|
require 'xeroizer/models/employee'
|
39
44
|
require 'xeroizer/models/expense_claim'
|
40
45
|
require 'xeroizer/models/invoice'
|
46
|
+
require 'xeroizer/models/invoice_reminder'
|
41
47
|
require 'xeroizer/models/item'
|
42
48
|
require 'xeroizer/models/item_purchase_details'
|
43
49
|
require 'xeroizer/models/item_sales_details'
|
@@ -50,6 +56,7 @@ require 'xeroizer/models/option'
|
|
50
56
|
require 'xeroizer/models/organisation'
|
51
57
|
require 'xeroizer/models/payment'
|
52
58
|
require 'xeroizer/models/prepayment'
|
59
|
+
require 'xeroizer/models/overpayment'
|
53
60
|
require 'xeroizer/models/phone'
|
54
61
|
require 'xeroizer/models/purchase_order'
|
55
62
|
require 'xeroizer/models/receipt'
|
@@ -64,6 +71,10 @@ require 'xeroizer/models/journal_line_tracking_category'
|
|
64
71
|
require 'xeroizer/models/contact_sales_tracking_category'
|
65
72
|
require 'xeroizer/models/contact_purchases_tracking_category'
|
66
73
|
|
74
|
+
require 'xeroizer/models/payroll/home_address'
|
75
|
+
require 'xeroizer/models/payroll/bank_account'
|
76
|
+
require 'xeroizer/models/payroll/employee'
|
77
|
+
|
67
78
|
require 'xeroizer/report/factory'
|
68
79
|
|
69
80
|
require 'xeroizer/response'
|
@@ -72,3 +83,4 @@ require 'xeroizer/generic_application'
|
|
72
83
|
require 'xeroizer/public_application'
|
73
84
|
require 'xeroizer/private_application'
|
74
85
|
require 'xeroizer/partner_application'
|
86
|
+
require 'xeroizer/payroll_application'
|
data/lib/xeroizer/exceptions.rb
CHANGED
@@ -7,7 +7,7 @@ module Xeroizer
|
|
7
7
|
extend Record::ApplicationHelper
|
8
8
|
|
9
9
|
attr_reader :client, :xero_url, :logger, :rate_limit_sleep, :rate_limit_max_attempts,
|
10
|
-
:default_headers, :unitdp, :before_request, :after_request, :nonce_used_max_attempts
|
10
|
+
:default_headers, :unitdp, :before_request, :after_request, :around_request, :nonce_used_max_attempts
|
11
11
|
|
12
12
|
extend Forwardable
|
13
13
|
def_delegators :client, :access_token
|
@@ -16,6 +16,7 @@ module Xeroizer
|
|
16
16
|
record :Allocation
|
17
17
|
record :Attachment
|
18
18
|
record :BrandingTheme
|
19
|
+
record :Balances
|
19
20
|
record :Contact
|
20
21
|
record :ContactGroup
|
21
22
|
record :CreditNote
|
@@ -23,12 +24,15 @@ module Xeroizer
|
|
23
24
|
record :Employee
|
24
25
|
record :ExpenseClaim
|
25
26
|
record :Invoice
|
27
|
+
record :InvoiceReminder
|
26
28
|
record :Item
|
27
29
|
record :Journal
|
30
|
+
record :LineItem
|
28
31
|
record :ManualJournal
|
29
32
|
record :Organisation
|
30
33
|
record :Payment
|
31
34
|
record :Prepayment
|
35
|
+
record :Overpayment
|
32
36
|
record :PurchaseOrder
|
33
37
|
record :Receipt
|
34
38
|
record :RepeatingInvoice
|
@@ -37,6 +41,7 @@ module Xeroizer
|
|
37
41
|
record :TrackingCategory
|
38
42
|
record :TrackingCategoryChild
|
39
43
|
record :BankTransaction
|
44
|
+
record :BankTransfer
|
40
45
|
record :User
|
41
46
|
|
42
47
|
report :AgedPayablesByContact
|
@@ -63,10 +68,17 @@ module Xeroizer
|
|
63
68
|
@default_headers = options[:default_headers] || {}
|
64
69
|
@before_request = options.delete(:before_request)
|
65
70
|
@after_request = options.delete(:after_request)
|
71
|
+
@around_request = options.delete(:around_request)
|
66
72
|
@client = OAuth.new(consumer_key, consumer_secret, options.merge({default_headers: default_headers}))
|
67
73
|
@logger = options[:logger] || false
|
68
74
|
@unitdp = options[:unitdp] || 2
|
69
75
|
end
|
70
76
|
|
77
|
+
def payroll(options = {})
|
78
|
+
xero_client = self.clone
|
79
|
+
xero_client.xero_url = options[:xero_url] || "https://api.xero.com/payroll.xro/1.0"
|
80
|
+
@payroll ||= PayrollApplication.new(xero_client)
|
81
|
+
end
|
82
|
+
|
71
83
|
end
|
72
84
|
end
|
data/lib/xeroizer/http.rb
CHANGED
@@ -14,7 +14,7 @@
|
|
14
14
|
|
15
15
|
module Xeroizer
|
16
16
|
module Http
|
17
|
-
class BadResponse <
|
17
|
+
class BadResponse < XeroizerError; end
|
18
18
|
RequestInfo = Struct.new(:url, :headers, :params, :body)
|
19
19
|
|
20
20
|
ACCEPT_MIME_MAP = {
|
@@ -53,90 +53,100 @@ module Xeroizer
|
|
53
53
|
|
54
54
|
private
|
55
55
|
|
56
|
-
|
57
|
-
|
56
|
+
def http_request(client, method, url, body, params = {})
|
57
|
+
# headers = {'Accept-Encoding' => 'gzip, deflate'}
|
58
58
|
|
59
|
-
|
59
|
+
headers = self.default_headers.merge({ 'charset' => 'utf-8' })
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
# include the unitdp query string parameter
|
62
|
+
params.merge!(unitdp_param(url))
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
if method != :get
|
65
|
+
headers['Content-Type'] ||= "application/x-www-form-urlencoded"
|
66
|
+
end
|
67
67
|
|
68
|
-
|
69
|
-
|
68
|
+
content_type = params.delete(:content_type)
|
69
|
+
headers['Content-Type'] = content_type if content_type
|
70
70
|
|
71
|
-
|
72
|
-
|
71
|
+
# HAX. Xero completely misuse the If-Modified-Since HTTP header.
|
72
|
+
headers['If-Modified-Since'] = params.delete(:ModifiedAfter).utc.strftime("%Y-%m-%dT%H:%M:%S") if params[:ModifiedAfter]
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
74
|
+
# Allow 'Accept' header to be specified with :accept parameter.
|
75
|
+
# Valid values are :pdf or :json.
|
76
|
+
if params[:response]
|
77
|
+
response_type = params.delete(:response)
|
78
|
+
headers['Accept'] = case response_type
|
79
|
+
when Symbol then ACCEPT_MIME_MAP[response_type]
|
80
|
+
else response_type
|
82
81
|
end
|
82
|
+
end
|
83
83
|
|
84
|
-
|
85
|
-
|
86
|
-
|
84
|
+
if params.any?
|
85
|
+
url += "?" + params.map {|key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"}.join("&")
|
86
|
+
end
|
87
87
|
|
88
|
-
|
88
|
+
uri = URI.parse(url)
|
89
89
|
|
90
|
-
|
90
|
+
attempts = 0
|
91
91
|
|
92
|
-
|
93
|
-
|
92
|
+
request_info = RequestInfo.new(url, headers, params, body)
|
93
|
+
before_request.call(request_info) if before_request
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
begin
|
96
|
+
attempts += 1
|
97
|
+
logger.info("XeroGateway Request: #{method.to_s.upcase} #{uri.request_uri}") if self.logger
|
98
98
|
|
99
|
-
|
99
|
+
raw_body = params.delete(:raw_body) ? body : {:xml => body}
|
100
100
|
|
101
|
-
|
101
|
+
response = with_around_request(request_info) do
|
102
|
+
case method
|
102
103
|
when :get then client.get(uri.request_uri, headers)
|
103
104
|
when :post then client.post(uri.request_uri, raw_body, headers)
|
104
105
|
when :put then client.put(uri.request_uri, raw_body, headers)
|
105
106
|
end
|
107
|
+
end
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
else
|
122
|
-
handle_unknown_response_error!(response)
|
123
|
-
end
|
124
|
-
rescue Xeroizer::OAuth::NonceUsed => exception
|
125
|
-
raise if attempts > nonce_used_max_attempts
|
126
|
-
logger.info("Nonce used: " + exception.to_s) if self.logger
|
127
|
-
sleep_for(1)
|
128
|
-
retry
|
129
|
-
rescue Xeroizer::OAuth::RateLimitExceeded
|
130
|
-
if self.rate_limit_sleep
|
131
|
-
raise if attempts > rate_limit_max_attempts
|
132
|
-
logger.info("Rate limit exceeded, retrying") if self.logger
|
133
|
-
sleep_for(self.rate_limit_sleep)
|
134
|
-
retry
|
109
|
+
log_response(response, uri)
|
110
|
+
after_request.call(request_info, response) if after_request
|
111
|
+
|
112
|
+
case response.code.to_i
|
113
|
+
when 200
|
114
|
+
response.plain_body
|
115
|
+
when 400
|
116
|
+
handle_error!(response, body)
|
117
|
+
when 401
|
118
|
+
handle_oauth_error!(response)
|
119
|
+
when 404
|
120
|
+
handle_object_not_found!(response, url)
|
121
|
+
when 503
|
122
|
+
handle_oauth_error!(response)
|
135
123
|
else
|
136
|
-
|
137
|
-
|
124
|
+
handle_unknown_response_error!(response)
|
125
|
+
end
|
126
|
+
rescue Xeroizer::OAuth::NonceUsed => exception
|
127
|
+
raise if attempts > nonce_used_max_attempts
|
128
|
+
logger.info("Nonce used: " + exception.to_s) if self.logger
|
129
|
+
sleep_for(1)
|
130
|
+
retry
|
131
|
+
rescue Xeroizer::OAuth::RateLimitExceeded
|
132
|
+
if self.rate_limit_sleep
|
133
|
+
raise if attempts > rate_limit_max_attempts
|
134
|
+
logger.info("Rate limit exceeded, retrying") if self.logger
|
135
|
+
sleep_for(self.rate_limit_sleep)
|
136
|
+
retry
|
137
|
+
else
|
138
|
+
raise
|
138
139
|
end
|
139
140
|
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def with_around_request(request, &block)
|
144
|
+
if around_request
|
145
|
+
around_request.call(request, &block)
|
146
|
+
else
|
147
|
+
block.call
|
148
|
+
end
|
149
|
+
end
|
140
150
|
|
141
151
|
def log_response(response, uri)
|
142
152
|
if self.logger
|
@@ -148,78 +158,76 @@ module Xeroizer
|
|
148
158
|
end
|
149
159
|
|
150
160
|
def handle_oauth_error!(response)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
else
|
169
|
-
raise OAuth::UnknownError.new("Xero API may be down or the way OAuth errors are provided by Xero may have changed.")
|
161
|
+
error_details = CGI.parse(response.plain_body)
|
162
|
+
description = error_details["oauth_problem_advice"].first
|
163
|
+
problem = error_details["oauth_problem"].first
|
164
|
+
|
165
|
+
# see http://oauth.pbworks.com/ProblemReporting
|
166
|
+
# In addition to token_expired and token_rejected, Xero also returns
|
167
|
+
# 'rate limit exceeded' when more than 60 requests have been made in
|
168
|
+
# a second.
|
169
|
+
if problem
|
170
|
+
case problem
|
171
|
+
when "token_expired" then raise OAuth::TokenExpired.new(description)
|
172
|
+
when "token_rejected" then raise OAuth::TokenInvalid.new(description)
|
173
|
+
when "rate limit exceeded" then raise OAuth::RateLimitExceeded.new(description)
|
174
|
+
when "consumer_key_unknown" then raise OAuth::ConsumerKeyUnknown.new(description)
|
175
|
+
when "nonce_used" then raise OAuth::NonceUsed.new(description)
|
176
|
+
when "organisation offline" then raise OAuth::OrganisationOffline.new(description)
|
177
|
+
else raise OAuth::UnknownError.new(problem + ':' + description)
|
170
178
|
end
|
179
|
+
else
|
180
|
+
raise OAuth::UnknownError.new("Xero API may be down or the way OAuth errors are provided by Xero may have changed.")
|
171
181
|
end
|
182
|
+
end
|
172
183
|
|
173
|
-
|
174
|
-
|
175
|
-
raw_response = response.plain_body
|
176
|
-
|
177
|
-
# XeroGenericApplication API Exceptions *claim* to be UTF-16 encoded, but fail REXML/Iconv parsing...
|
178
|
-
# So let's ignore that :)
|
179
|
-
raw_response.gsub! '<?xml version="1.0" encoding="utf-16"?>', ''
|
180
|
-
|
181
|
-
# doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
|
182
|
-
doc = Nokogiri::XML(raw_response)
|
184
|
+
def handle_error!(response, request_body)
|
183
185
|
|
184
|
-
|
186
|
+
raw_response = response.plain_body
|
185
187
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
doc,
|
190
|
-
request_body)
|
188
|
+
# XeroGenericApplication API Exceptions *claim* to be UTF-16 encoded, but fail REXML/Iconv parsing...
|
189
|
+
# So let's ignore that :)
|
190
|
+
raw_response.gsub! '<?xml version="1.0" encoding="utf-16"?>', ''
|
191
191
|
|
192
|
-
|
192
|
+
# doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
|
193
|
+
doc = Nokogiri::XML(raw_response)
|
193
194
|
|
194
|
-
|
195
|
+
if doc && doc.root && doc.root.name == "ApiException"
|
195
196
|
|
196
|
-
|
197
|
+
raise ApiException.new(doc.root.xpath("Type").text,
|
198
|
+
doc.root.xpath("Message").text,
|
199
|
+
raw_response,
|
200
|
+
doc,
|
201
|
+
request_body)
|
197
202
|
|
203
|
+
else
|
204
|
+
raise BadResponse.new("Unparseable 400 Response: #{raw_response}")
|
198
205
|
end
|
206
|
+
end
|
199
207
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
end
|
208
|
+
def handle_object_not_found!(response, request_url)
|
209
|
+
case request_url
|
210
|
+
when /Invoices/ then raise InvoiceNotFoundError.new("Invoice not found in Xero.")
|
211
|
+
when /CreditNotes/ then raise CreditNoteNotFoundError.new("Credit Note not found in Xero.")
|
212
|
+
else raise ObjectNotFound.new(request_url)
|
206
213
|
end
|
214
|
+
end
|
207
215
|
|
208
|
-
|
209
|
-
|
210
|
-
|
216
|
+
def handle_unknown_response_error!(response)
|
217
|
+
raise BadResponse.new("Unknown response code: #{response.code.to_i}")
|
218
|
+
end
|
211
219
|
|
212
|
-
|
213
|
-
|
214
|
-
|
220
|
+
def sleep_for(seconds = 1)
|
221
|
+
sleep seconds
|
222
|
+
end
|
215
223
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
224
|
+
# unitdp query string parameter to be added to request params
|
225
|
+
# when the application option has been set and the model has line items
|
226
|
+
# http://developer.xero.com/documentation/advanced-docs/rounding-in-xero/#unitamount
|
227
|
+
def unitdp_param(request_url)
|
228
|
+
models = [/Invoices/, /CreditNotes/, /BankTransactions/, /Receipts/]
|
229
|
+
self.unitdp == 4 && models.any?{ |m| request_url =~ m } ? {:unitdp => 4} : {}
|
230
|
+
end
|
223
231
|
|
224
232
|
end
|
225
233
|
end
|