xero_gateway-float 2.0.15
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.
- data/Gemfile +12 -0
- data/LICENSE +14 -0
- data/README.textile +357 -0
- data/Rakefile +14 -0
- data/examples/oauth.rb +25 -0
- data/examples/partner_app.rb +36 -0
- data/init.rb +1 -0
- data/lib/oauth/oauth_consumer.rb +14 -0
- data/lib/xero_gateway.rb +39 -0
- data/lib/xero_gateway/account.rb +95 -0
- data/lib/xero_gateway/accounts_list.rb +87 -0
- data/lib/xero_gateway/address.rb +96 -0
- data/lib/xero_gateway/bank_transaction.rb +178 -0
- data/lib/xero_gateway/ca-certificates.crt +2560 -0
- data/lib/xero_gateway/contact.rb +206 -0
- data/lib/xero_gateway/credit_note.rb +222 -0
- data/lib/xero_gateway/currency.rb +56 -0
- data/lib/xero_gateway/dates.rb +30 -0
- data/lib/xero_gateway/error.rb +18 -0
- data/lib/xero_gateway/exceptions.rb +46 -0
- data/lib/xero_gateway/gateway.rb +622 -0
- data/lib/xero_gateway/http.rb +138 -0
- data/lib/xero_gateway/http_encoding_helper.rb +49 -0
- data/lib/xero_gateway/invoice.rb +236 -0
- data/lib/xero_gateway/line_item.rb +125 -0
- data/lib/xero_gateway/line_item_calculations.rb +55 -0
- data/lib/xero_gateway/money.rb +16 -0
- data/lib/xero_gateway/oauth.rb +87 -0
- data/lib/xero_gateway/organisation.rb +75 -0
- data/lib/xero_gateway/partner_app.rb +30 -0
- data/lib/xero_gateway/payment.rb +40 -0
- data/lib/xero_gateway/phone.rb +77 -0
- data/lib/xero_gateway/private_app.rb +17 -0
- data/lib/xero_gateway/response.rb +41 -0
- data/lib/xero_gateway/tax_rate.rb +63 -0
- data/lib/xero_gateway/tracking_category.rb +87 -0
- data/test/integration/accounts_list_test.rb +109 -0
- data/test/integration/create_bank_transaction_test.rb +38 -0
- data/test/integration/create_contact_test.rb +66 -0
- data/test/integration/create_credit_note_test.rb +49 -0
- data/test/integration/create_invoice_test.rb +49 -0
- data/test/integration/get_accounts_test.rb +23 -0
- data/test/integration/get_bank_transaction_test.rb +51 -0
- data/test/integration/get_bank_transactions_test.rb +88 -0
- data/test/integration/get_contact_test.rb +28 -0
- data/test/integration/get_contacts_test.rb +40 -0
- data/test/integration/get_credit_note_test.rb +48 -0
- data/test/integration/get_credit_notes_test.rb +90 -0
- data/test/integration/get_currencies_test.rb +25 -0
- data/test/integration/get_invoice_test.rb +48 -0
- data/test/integration/get_invoices_test.rb +92 -0
- data/test/integration/get_organisation_test.rb +24 -0
- data/test/integration/get_tax_rates_test.rb +25 -0
- data/test/integration/get_tracking_categories_test.rb +27 -0
- data/test/integration/update_bank_transaction_test.rb +31 -0
- data/test/integration/update_contact_test.rb +31 -0
- data/test/integration/update_invoice_test.rb +31 -0
- data/test/test_helper.rb +179 -0
- data/test/unit/account_test.rb +47 -0
- data/test/unit/bank_transaction_test.rb +126 -0
- data/test/unit/contact_test.rb +97 -0
- data/test/unit/credit_note_test.rb +284 -0
- data/test/unit/currency_test.rb +31 -0
- data/test/unit/gateway_test.rb +119 -0
- data/test/unit/invoice_test.rb +326 -0
- data/test/unit/oauth_test.rb +116 -0
- data/test/unit/organisation_test.rb +38 -0
- data/test/unit/tax_rate_test.rb +38 -0
- data/test/unit/tracking_category_test.rb +52 -0
- data/xero_gateway.gemspec +15 -0
- metadata +164 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
module Dates
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def format_date(time)
|
9
|
+
return time.strftime("%Y-%m-%d")
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_date_time(time)
|
13
|
+
return time.strftime("%Y%m%d%H%M%S")
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_date(time)
|
17
|
+
Date.civil(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_date_time(time)
|
21
|
+
Time.local(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i, time[11..12].to_i, time[14..15].to_i, time[17..18].to_i)
|
22
|
+
end
|
23
|
+
|
24
|
+
#eg: 2011-09-14T10:15:03.893
|
25
|
+
def parse_utc_date_time(time)
|
26
|
+
Time.utc(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i, time[11..12].to_i, time[14..15].to_i, time[17..18].to_i)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class Error
|
3
|
+
attr_accessor :description, :date_time, :type, :message
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
params.each do |k,v|
|
7
|
+
self.send("#{k}=", v)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
[:description, :date_time, :type, :message].each do |field|
|
13
|
+
return false if send(field) != other.send(field)
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class ApiException < StandardError
|
3
|
+
|
4
|
+
def initialize(type, message, request_xml, response_xml)
|
5
|
+
@type = type
|
6
|
+
@message = message
|
7
|
+
@request_xml = request_xml
|
8
|
+
@response_xml = response_xml
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :request_xml, :response_xml
|
12
|
+
|
13
|
+
def message
|
14
|
+
"#{@type}: #{@message} \n Generated by the following XML: \n #{@response_xml}"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class UnparseableResponse < StandardError
|
20
|
+
|
21
|
+
def initialize(root_element_name)
|
22
|
+
@root_element_name = root_element_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"A root element of #{@root_element_name} was returned, and we don't understand that!"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class ObjectNotFound < StandardError
|
32
|
+
|
33
|
+
def initialize(api_endpoint)
|
34
|
+
@api_endpoint = api_endpoint
|
35
|
+
end
|
36
|
+
|
37
|
+
def message
|
38
|
+
"Couldn't find object for API Endpoint #{@api_endpoint}"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class InvoiceNotFoundError < StandardError; end
|
44
|
+
class BankTransactionNotFoundError < StandardError; end
|
45
|
+
class CreditNoteNotFoundError < StandardError; end
|
46
|
+
end
|
@@ -0,0 +1,622 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
|
3
|
+
class Gateway
|
4
|
+
include Http
|
5
|
+
include Dates
|
6
|
+
|
7
|
+
attr_accessor :client, :xero_url, :logger
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :authorization_expires_at
|
11
|
+
|
12
|
+
#
|
13
|
+
# The consumer key and secret here correspond to those provided
|
14
|
+
# to you by Xero inside the API Previewer.
|
15
|
+
def initialize(consumer_key, consumer_secret, options = {})
|
16
|
+
@xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
|
17
|
+
@client = OAuth.new(consumer_key, consumer_secret, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Retrieve all contacts from Xero
|
22
|
+
#
|
23
|
+
# Usage : get_contacts(:order => :name)
|
24
|
+
# get_contacts(:modified_since => Time)
|
25
|
+
#
|
26
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
27
|
+
def get_contacts(options = {})
|
28
|
+
request_params = {}
|
29
|
+
|
30
|
+
if !options[:updated_after].nil?
|
31
|
+
warn '[warning] :updated_after is depracated in XeroGateway#get_contacts. Use :modified_since'
|
32
|
+
options[:modified_since] = options.delete(:updated_after)
|
33
|
+
end
|
34
|
+
|
35
|
+
request_params[:ContactID] = options[:contact_id] if options[:contact_id]
|
36
|
+
request_params[:ContactNumber] = options[:contact_number] if options[:contact_number]
|
37
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
38
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
39
|
+
request_params[:where] = options[:where] if options[:where]
|
40
|
+
|
41
|
+
response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params)
|
42
|
+
|
43
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contacts'})
|
44
|
+
end
|
45
|
+
|
46
|
+
# Retrieve a contact from Xero
|
47
|
+
# Usage get_contact_by_id(contact_id)
|
48
|
+
def get_contact_by_id(contact_id)
|
49
|
+
get_contact(contact_id)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Retrieve a contact from Xero
|
53
|
+
# Usage get_contact_by_id(contact_id)
|
54
|
+
def get_contact_by_number(contact_number)
|
55
|
+
get_contact(nil, contact_number)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Factory method for building new Contact objects associated with this gateway.
|
59
|
+
def build_contact(contact = {})
|
60
|
+
case contact
|
61
|
+
when Contact then contact.gateway = self
|
62
|
+
when Hash then contact = Contact.new(contact.merge({:gateway => self}))
|
63
|
+
end
|
64
|
+
contact
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Creates a contact in Xero
|
69
|
+
#
|
70
|
+
# Usage :
|
71
|
+
#
|
72
|
+
# contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT #{Time.now.to_i}")
|
73
|
+
# contact.email = "whoever@something.com"
|
74
|
+
# contact.phone.number = "12345"
|
75
|
+
# contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
76
|
+
# contact.address.line_2 = "LINE 2 OF THE ADDRESS"
|
77
|
+
# contact.address.line_3 = "LINE 3 OF THE ADDRESS"
|
78
|
+
# contact.address.line_4 = "LINE 4 OF THE ADDRESS"
|
79
|
+
# contact.address.city = "WELLINGTON"
|
80
|
+
# contact.address.region = "WELLINGTON"
|
81
|
+
# contact.address.country = "NEW ZEALAND"
|
82
|
+
# contact.address.post_code = "6021"
|
83
|
+
#
|
84
|
+
# create_contact(contact)
|
85
|
+
def create_contact(contact)
|
86
|
+
save_contact(contact)
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Updates an existing Xero contact
|
91
|
+
#
|
92
|
+
# Usage :
|
93
|
+
#
|
94
|
+
# contact = xero_gateway.get_contact(some_contact_id)
|
95
|
+
# contact.email = "a_new_email_ddress"
|
96
|
+
#
|
97
|
+
# xero_gateway.update_contact(contact)
|
98
|
+
def update_contact(contact)
|
99
|
+
raise "contact_id or contact_number is required for updating contacts" if contact.contact_id.nil? and contact.contact_number.nil?
|
100
|
+
save_contact(contact)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Updates an array of contacts in a single API operation.
|
105
|
+
#
|
106
|
+
# Usage :
|
107
|
+
# contacts = [XeroGateway::Contact.new(:name => 'Joe Bloggs'), XeroGateway::Contact.new(:name => 'Jane Doe')]
|
108
|
+
# result = gateway.update_contacts(contacts)
|
109
|
+
#
|
110
|
+
# Will update contacts with matching contact_id, contact_number or name or create if they don't exist.
|
111
|
+
#
|
112
|
+
def update_contacts(contacts)
|
113
|
+
b = Builder::XmlMarkup.new
|
114
|
+
request_xml = b.Contacts {
|
115
|
+
contacts.each do | contact |
|
116
|
+
contact.to_xml(b)
|
117
|
+
end
|
118
|
+
}
|
119
|
+
|
120
|
+
response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
|
121
|
+
|
122
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'POST/contacts'})
|
123
|
+
response.contacts.each_with_index do | response_contact, index |
|
124
|
+
contacts[index].contact_id = response_contact.contact_id if response_contact && response_contact.contact_id
|
125
|
+
end
|
126
|
+
response
|
127
|
+
end
|
128
|
+
|
129
|
+
# Retrieves all invoices from Xero
|
130
|
+
#
|
131
|
+
# Usage : get_invoices
|
132
|
+
# get_invoices(:invoice_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
133
|
+
#
|
134
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
135
|
+
def get_invoices(options = {})
|
136
|
+
|
137
|
+
request_params = {}
|
138
|
+
|
139
|
+
request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id]
|
140
|
+
request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
|
141
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
142
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
143
|
+
|
144
|
+
request_params[:where] = options[:where] if options[:where]
|
145
|
+
|
146
|
+
response_xml = http_get(@client, "#{@xero_url}/Invoices", request_params)
|
147
|
+
|
148
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoices'})
|
149
|
+
end
|
150
|
+
|
151
|
+
# Retrieves a single invoice
|
152
|
+
#
|
153
|
+
# Usage : get_invoice("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
154
|
+
# get_invoice("OIT-12345") # By number
|
155
|
+
def get_invoice(invoice_id_or_number)
|
156
|
+
request_params = {}
|
157
|
+
|
158
|
+
url = "#{@xero_url}/Invoices/#{URI.escape(invoice_id_or_number)}"
|
159
|
+
|
160
|
+
response_xml = http_get(@client, url, request_params)
|
161
|
+
|
162
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoice'})
|
163
|
+
end
|
164
|
+
|
165
|
+
# Factory method for building new Invoice objects associated with this gateway.
|
166
|
+
def build_invoice(invoice = {})
|
167
|
+
case invoice
|
168
|
+
when Invoice then invoice.gateway = self
|
169
|
+
when Hash then invoice = Invoice.new(invoice.merge(:gateway => self))
|
170
|
+
end
|
171
|
+
invoice
|
172
|
+
end
|
173
|
+
|
174
|
+
# Creates an invoice in Xero based on an invoice object.
|
175
|
+
#
|
176
|
+
# Invoice and line item totals are calculated automatically.
|
177
|
+
#
|
178
|
+
# Usage :
|
179
|
+
#
|
180
|
+
# invoice = XeroGateway::Invoice.new({
|
181
|
+
# :invoice_type => "ACCREC",
|
182
|
+
# :due_date => 1.month.from_now,
|
183
|
+
# :invoice_number => "YOUR INVOICE NUMBER",
|
184
|
+
# :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
185
|
+
# :line_amount_types => "Inclusive"
|
186
|
+
# })
|
187
|
+
# invoice.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
188
|
+
# invoice.contact.phone.number = "12345"
|
189
|
+
# invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
190
|
+
# invoice.line_items << XeroGateway::LineItem.new(
|
191
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
192
|
+
# :unit_amount => 100,
|
193
|
+
# :tax_amount => 12.5,
|
194
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
195
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
196
|
+
# )
|
197
|
+
#
|
198
|
+
# create_invoice(invoice)
|
199
|
+
def create_invoice(invoice)
|
200
|
+
save_invoice(invoice)
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Updates an existing Xero invoice
|
205
|
+
#
|
206
|
+
# Usage :
|
207
|
+
#
|
208
|
+
# invoice = xero_gateway.get_invoice(some_invoice_id)
|
209
|
+
# invoice.due_date = Date.today
|
210
|
+
#
|
211
|
+
# xero_gateway.update_invoice(invoice)
|
212
|
+
|
213
|
+
def update_invoice(invoice)
|
214
|
+
raise "invoice_id is required for updating invoices" if invoice.invoice_id.nil?
|
215
|
+
save_invoice(invoice)
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Creates an array of invoices with a single API request.
|
220
|
+
#
|
221
|
+
# Usage :
|
222
|
+
# invoices = [XeroGateway::Invoice.new(...), XeroGateway::Invoice.new(...)]
|
223
|
+
# result = gateway.create_invoices(invoices)
|
224
|
+
#
|
225
|
+
def create_invoices(invoices)
|
226
|
+
b = Builder::XmlMarkup.new
|
227
|
+
request_xml = b.Invoices {
|
228
|
+
invoices.each do | invoice |
|
229
|
+
invoice.to_xml(b)
|
230
|
+
end
|
231
|
+
}
|
232
|
+
|
233
|
+
response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
|
234
|
+
|
235
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/invoices'})
|
236
|
+
response.invoices.each_with_index do | response_invoice, index |
|
237
|
+
invoices[index].invoice_id = response_invoice.invoice_id if response_invoice && response_invoice.invoice_id
|
238
|
+
end
|
239
|
+
response
|
240
|
+
end
|
241
|
+
|
242
|
+
# Retrieves all credit_notes from Xero
|
243
|
+
#
|
244
|
+
# Usage : get_credit_notes
|
245
|
+
# get_credit_notes(:credit_note_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
246
|
+
#
|
247
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
248
|
+
def get_credit_notes(options = {})
|
249
|
+
|
250
|
+
request_params = {}
|
251
|
+
|
252
|
+
request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id]
|
253
|
+
request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number]
|
254
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
255
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
256
|
+
|
257
|
+
request_params[:where] = options[:where] if options[:where]
|
258
|
+
|
259
|
+
response_xml = http_get(@client, "#{@xero_url}/CreditNotes", request_params)
|
260
|
+
|
261
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNotes'})
|
262
|
+
end
|
263
|
+
|
264
|
+
# Retrieves a single credit_note
|
265
|
+
#
|
266
|
+
# Usage : get_credit_note("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
267
|
+
# get_credit_note("OIT-12345") # By number
|
268
|
+
def get_credit_note(credit_note_id_or_number)
|
269
|
+
request_params = {}
|
270
|
+
|
271
|
+
url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}"
|
272
|
+
|
273
|
+
response_xml = http_get(@client, url, request_params)
|
274
|
+
|
275
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNote'})
|
276
|
+
end
|
277
|
+
|
278
|
+
# Factory method for building new CreditNote objects associated with this gateway.
|
279
|
+
def build_credit_note(credit_note = {})
|
280
|
+
case credit_note
|
281
|
+
when CreditNote then credit_note.gateway = self
|
282
|
+
when Hash then credit_note = CreditNote.new(credit_note.merge(:gateway => self))
|
283
|
+
end
|
284
|
+
credit_note
|
285
|
+
end
|
286
|
+
|
287
|
+
# Creates an credit_note in Xero based on an credit_note object.
|
288
|
+
#
|
289
|
+
# CreditNote and line item totals are calculated automatically.
|
290
|
+
#
|
291
|
+
# Usage :
|
292
|
+
#
|
293
|
+
# credit_note = XeroGateway::CreditNote.new({
|
294
|
+
# :credit_note_type => "ACCREC",
|
295
|
+
# :due_date => 1.month.from_now,
|
296
|
+
# :credit_note_number => "YOUR CREDIT_NOTE NUMBER",
|
297
|
+
# :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
298
|
+
# :line_amount_types => "Inclusive"
|
299
|
+
# })
|
300
|
+
# credit_note.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
301
|
+
# credit_note.contact.phone.number = "12345"
|
302
|
+
# credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
303
|
+
# credit_note.line_items << XeroGateway::LineItem.new(
|
304
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
305
|
+
# :unit_amount => 100,
|
306
|
+
# :tax_amount => 12.5,
|
307
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
308
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
309
|
+
# )
|
310
|
+
#
|
311
|
+
# create_credit_note(credit_note)
|
312
|
+
def create_credit_note(credit_note)
|
313
|
+
request_xml = credit_note.to_xml
|
314
|
+
response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml)
|
315
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_note'})
|
316
|
+
|
317
|
+
# Xero returns credit_notes inside an <CreditNotes> tag, even though there's only ever
|
318
|
+
# one for this request
|
319
|
+
response.response_item = response.credit_notes.first
|
320
|
+
|
321
|
+
if response.success? && response.credit_note && response.credit_note.credit_note_id
|
322
|
+
credit_note.credit_note_id = response.credit_note.credit_note_id
|
323
|
+
end
|
324
|
+
|
325
|
+
response
|
326
|
+
end
|
327
|
+
|
328
|
+
#
|
329
|
+
# Creates an array of credit_notes with a single API request.
|
330
|
+
#
|
331
|
+
# Usage :
|
332
|
+
# credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)]
|
333
|
+
# result = gateway.create_credit_notes(credit_notes)
|
334
|
+
#
|
335
|
+
def create_credit_notes(credit_notes)
|
336
|
+
b = Builder::XmlMarkup.new
|
337
|
+
request_xml = b.CreditNotes {
|
338
|
+
credit_notes.each do | credit_note |
|
339
|
+
credit_note.to_xml(b)
|
340
|
+
end
|
341
|
+
}
|
342
|
+
|
343
|
+
response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml, {})
|
344
|
+
|
345
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_notes'})
|
346
|
+
response.credit_notes.each_with_index do | response_credit_note, index |
|
347
|
+
credit_notes[index].credit_note_id = response_credit_note.credit_note_id if response_credit_note && response_credit_note.credit_note_id
|
348
|
+
end
|
349
|
+
response
|
350
|
+
end
|
351
|
+
|
352
|
+
# Creates a bank transaction in Xero based on a bank transaction object.
|
353
|
+
#
|
354
|
+
# Bank transaction and line item totals are calculated automatically.
|
355
|
+
#
|
356
|
+
# Usage :
|
357
|
+
#
|
358
|
+
# bank_transaction = XeroGateway::BankTransaction.new({
|
359
|
+
# :type => "RECEIVE",
|
360
|
+
# :date => 1.month.from_now,
|
361
|
+
# :reference => "YOUR INVOICE NUMBER",
|
362
|
+
# })
|
363
|
+
# bank_transaction.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
364
|
+
# bank_transaction.contact.phone.number = "12345"
|
365
|
+
# bank_transaction.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
366
|
+
# bank_transaction.line_items << XeroGateway::LineItem.new(
|
367
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
368
|
+
# :unit_amount => 100,
|
369
|
+
# :tax_amount => 12.5,
|
370
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
371
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
372
|
+
# )
|
373
|
+
# bank_transaction.bank_account = XeroGateway::Account.new(:code => 'BANK-ABC)
|
374
|
+
#
|
375
|
+
# create_bank_transaction(bank_transaction)
|
376
|
+
def create_bank_transaction(bank_transaction)
|
377
|
+
save_bank_transaction(bank_transaction)
|
378
|
+
end
|
379
|
+
|
380
|
+
#
|
381
|
+
# Updates an existing Xero bank transaction
|
382
|
+
#
|
383
|
+
# Usage :
|
384
|
+
#
|
385
|
+
# bank_transaction = xero_gateway.get_bank_transaction(some_bank_transaction_id)
|
386
|
+
# bank_transaction.due_date = Date.today
|
387
|
+
#
|
388
|
+
# xero_gateway.update_bank_transaction(bank_transaction)
|
389
|
+
def update_bank_transaction(bank_transaction)
|
390
|
+
raise "bank_transaction_id is required for updating bank transactions" if bank_transaction.bank_transaction_id.nil?
|
391
|
+
save_bank_transaction(bank_transaction)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Retrieves all bank transactions from Xero
|
395
|
+
#
|
396
|
+
# Usage : get_bank_transactions
|
397
|
+
# get_bank_transactions(:bank_transaction_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
398
|
+
#
|
399
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
400
|
+
def get_bank_transactions(options = {})
|
401
|
+
request_params = {}
|
402
|
+
request_params[:BankTransactionID] = options[:bank_transaction_id] if options[:bank_transaction_id]
|
403
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
404
|
+
request_params[:where] = options[:where] if options[:where]
|
405
|
+
|
406
|
+
response_xml = http_get(@client, "#{@xero_url}/BankTransactions", request_params)
|
407
|
+
|
408
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransactions'})
|
409
|
+
end
|
410
|
+
|
411
|
+
# Retrieves a single bank transaction
|
412
|
+
#
|
413
|
+
# Usage : get_bank_transaction("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
414
|
+
# get_bank_transaction("OIT-12345") # By number
|
415
|
+
def get_bank_transaction(bank_transaction_id)
|
416
|
+
request_params = {}
|
417
|
+
url = "#{@xero_url}/BankTransactions/#{URI.escape(bank_transaction_id)}"
|
418
|
+
response_xml = http_get(@client, url, request_params)
|
419
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransaction'})
|
420
|
+
end
|
421
|
+
|
422
|
+
#
|
423
|
+
# Gets all accounts for a specific organization in Xero.
|
424
|
+
#
|
425
|
+
def get_accounts
|
426
|
+
response_xml = http_get(@client, "#{xero_url}/Accounts")
|
427
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/accounts'})
|
428
|
+
end
|
429
|
+
|
430
|
+
#
|
431
|
+
# Returns a XeroGateway::AccountsList object that makes working with
|
432
|
+
# the Xero list of accounts easier and allows caching the results.
|
433
|
+
#
|
434
|
+
def get_accounts_list(load_on_init = true)
|
435
|
+
AccountsList.new(self, load_on_init)
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Gets all tracking categories for a specific organization in Xero.
|
440
|
+
#
|
441
|
+
def get_tracking_categories
|
442
|
+
response_xml = http_get(@client, "#{xero_url}/TrackingCategories")
|
443
|
+
|
444
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/TrackingCategories'})
|
445
|
+
end
|
446
|
+
|
447
|
+
#
|
448
|
+
# Gets Organisation details
|
449
|
+
#
|
450
|
+
def get_organisation
|
451
|
+
response_xml = http_get(@client, "#{xero_url}/Organisation")
|
452
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/organisation'})
|
453
|
+
end
|
454
|
+
|
455
|
+
#
|
456
|
+
# Gets all currencies for a specific organisation in Xero
|
457
|
+
#
|
458
|
+
def get_currencies
|
459
|
+
response_xml = http_get(@client, "#{xero_url}/Currencies")
|
460
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/currencies'})
|
461
|
+
end
|
462
|
+
|
463
|
+
#
|
464
|
+
# Gets all Tax Rates for a specific organisation in Xero
|
465
|
+
#
|
466
|
+
def get_tax_rates
|
467
|
+
response_xml = http_get(@client, "#{xero_url}/TaxRates")
|
468
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/tax_rates'})
|
469
|
+
end
|
470
|
+
|
471
|
+
def get_report(report_name, request_params = {})
|
472
|
+
response_xml = http_get(@client, "#{@xero_url}/Reports/#{report_name}", request_params)
|
473
|
+
end
|
474
|
+
|
475
|
+
|
476
|
+
private
|
477
|
+
|
478
|
+
def get_contact(contact_id = nil, contact_number = nil)
|
479
|
+
request_params = contact_id ? { :contactID => contact_id } : { :contactNumber => contact_number }
|
480
|
+
response_xml = http_get(@client, "#{@xero_url}/Contacts/#{URI.escape(contact_id||contact_number)}", request_params)
|
481
|
+
|
482
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contact'})
|
483
|
+
end
|
484
|
+
|
485
|
+
|
486
|
+
# Create or update a contact record based on if it has a contact_id or contact_number.
|
487
|
+
def save_contact(contact)
|
488
|
+
request_xml = contact.to_xml
|
489
|
+
|
490
|
+
response_xml = nil
|
491
|
+
create_or_save = nil
|
492
|
+
if contact.contact_id.nil? && contact.contact_number.nil?
|
493
|
+
# Create new contact record.
|
494
|
+
response_xml = http_put(@client, "#{@xero_url}/Contacts", request_xml, {})
|
495
|
+
create_or_save = :create
|
496
|
+
else
|
497
|
+
# Update existing contact record.
|
498
|
+
response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
|
499
|
+
create_or_save = :save
|
500
|
+
end
|
501
|
+
|
502
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/contact"})
|
503
|
+
contact.contact_id = response.contact.contact_id if response.contact && response.contact.contact_id
|
504
|
+
response
|
505
|
+
end
|
506
|
+
|
507
|
+
# Create or update an invoice record based on if it has an invoice_id.
|
508
|
+
def save_invoice(invoice)
|
509
|
+
request_xml = invoice.to_xml
|
510
|
+
|
511
|
+
response_xml = nil
|
512
|
+
create_or_save = nil
|
513
|
+
if invoice.invoice_id.nil?
|
514
|
+
# Create new invoice record.
|
515
|
+
response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
|
516
|
+
create_or_save = :create
|
517
|
+
else
|
518
|
+
# Update existing invoice record.
|
519
|
+
response_xml = http_post(@client, "#{@xero_url}/Invoices", request_xml, {})
|
520
|
+
create_or_save = :save
|
521
|
+
end
|
522
|
+
|
523
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/invoice"})
|
524
|
+
|
525
|
+
# Xero returns invoices inside an <Invoices> tag, even though there's only ever
|
526
|
+
# one for this request
|
527
|
+
response.response_item = response.invoices.first
|
528
|
+
|
529
|
+
if response.success? && response.invoice && response.invoice.invoice_id
|
530
|
+
invoice.invoice_id = response.invoice.invoice_id
|
531
|
+
end
|
532
|
+
|
533
|
+
response
|
534
|
+
end
|
535
|
+
|
536
|
+
# Create or update a bank transaction record based on if it has an bank_transaction_id.
|
537
|
+
def save_bank_transaction(bank_transaction)
|
538
|
+
request_xml = bank_transaction.to_xml
|
539
|
+
response_xml = nil
|
540
|
+
create_or_save = nil
|
541
|
+
|
542
|
+
if bank_transaction.bank_transaction_id.nil?
|
543
|
+
# Create new bank transaction record.
|
544
|
+
response_xml = http_put(@client, "#{@xero_url}/BankTransactions", request_xml, {})
|
545
|
+
create_or_save = :create
|
546
|
+
else
|
547
|
+
# Update existing bank transaction record.
|
548
|
+
response_xml = http_post(@client, "#{@xero_url}/BankTransactions", request_xml, {})
|
549
|
+
create_or_save = :save
|
550
|
+
end
|
551
|
+
|
552
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/BankTransactions"})
|
553
|
+
|
554
|
+
# Xero returns bank transactions inside an <BankTransactions> tag, even though there's only ever
|
555
|
+
# one for this request
|
556
|
+
response.response_item = response.bank_transactions.first
|
557
|
+
|
558
|
+
if response.success? && response.bank_transaction && response.bank_transaction.bank_transaction_id
|
559
|
+
bank_transaction.bank_transaction_id = response.bank_transaction.bank_transaction_id
|
560
|
+
end
|
561
|
+
|
562
|
+
response
|
563
|
+
end
|
564
|
+
|
565
|
+
def parse_response(raw_response, request = {}, options = {})
|
566
|
+
|
567
|
+
response = XeroGateway::Response.new
|
568
|
+
|
569
|
+
doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
|
570
|
+
|
571
|
+
# check for responses we don't understand
|
572
|
+
raise UnparseableResponse.new(doc.root.name) unless doc.root.name == "Response"
|
573
|
+
|
574
|
+
response_element = REXML::XPath.first(doc, "/Response")
|
575
|
+
|
576
|
+
response_element.children.reject { |e| e.is_a? REXML::Text }.each do |element|
|
577
|
+
case(element.name)
|
578
|
+
when "ID" then response.response_id = element.text
|
579
|
+
when "Status" then response.status = element.text
|
580
|
+
when "ProviderName" then response.provider = element.text
|
581
|
+
when "DateTimeUTC" then response.date_time = element.text
|
582
|
+
when "Contact" then response.response_item = Contact.from_xml(element, self)
|
583
|
+
when "Invoice" then response.response_item = Invoice.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"})
|
584
|
+
when "BankTransaction"
|
585
|
+
response.response_item = BankTransaction.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
|
586
|
+
when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) }
|
587
|
+
when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) }
|
588
|
+
when "BankTransactions"
|
589
|
+
element.children.each do |child|
|
590
|
+
response.response_item << BankTransaction.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
|
591
|
+
end
|
592
|
+
when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) }
|
593
|
+
when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) }
|
594
|
+
when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) }
|
595
|
+
when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) }
|
596
|
+
when "Organisations" then response.response_item = Organisation.from_xml(element.children.first) # Xero only returns the Authorized Organisation
|
597
|
+
when "TrackingCategories" then element.children.each {|child| response.response_item << TrackingCategory.from_xml(child) }
|
598
|
+
when "Errors" then element.children.each { |error| parse_error(error, response) }
|
599
|
+
end
|
600
|
+
end if response_element
|
601
|
+
|
602
|
+
# If a single result is returned don't put it in an array
|
603
|
+
if response.response_item.is_a?(Array) && response.response_item.size == 1
|
604
|
+
response.response_item = response.response_item.first
|
605
|
+
end
|
606
|
+
|
607
|
+
response.request_params = request[:request_params]
|
608
|
+
response.request_xml = request[:request_xml]
|
609
|
+
response.response_xml = raw_response
|
610
|
+
response
|
611
|
+
end
|
612
|
+
|
613
|
+
def parse_error(error_element, response)
|
614
|
+
response.errors << Error.new(
|
615
|
+
:description => REXML::XPath.first(error_element, "Description").text,
|
616
|
+
:date_time => REXML::XPath.first(error_element, "//DateTime").text,
|
617
|
+
:type => REXML::XPath.first(error_element, "//ExceptionType").text,
|
618
|
+
:message => REXML::XPath.first(error_element, "//Message").text
|
619
|
+
)
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|