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.
Files changed (71) hide show
  1. data/Gemfile +12 -0
  2. data/LICENSE +14 -0
  3. data/README.textile +357 -0
  4. data/Rakefile +14 -0
  5. data/examples/oauth.rb +25 -0
  6. data/examples/partner_app.rb +36 -0
  7. data/init.rb +1 -0
  8. data/lib/oauth/oauth_consumer.rb +14 -0
  9. data/lib/xero_gateway.rb +39 -0
  10. data/lib/xero_gateway/account.rb +95 -0
  11. data/lib/xero_gateway/accounts_list.rb +87 -0
  12. data/lib/xero_gateway/address.rb +96 -0
  13. data/lib/xero_gateway/bank_transaction.rb +178 -0
  14. data/lib/xero_gateway/ca-certificates.crt +2560 -0
  15. data/lib/xero_gateway/contact.rb +206 -0
  16. data/lib/xero_gateway/credit_note.rb +222 -0
  17. data/lib/xero_gateway/currency.rb +56 -0
  18. data/lib/xero_gateway/dates.rb +30 -0
  19. data/lib/xero_gateway/error.rb +18 -0
  20. data/lib/xero_gateway/exceptions.rb +46 -0
  21. data/lib/xero_gateway/gateway.rb +622 -0
  22. data/lib/xero_gateway/http.rb +138 -0
  23. data/lib/xero_gateway/http_encoding_helper.rb +49 -0
  24. data/lib/xero_gateway/invoice.rb +236 -0
  25. data/lib/xero_gateway/line_item.rb +125 -0
  26. data/lib/xero_gateway/line_item_calculations.rb +55 -0
  27. data/lib/xero_gateway/money.rb +16 -0
  28. data/lib/xero_gateway/oauth.rb +87 -0
  29. data/lib/xero_gateway/organisation.rb +75 -0
  30. data/lib/xero_gateway/partner_app.rb +30 -0
  31. data/lib/xero_gateway/payment.rb +40 -0
  32. data/lib/xero_gateway/phone.rb +77 -0
  33. data/lib/xero_gateway/private_app.rb +17 -0
  34. data/lib/xero_gateway/response.rb +41 -0
  35. data/lib/xero_gateway/tax_rate.rb +63 -0
  36. data/lib/xero_gateway/tracking_category.rb +87 -0
  37. data/test/integration/accounts_list_test.rb +109 -0
  38. data/test/integration/create_bank_transaction_test.rb +38 -0
  39. data/test/integration/create_contact_test.rb +66 -0
  40. data/test/integration/create_credit_note_test.rb +49 -0
  41. data/test/integration/create_invoice_test.rb +49 -0
  42. data/test/integration/get_accounts_test.rb +23 -0
  43. data/test/integration/get_bank_transaction_test.rb +51 -0
  44. data/test/integration/get_bank_transactions_test.rb +88 -0
  45. data/test/integration/get_contact_test.rb +28 -0
  46. data/test/integration/get_contacts_test.rb +40 -0
  47. data/test/integration/get_credit_note_test.rb +48 -0
  48. data/test/integration/get_credit_notes_test.rb +90 -0
  49. data/test/integration/get_currencies_test.rb +25 -0
  50. data/test/integration/get_invoice_test.rb +48 -0
  51. data/test/integration/get_invoices_test.rb +92 -0
  52. data/test/integration/get_organisation_test.rb +24 -0
  53. data/test/integration/get_tax_rates_test.rb +25 -0
  54. data/test/integration/get_tracking_categories_test.rb +27 -0
  55. data/test/integration/update_bank_transaction_test.rb +31 -0
  56. data/test/integration/update_contact_test.rb +31 -0
  57. data/test/integration/update_invoice_test.rb +31 -0
  58. data/test/test_helper.rb +179 -0
  59. data/test/unit/account_test.rb +47 -0
  60. data/test/unit/bank_transaction_test.rb +126 -0
  61. data/test/unit/contact_test.rb +97 -0
  62. data/test/unit/credit_note_test.rb +284 -0
  63. data/test/unit/currency_test.rb +31 -0
  64. data/test/unit/gateway_test.rb +119 -0
  65. data/test/unit/invoice_test.rb +326 -0
  66. data/test/unit/oauth_test.rb +116 -0
  67. data/test/unit/organisation_test.rb +38 -0
  68. data/test/unit/tax_rate_test.rb +38 -0
  69. data/test/unit/tracking_category_test.rb +52 -0
  70. data/xero_gateway.gemspec +15 -0
  71. 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