xero_gateway-float 2.0.15

Sign up to get free protection for your applications and to get access to all the features.
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