xero_gateway 2.0.19 → 2.1.0

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.
@@ -140,7 +140,7 @@ module XeroGateway
140
140
  b.Contact {
141
141
  b.ContactID self.contact_id if self.contact_id
142
142
  b.ContactNumber self.contact_number if self.contact_number
143
- b.Name self.name
143
+ b.Name self.name if self.name
144
144
  b.EmailAddress self.email if self.email
145
145
  b.FirstName self.first_name if self.first_name
146
146
  b.LastName self.last_name if self.last_name
@@ -154,10 +154,10 @@ module XeroGateway
154
154
  b.DefaultCurrency if self.default_currency
155
155
  b.Addresses {
156
156
  addresses.each { |address| address.to_xml(b) }
157
- }
157
+ } if self.addresses.any?
158
158
  b.Phones {
159
159
  phones.each { |phone| phone.to_xml(b) }
160
- }
160
+ } if self.phones.any?
161
161
  }
162
162
  end
163
163
 
@@ -14,5 +14,21 @@ module XeroGateway
14
14
  end
15
15
  return true
16
16
  end
17
+
18
+ # pass a REXML::Element error object to
19
+ # have returned a new Error object
20
+ def self.parse(error_element)
21
+ description = REXML::XPath.first(error_element, "Description")
22
+ date = REXML::XPath.first(error_element, "//DateTime")
23
+ type = REXML::XPath.first(error_element, "//ExceptionType")
24
+ message = REXML::XPath.first(error_element, "//Message")
25
+ Error.new(
26
+ :description => (description.text if description),
27
+ :date_time => (date.text if date),
28
+ :type => (type.text if type),
29
+ :message => (message.text if message)
30
+ )
31
+ end
32
+
17
33
  end
18
34
  end
@@ -1,22 +1,22 @@
1
1
  module XeroGateway
2
-
2
+
3
3
  class Gateway
4
4
  include Http
5
5
  include Dates
6
-
6
+
7
7
  attr_accessor :client, :xero_url, :logger
8
-
8
+
9
9
  extend Forwardable
10
10
  def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :expires_at, :authorization_expires_at
11
11
 
12
12
  #
13
13
  # The consumer key and secret here correspond to those provided
14
- # to you by Xero inside the API Previewer.
14
+ # to you by Xero inside the API Previewer.
15
15
  def initialize(consumer_key, consumer_secret, options = {})
16
16
  @xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
17
17
  @client = OAuth.new(consumer_key, consumer_secret, options)
18
18
  end
19
-
19
+
20
20
  #
21
21
  # Retrieve all contacts from Xero
22
22
  #
@@ -34,27 +34,27 @@ module XeroGateway
34
34
 
35
35
  request_params[:ContactID] = options[:contact_id] if options[:contact_id]
36
36
  request_params[:ContactNumber] = options[:contact_number] if options[:contact_number]
37
- request_params[:OrderBy] = options[:order] if options[:order]
37
+ request_params[:OrderBy] = options[:order] if options[:order]
38
38
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
39
39
  request_params[:where] = options[:where] if options[:where]
40
-
40
+
41
41
  response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params)
42
-
42
+
43
43
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contacts'})
44
44
  end
45
-
45
+
46
46
  # Retrieve a contact from Xero
47
- # Usage get_contact_by_id(contact_id)
47
+ # Usage get_contact_by_id(contact_id)
48
48
  def get_contact_by_id(contact_id)
49
49
  get_contact(contact_id)
50
50
  end
51
51
 
52
52
  # Retrieve a contact from Xero
53
- # Usage get_contact_by_id(contact_id)
53
+ # Usage get_contact_by_id(contact_id)
54
54
  def get_contact_by_number(contact_number)
55
55
  get_contact(nil, contact_number)
56
56
  end
57
-
57
+
58
58
  # Factory method for building new Contact objects associated with this gateway.
59
59
  def build_contact(contact = {})
60
60
  case contact
@@ -63,11 +63,11 @@ module XeroGateway
63
63
  end
64
64
  contact
65
65
  end
66
-
66
+
67
67
  #
68
68
  # Creates a contact in Xero
69
69
  #
70
- # Usage :
70
+ # Usage :
71
71
  #
72
72
  # contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT #{Time.now.to_i}")
73
73
  # contact.email = "whoever@something.com"
@@ -85,24 +85,24 @@ module XeroGateway
85
85
  def create_contact(contact)
86
86
  save_contact(contact)
87
87
  end
88
-
88
+
89
89
  #
90
90
  # Updates an existing Xero contact
91
91
  #
92
- # Usage :
92
+ # Usage :
93
93
  #
94
94
  # contact = xero_gateway.get_contact(some_contact_id)
95
95
  # contact.email = "a_new_email_ddress"
96
96
  #
97
- # xero_gateway.update_contact(contact)
97
+ # xero_gateway.update_contact(contact)
98
98
  def update_contact(contact)
99
99
  raise "contact_id or contact_number is required for updating contacts" if contact.contact_id.nil? and contact.contact_number.nil?
100
100
  save_contact(contact)
101
101
  end
102
-
102
+
103
103
  #
104
104
  # Updates an array of contacts in a single API operation.
105
- #
105
+ #
106
106
  # Usage :
107
107
  # contacts = [XeroGateway::Contact.new(:name => 'Joe Bloggs'), XeroGateway::Contact.new(:name => 'Jane Doe')]
108
108
  # result = gateway.update_contacts(contacts)
@@ -116,7 +116,7 @@ module XeroGateway
116
116
  contact.to_xml(b)
117
117
  end
118
118
  }
119
-
119
+
120
120
  response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
121
121
 
122
122
  response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'POST/contacts'})
@@ -125,7 +125,7 @@ module XeroGateway
125
125
  end
126
126
  response
127
127
  end
128
-
128
+
129
129
  # Retrieves all invoices from Xero
130
130
  #
131
131
  # Usage : get_invoices
@@ -133,12 +133,12 @@ module XeroGateway
133
133
  #
134
134
  # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
135
135
  def get_invoices(options = {})
136
-
136
+
137
137
  request_params = {}
138
-
138
+
139
139
  request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id]
140
140
  request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
141
- request_params[:OrderBy] = options[:order] if options[:order]
141
+ request_params[:OrderBy] = options[:order] if options[:order]
142
142
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
143
143
 
144
144
  request_params[:where] = options[:where] if options[:where]
@@ -147,21 +147,33 @@ module XeroGateway
147
147
 
148
148
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoices'})
149
149
  end
150
-
150
+
151
151
  # Retrieves a single invoice
152
152
  #
153
+ # You can get a PDF-formatted invoice by specifying :pdf as the format argument
154
+ #
153
155
  # Usage : get_invoice("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
154
156
  # get_invoice("OIT-12345") # By number
155
- def get_invoice(invoice_id_or_number)
157
+ def get_invoice(invoice_id_or_number, format = :xml)
156
158
  request_params = {}
157
-
159
+ headers = {}
160
+
161
+ headers.merge!("Accept" => "application/pdf") if format == :pdf
162
+
158
163
  url = "#{@xero_url}/Invoices/#{URI.escape(invoice_id_or_number)}"
159
-
160
- response_xml = http_get(@client, url, request_params)
161
164
 
162
- parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoice'})
165
+ response = http_get(@client, url, request_params, headers)
166
+
167
+ if format == :pdf
168
+ Tempfile.open(invoice_id_or_number) do |f|
169
+ f.write(response)
170
+ f
171
+ end
172
+ else
173
+ parse_response(response, {:request_params => request_params}, {:request_signature => 'GET/Invoice'})
174
+ end
163
175
  end
164
-
176
+
165
177
  # Factory method for building new Invoice objects associated with this gateway.
166
178
  def build_invoice(invoice = {})
167
179
  case invoice
@@ -170,12 +182,12 @@ module XeroGateway
170
182
  end
171
183
  invoice
172
184
  end
173
-
185
+
174
186
  # Creates an invoice in Xero based on an invoice object.
175
187
  #
176
188
  # Invoice and line item totals are calculated automatically.
177
189
  #
178
- # Usage :
190
+ # Usage :
179
191
  #
180
192
  # invoice = XeroGateway::Invoice.new({
181
193
  # :invoice_type => "ACCREC",
@@ -186,7 +198,7 @@ module XeroGateway
186
198
  # })
187
199
  # invoice.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
188
200
  # invoice.contact.phone.number = "12345"
189
- # invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
201
+ # invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
190
202
  # invoice.line_items << XeroGateway::LineItem.new(
191
203
  # :description => "THE DESCRIPTION OF THE LINE ITEM",
192
204
  # :unit_amount => 100,
@@ -203,12 +215,12 @@ module XeroGateway
203
215
  #
204
216
  # Updates an existing Xero invoice
205
217
  #
206
- # Usage :
218
+ # Usage :
207
219
  #
208
220
  # invoice = xero_gateway.get_invoice(some_invoice_id)
209
221
  # invoice.due_date = Date.today
210
222
  #
211
- # xero_gateway.update_invoice(invoice)
223
+ # xero_gateway.update_invoice(invoice)
212
224
 
213
225
  def update_invoice(invoice)
214
226
  raise "invoice_id is required for updating invoices" if invoice.invoice_id.nil?
@@ -217,7 +229,7 @@ module XeroGateway
217
229
 
218
230
  #
219
231
  # Creates an array of invoices with a single API request.
220
- #
232
+ #
221
233
  # Usage :
222
234
  # invoices = [XeroGateway::Invoice.new(...), XeroGateway::Invoice.new(...)]
223
235
  # result = gateway.create_invoices(invoices)
@@ -229,8 +241,8 @@ module XeroGateway
229
241
  invoice.to_xml(b)
230
242
  end
231
243
  }
232
-
233
- response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
244
+
245
+ response_xml = http_put(@client, "#{@xero_url}/Invoices?SummarizeErrors=false", request_xml, {})
234
246
 
235
247
  response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/invoices'})
236
248
  response.invoices.each_with_index do | response_invoice, index |
@@ -246,12 +258,12 @@ module XeroGateway
246
258
  #
247
259
  # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
248
260
  def get_credit_notes(options = {})
249
-
261
+
250
262
  request_params = {}
251
-
263
+
252
264
  request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id]
253
265
  request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number]
254
- request_params[:OrderBy] = options[:order] if options[:order]
266
+ request_params[:OrderBy] = options[:order] if options[:order]
255
267
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
256
268
 
257
269
  request_params[:where] = options[:where] if options[:where]
@@ -260,21 +272,21 @@ module XeroGateway
260
272
 
261
273
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNotes'})
262
274
  end
263
-
275
+
264
276
  # Retrieves a single credit_note
265
277
  #
266
278
  # Usage : get_credit_note("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
267
279
  # get_credit_note("OIT-12345") # By number
268
280
  def get_credit_note(credit_note_id_or_number)
269
281
  request_params = {}
270
-
282
+
271
283
  url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}"
272
-
284
+
273
285
  response_xml = http_get(@client, url, request_params)
274
286
 
275
287
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNote'})
276
288
  end
277
-
289
+
278
290
  # Factory method for building new CreditNote objects associated with this gateway.
279
291
  def build_credit_note(credit_note = {})
280
292
  case credit_note
@@ -283,12 +295,12 @@ module XeroGateway
283
295
  end
284
296
  credit_note
285
297
  end
286
-
298
+
287
299
  # Creates an credit_note in Xero based on an credit_note object.
288
300
  #
289
301
  # CreditNote and line item totals are calculated automatically.
290
302
  #
291
- # Usage :
303
+ # Usage :
292
304
  #
293
305
  # credit_note = XeroGateway::CreditNote.new({
294
306
  # :credit_note_type => "ACCREC",
@@ -299,7 +311,7 @@ module XeroGateway
299
311
  # })
300
312
  # credit_note.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
301
313
  # credit_note.contact.phone.number = "12345"
302
- # credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
314
+ # credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
303
315
  # credit_note.line_items << XeroGateway::LineItem.new(
304
316
  # :description => "THE DESCRIPTION OF THE LINE ITEM",
305
317
  # :unit_amount => 100,
@@ -313,21 +325,21 @@ module XeroGateway
313
325
  request_xml = credit_note.to_xml
314
326
  response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml)
315
327
  response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_note'})
316
-
328
+
317
329
  # Xero returns credit_notes inside an <CreditNotes> tag, even though there's only ever
318
330
  # one for this request
319
331
  response.response_item = response.credit_notes.first
320
-
332
+
321
333
  if response.success? && response.credit_note && response.credit_note.credit_note_id
322
- credit_note.credit_note_id = response.credit_note.credit_note_id
334
+ credit_note.credit_note_id = response.credit_note.credit_note_id
323
335
  end
324
-
336
+
325
337
  response
326
338
  end
327
-
339
+
328
340
  #
329
341
  # Creates an array of credit_notes with a single API request.
330
- #
342
+ #
331
343
  # Usage :
332
344
  # credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)]
333
345
  # result = gateway.create_credit_notes(credit_notes)
@@ -339,7 +351,7 @@ module XeroGateway
339
351
  credit_note.to_xml(b)
340
352
  end
341
353
  }
342
-
354
+
343
355
  response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml, {})
344
356
 
345
357
  response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_notes'})
@@ -475,7 +487,7 @@ module XeroGateway
475
487
  response_xml = http_get(@client, "#{xero_url}/Accounts")
476
488
  parse_response(response_xml, {}, {:request_signature => 'GET/accounts'})
477
489
  end
478
-
490
+
479
491
  #
480
492
  # Returns a XeroGateway::AccountsList object that makes working with
481
493
  # the Xero list of accounts easier and allows caching the results.
@@ -500,7 +512,7 @@ module XeroGateway
500
512
  response_xml = http_get(@client, "#{xero_url}/Organisation")
501
513
  parse_response(response_xml, {}, {:request_signature => 'GET/organisation'})
502
514
  end
503
-
515
+
504
516
  #
505
517
  # Gets all currencies for a specific organisation in Xero
506
518
  #
@@ -508,7 +520,7 @@ module XeroGateway
508
520
  response_xml = http_get(@client, "#{xero_url}/Currencies")
509
521
  parse_response(response_xml, {}, {:request_signature => 'GET/currencies'})
510
522
  end
511
-
523
+
512
524
  #
513
525
  # Gets all Tax Rates for a specific organisation in Xero
514
526
  #
@@ -517,19 +529,33 @@ module XeroGateway
517
529
  parse_response(response_xml, {}, {:request_signature => 'GET/tax_rates'})
518
530
  end
519
531
 
532
+ #
533
+ # Create Payment record in Xero
534
+ #
535
+ def create_payment(payment)
536
+ b = Builder::XmlMarkup.new
537
+
538
+ request_xml = b.Payments do
539
+ payment.to_xml(b)
540
+ end
541
+
542
+ response_xml = http_put(@client, "#{xero_url}/Payments", request_xml)
543
+ parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/payments'})
544
+ end
545
+
520
546
  private
521
547
 
522
- def get_contact(contact_id = nil, contact_number = nil)
548
+ def get_contact(contact_id = nil, contact_number = nil)
523
549
  request_params = contact_id ? { :contactID => contact_id } : { :contactNumber => contact_number }
524
550
  response_xml = http_get(@client, "#{@xero_url}/Contacts/#{URI.escape(contact_id||contact_number)}", request_params)
525
551
 
526
552
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contact'})
527
553
  end
528
-
554
+
529
555
  # Create or update a contact record based on if it has a contact_id or contact_number.
530
556
  def save_contact(contact)
531
557
  request_xml = contact.to_xml
532
-
558
+
533
559
  response_xml = nil
534
560
  create_or_save = nil
535
561
  if contact.contact_id.nil? && contact.contact_number.nil?
@@ -550,7 +576,7 @@ module XeroGateway
550
576
  # Create or update an invoice record based on if it has an invoice_id.
551
577
  def save_invoice(invoice)
552
578
  request_xml = invoice.to_xml
553
-
579
+
554
580
  response_xml = nil
555
581
  create_or_save = nil
556
582
  if invoice.invoice_id.nil?
@@ -564,15 +590,15 @@ module XeroGateway
564
590
  end
565
591
 
566
592
  response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/invoice"})
567
-
593
+
568
594
  # Xero returns invoices inside an <Invoices> tag, even though there's only ever
569
595
  # one for this request
570
596
  response.response_item = response.invoices.first
571
-
597
+
572
598
  if response.success? && response.invoice && response.invoice.invoice_id
573
- invoice.invoice_id = response.invoice.invoice_id
599
+ invoice.invoice_id = response.invoice.invoice_id
574
600
  end
575
-
601
+
576
602
  response
577
603
  end
578
604
 
@@ -637,12 +663,12 @@ module XeroGateway
637
663
  response = XeroGateway::Response.new
638
664
 
639
665
  doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
640
-
666
+
641
667
  # check for responses we don't understand
642
668
  raise UnparseableResponse.new(doc.root.name) unless doc.root.name == "Response"
643
669
 
644
670
  response_element = REXML::XPath.first(doc, "/Response")
645
-
671
+
646
672
  response_element.children.reject { |e| e.is_a? REXML::Text }.each do |element|
647
673
  case(element.name)
648
674
  when "ID" then response.response_id = element.text
@@ -671,28 +697,20 @@ module XeroGateway
671
697
  when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) }
672
698
  when "Organisations" then response.response_item = Organisation.from_xml(element.children.first) # Xero only returns the Authorized Organisation
673
699
  when "TrackingCategories" then element.children.each {|child| response.response_item << TrackingCategory.from_xml(child) }
674
- when "Errors" then element.children.each { |error| parse_error(error, response) }
700
+ when "Errors" then response.errors = element.children.map { |error| Error.parse(error) }
675
701
  end
676
702
  end if response_element
677
-
703
+
678
704
  # If a single result is returned don't put it in an array
679
705
  if response.response_item.is_a?(Array) && response.response_item.size == 1
680
706
  response.response_item = response.response_item.first
681
707
  end
682
-
708
+
683
709
  response.request_params = request[:request_params]
684
710
  response.request_xml = request[:request_xml]
685
711
  response.response_xml = raw_response
686
712
  response
687
- end
688
-
689
- def parse_error(error_element, response)
690
- response.errors << Error.new(
691
- :description => REXML::XPath.first(error_element, "Description").text,
692
- :date_time => REXML::XPath.first(error_element, "//DateTime").text,
693
- :type => REXML::XPath.first(error_element, "//ExceptionType").text,
694
- :message => REXML::XPath.first(error_element, "//Message").text
695
- )
696
713
  end
697
- end
714
+
715
+ end
698
716
  end
@@ -3,25 +3,25 @@ module XeroGateway
3
3
  OPEN_TIMEOUT = 10 unless defined? OPEN_TIMEOUT
4
4
  READ_TIMEOUT = 60 unless defined? READ_TIMEOUT
5
5
  ROOT_CA_FILE = File.join(File.dirname(__FILE__), 'ca-certificates.crt') unless defined? ROOT_CA_FILE
6
-
7
- def http_get(client, url, extra_params = {})
8
- http_request(client, :get, url, nil, extra_params)
6
+
7
+ def http_get(client, url, extra_params = {}, headers = {})
8
+ http_request(client, :get, url, nil, extra_params, headers)
9
9
  end
10
10
 
11
- def http_post(client, url, body, extra_params = {})
12
- http_request(client, :post, url, body, extra_params)
11
+ def http_post(client, url, body, extra_params = {}, headers = {})
12
+ http_request(client, :post, url, body, extra_params, headers)
13
13
  end
14
14
 
15
- def http_put(client, url, body, extra_params = {})
16
- http_request(client, :put, url, body, extra_params)
15
+ def http_put(client, url, body, extra_params = {}, headers = {})
16
+ http_request(client, :put, url, body, extra_params, headers)
17
17
  end
18
-
18
+
19
19
  private
20
-
21
- def http_request(client, method, url, body, params = {})
20
+
21
+ def http_request(client, method, url, body, params = {}, headers = {})
22
22
  # headers = {'Accept-Encoding' => 'gzip, deflate'}
23
23
 
24
- headers = { 'charset' => 'utf-8' }
24
+ headers = headers.merge!('charset' => 'utf-8')
25
25
 
26
26
  if method != :get
27
27
  headers['Content-Type'] ||= "application/x-www-form-urlencoded"
@@ -38,39 +38,43 @@ module XeroGateway
38
38
 
39
39
  # # Only setup @cached_http once on first use as loading the CA file is quite expensive computationally.
40
40
  # unless @cached_http && @cached_http.address == uri.host && @cached_http.port == uri.port
41
- # @cached_http = Net::HTTP.new(uri.host, uri.port)
41
+ # @cached_http = Net::HTTP.new(uri.host, uri.port)
42
42
  # @cached_http.open_timeout = OPEN_TIMEOUT
43
43
  # @cached_http.read_timeout = READ_TIMEOUT
44
44
  # @cached_http.use_ssl = true
45
- #
45
+ #
46
46
  # # Need to validate server's certificate against root certificate authority to prevent man-in-the-middle attacks.
47
47
  # @cached_http.ca_file = ROOT_CA_FILE
48
48
  # # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
49
49
  # @cached_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
50
50
  # @cached_http.verify_depth = 5
51
51
  # end
52
-
52
+
53
53
  logger.info("\n== [#{Time.now.to_s}] XeroGateway Request: #{uri.request_uri} ") if self.logger
54
-
54
+
55
55
  response = case method
56
56
  when :get then client.get(uri.request_uri, headers)
57
57
  when :post then client.post(uri.request_uri, { :xml => body }, headers)
58
58
  when :put then client.put(uri.request_uri, { :xml => body }, headers)
59
59
  end
60
-
60
+
61
61
  if self.logger
62
62
  logger.info("== [#{Time.now.to_s}] XeroGateway Response (#{response.code})")
63
-
63
+
64
64
  unless response.code.to_i == 200
65
65
  logger.info("== #{uri.request_uri} Response Body \n\n #{response.plain_body} \n == End Response Body")
66
66
  end
67
67
  end
68
-
68
+
69
69
  case response.code.to_i
70
70
  when 200
71
- response.plain_body
71
+ if RUBY_VERSION >= "1.9"
72
+ response.plain_body.force_encoding("UTF-8")
73
+ else
74
+ response.plain_body
75
+ end
72
76
  when 400
73
- handle_error!(body, response)
77
+ handle_error!(body, response)
74
78
  when 401
75
79
  handle_oauth_error!(response)
76
80
  when 404
@@ -79,11 +83,11 @@ module XeroGateway
79
83
  raise "Unknown response code: #{response.code.to_i}"
80
84
  end
81
85
  end
82
-
86
+
83
87
  def handle_oauth_error!(response)
84
88
  error_details = CGI.parse(response.plain_body)
85
89
  description = error_details["oauth_problem_advice"].first
86
-
90
+
87
91
  # see http://oauth.pbworks.com/ProblemReporting
88
92
  # In addition to token_expired and token_rejected, Xero also returns
89
93
  # 'rate limit exceeded' when more than 60 requests have been made in
@@ -96,32 +100,32 @@ module XeroGateway
96
100
  else raise OAuth::UnknownError.new(error_details["oauth_problem"].first + ':' + description)
97
101
  end
98
102
  end
99
-
103
+
100
104
  def handle_error!(request_xml, response)
101
-
105
+
102
106
  raw_response = response.plain_body
103
-
107
+
104
108
  # Xero Gateway API Exceptions *claim* to be UTF-16 encoded, but fail REXML/Iconv parsing...
105
109
  # So let's ignore that :)
106
110
  raw_response.gsub! '<?xml version="1.0" encoding="utf-16"?>', ''
107
-
111
+
108
112
  doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
109
-
113
+
110
114
  if doc.root.name == "ApiException"
111
115
 
112
- raise ApiException.new(doc.root.elements["Type"].text,
116
+ raise ApiException.new(doc.root.elements["Type"].text,
113
117
  doc.root.elements["Message"].text,
114
- request_xml,
118
+ request_xml,
115
119
  raw_response)
116
120
 
117
121
  else
118
-
122
+
119
123
  raise "Unparseable 400 Response: #{raw_response}"
120
-
124
+
121
125
  end
122
-
126
+
123
127
  end
124
-
128
+
125
129
  def handle_object_not_found!(response, request_url)
126
130
  case(request_url)
127
131
  when /Invoices/ then raise InvoiceNotFoundError.new("Invoice not found in Xero.")
@@ -130,6 +134,6 @@ module XeroGateway
130
134
  else raise ObjectNotFound.new(request_url)
131
135
  end
132
136
  end
133
-
137
+
134
138
  end
135
139
  end
@@ -30,13 +30,14 @@ module XeroGateway
30
30
  attr_accessor :gateway
31
31
 
32
32
  # Any errors that occurred when the #valid? method called.
33
- attr_reader :errors
33
+ # Or errors that were within the XML payload from Xero
34
+ attr_accessor :errors
34
35
 
35
36
  # Represents whether the line_items have been downloaded when getting from GET /API.XRO/2.0/INVOICES
36
37
  attr_accessor :line_items_downloaded
37
38
 
38
39
  # All accessible fields
39
- attr_accessor :invoice_id, :invoice_number, :invoice_type, :invoice_status, :date, :due_date, :reference, :line_amount_types, :currency_code, :line_items, :contact, :payments, :fully_paid_on, :amount_due, :amount_paid, :amount_credited, :sent_to_contact, :url
40
+ attr_accessor :invoice_id, :invoice_number, :invoice_type, :invoice_status, :date, :due_date, :reference, :branding_theme_id, :line_amount_types, :currency_code, :line_items, :contact, :payments, :fully_paid_on, :amount_due, :amount_paid, :amount_credited, :sent_to_contact, :url
40
41
 
41
42
 
42
43
  def initialize(params = {})
@@ -66,6 +67,10 @@ module XeroGateway
66
67
  def valid?
67
68
  @errors = []
68
69
 
70
+ if !INVOICE_TYPE[invoice_type]
71
+ @errors << ['invoice_type', "must be one of #{INVOICE_TYPE.keys.join('/')}"]
72
+ end
73
+
69
74
  if !invoice_id.nil? && invoice_id !~ GUID_REGEX
70
75
  @errors << ['invoice_id', 'must be blank or a valid Xero GUID']
71
76
  end
@@ -187,6 +192,7 @@ module XeroGateway
187
192
  b.DueDate Invoice.format_date(self.due_date) if self.due_date
188
193
  b.Status self.invoice_status if self.invoice_status
189
194
  b.Reference self.reference if self.reference
195
+ b.BrandingThemeID self.branding_theme_id if self.branding_theme_id
190
196
  b.LineAmountTypes self.line_amount_types
191
197
  b.LineItems {
192
198
  self.line_items.each do |line_item|
@@ -211,22 +217,24 @@ module XeroGateway
211
217
  when "DueDate" then invoice.due_date = parse_date(element.text)
212
218
  when "Status" then invoice.invoice_status = element.text
213
219
  when "Reference" then invoice.reference = element.text
220
+ when "BrandingThemeID" then invoice.branding_theme_id = element.text
214
221
  when "LineAmountTypes" then invoice.line_amount_types = element.text
215
222
  when "LineItems" then element.children.each {|line_item| invoice.line_items_downloaded = true; invoice.line_items << LineItem.from_xml(line_item) }
216
223
  when "SubTotal" then invoice.sub_total = BigDecimal.new(element.text)
217
224
  when "TotalTax" then invoice.total_tax = BigDecimal.new(element.text)
218
225
  when "Total" then invoice.total = BigDecimal.new(element.text)
219
226
  when "InvoiceID" then invoice.invoice_id = element.text
220
- when "InvoiceNumber" then invoice.invoice_number = element.text
227
+ when "InvoiceNumber" then invoice.invoice_number = element.text
221
228
  when "Payments" then element.children.each { | payment | invoice.payments << Payment.from_xml(payment) }
222
229
  when "AmountDue" then invoice.amount_due = BigDecimal.new(element.text)
223
230
  when "AmountPaid" then invoice.amount_paid = BigDecimal.new(element.text)
224
231
  when "AmountCredited" then invoice.amount_credited = BigDecimal.new(element.text)
225
232
  when "SentToContact" then invoice.sent_to_contact = (element.text.strip.downcase == "true")
226
233
  when "Url" then invoice.url = element.text
234
+ when "ValidationErrors" then invoice.errors = element.children.map { |error| Error.parse(error) }
227
235
  end
228
- end
236
+ end
229
237
  invoice
230
- end
238
+ end
231
239
  end
232
240
  end
@@ -1,18 +1,18 @@
1
1
  module XeroGateway
2
-
2
+
3
3
  # Shamelessly based on the Twitter Gem's OAuth implementation by John Nunemaker
4
4
  # Thanks!
5
- #
5
+ #
6
6
  # http://twitter.rubyforge.org/
7
7
  # http://github.com/jnunemaker/twitter/
8
-
8
+
9
9
  class OAuth
10
-
10
+
11
11
  class TokenExpired < StandardError; end
12
12
  class TokenInvalid < StandardError; end
13
13
  class RateLimitExceeded < StandardError; end
14
14
  class UnknownError < StandardError; end
15
-
15
+
16
16
  unless defined? XERO_CONSUMER_OPTIONS
17
17
  XERO_CONSUMER_OPTIONS = {
18
18
  :site => "https://api.xero.com",
@@ -21,54 +21,55 @@ module XeroGateway
21
21
  :authorize_path => "/oauth/Authorize"
22
22
  }.freeze
23
23
  end
24
-
24
+
25
25
  extend Forwardable
26
26
  def_delegators :access_token, :get, :post, :put, :delete
27
-
28
- attr_reader :ctoken, :csecret, :consumer_options, :session_handle, :expires_at, :authorization_expires_at
29
-
27
+
28
+ attr_reader :ctoken, :csecret, :consumer_options, :authorization_expires_at
29
+ attr_accessor :session_handle
30
+
30
31
  def initialize(ctoken, csecret, options = {})
31
32
  @ctoken, @csecret = ctoken, csecret
32
33
  @consumer_options = XERO_CONSUMER_OPTIONS.merge(options)
33
34
  end
34
-
35
+
35
36
  def consumer
36
37
  @consumer ||= ::OAuth::Consumer.new(@ctoken, @csecret, consumer_options)
37
38
  end
38
-
39
+
39
40
  def request_token(params = {})
40
41
  @request_token ||= consumer.get_request_token(params)
41
42
  end
42
-
43
+
43
44
  def authorize_from_request(rtoken, rsecret, params = {})
44
45
  request_token = ::OAuth::RequestToken.new(consumer, rtoken, rsecret)
45
46
  access_token = request_token.get_access_token(params)
46
47
  @atoken, @asecret = access_token.token, access_token.secret
47
-
48
+
48
49
  update_attributes_from_token(access_token)
49
50
  end
50
-
51
+
51
52
  def access_token
52
53
  @access_token ||= ::OAuth::AccessToken.new(consumer, @atoken, @asecret)
53
54
  end
54
-
55
+
55
56
  def authorize_from_access(atoken, asecret)
56
57
  @atoken, @asecret = atoken, asecret
57
58
  end
58
-
59
+
59
60
  # Renewing access tokens only works for Partner applications
60
61
  def renew_access_token(access_token = nil, access_secret = nil, session_handle = nil)
61
62
  access_token ||= @atoken
62
- access_secret ||= @asecret
63
+ access_secret ||= @asecret
63
64
  session_handle ||= @session_handle
64
-
65
+
65
66
  old_token = ::OAuth::RequestToken.new(consumer, access_token, access_secret)
66
-
67
+
67
68
  access_token = old_token.get_access_token({
68
69
  :oauth_session_handle => session_handle,
69
70
  :token => old_token
70
71
  })
71
-
72
+
72
73
  update_attributes_from_token(access_token)
73
74
  rescue ::OAuth::Unauthorized => e
74
75
  # If the original access token is for some reason invalid an OAuth::Unauthorized could be raised.
@@ -76,9 +77,9 @@ module XeroGateway
76
77
  # situation the end user will need to re-authorize the application via the request token authorization URL
77
78
  raise XeroGateway::OAuth::TokenInvalid.new(e.message)
78
79
  end
79
-
80
+
80
81
  private
81
-
82
+
82
83
  # Update instance variables with those from the AccessToken.
83
84
  def update_attributes_from_token(access_token)
84
85
  @expires_at = Time.now + access_token.params[:oauth_expires_in].to_i
@@ -87,6 +88,6 @@ module XeroGateway
87
88
  @atoken, @asecret = access_token.token, access_token.secret
88
89
  @access_token = nil
89
90
  end
90
-
91
+
91
92
  end
92
93
  end
@@ -6,7 +6,7 @@ module XeroGateway
6
6
  NO_SSL_CLIENT_CERT_MESSAGE = "You need to provide a client ssl certificate and key pair (these are the ones you got from Entrust and should not be password protected) as :ssl_client_cert and :ssl_client_key (should be .crt or .pem files)"
7
7
  NO_PRIVATE_KEY_ERROR_MESSAGE = "You need to provide your private key (corresponds to the public key you uploaded at api.xero.com) as :private_key_file (should be .crt or .pem files)"
8
8
 
9
- def_delegators :client, :session_handle, :renew_access_token
9
+ def_delegators :client, :session_handle, :renew_access_token, :authorization_expires_at
10
10
 
11
11
  def initialize(consumer_key, consumer_secret, options = {})
12
12
 
@@ -26,5 +26,10 @@ module XeroGateway
26
26
  @xero_url = options[:xero_url] || "https://api-partner.xero.com/api.xro/2.0"
27
27
  @client = OAuth.new(consumer_key, consumer_secret, options)
28
28
  end
29
+
30
+ def set_session_handle(handle)
31
+ client.session_handle = handle
32
+ end
33
+
29
34
  end
30
35
  end
@@ -7,16 +7,16 @@ module XeroGateway
7
7
  attr_reader :errors
8
8
 
9
9
  # All accessible fields
10
- attr_accessor :payment_id, :date, :amount, :reference, :currency_rate
11
-
10
+ attr_accessor :invoice_id, :invoice_number, :account_id, :code, :payment_id, :date, :amount, :reference, :currency_rate
11
+
12
12
  def initialize(params = {})
13
13
  @errors ||= []
14
-
14
+
15
15
  params.each do |k,v|
16
16
  self.send("#{k}=", v)
17
17
  end
18
18
  end
19
-
19
+
20
20
  def self.from_xml(payment_element)
21
21
  payment = Payment.new
22
22
  payment_element.children.each do | element |
@@ -26,18 +26,45 @@ module XeroGateway
26
26
  when 'Amount' then payment.amount = BigDecimal.new(element.text)
27
27
  when 'Reference' then payment.reference = element.text
28
28
  when 'CurrencyRate' then payment.currency_rate = BigDecimal.new(element.text)
29
- end
29
+ when 'Invoice' then payment.send("#{element.children.first.name.underscore}=", element.children.first.text)
30
+ when 'Account' then payment.send("#{element.children.first.name.underscore}=", element.children.first.text)
31
+ end
30
32
  end
31
33
  payment
32
- end
33
-
34
+ end
35
+
34
36
  def ==(other)
35
37
  [:payment_id, :date, :amount].each do |field|
36
38
  return false if send(field) != other.send(field)
37
39
  end
38
40
  return true
39
41
  end
40
-
42
+
43
+ def to_xml(b = Builder::XmlMarkup.new)
44
+ b.Payment do
45
+
46
+ if self.invoice_id || self.invoice_number
47
+ b.Invoice do |i|
48
+ i.InvoiceID self.invoice_id if self.invoice_id
49
+ i.InvoiceNumber self.invoice_number if self.invoice_number
50
+ end
51
+ end
52
+
53
+ if self.account_id || self.code
54
+ b.Account do |a|
55
+ a.AccountID self.account_id if self.account_id
56
+ a.Code self.code if self.code
57
+ end
58
+ end
59
+
60
+ b.Amount self.amount if self.amount
61
+ b.CurrencyRate self.currency_rate if self.currency_rate
62
+ b.Reference self.reference if self.reference
63
+
64
+ b.Date self.class.format_date(self.date || Date.today)
65
+ end
66
+ end
67
+
41
68
 
42
69
  end
43
70
  end
@@ -47,7 +47,7 @@ module XeroGateway
47
47
  attribute = element.name
48
48
  underscored_attribute = element.name.underscore
49
49
 
50
- raise "Unknown attribute: #{attribute}" unless ATTRS.keys.include?(attribute)
50
+ next if !ATTRS.keys.include?(attribute)
51
51
 
52
52
  case (ATTRS[attribute])
53
53
  when :boolean then tax_rate.send("#{underscored_attribute}=", (element.text == "true"))
data/lib/xero_gateway.rb CHANGED
@@ -8,6 +8,7 @@ require "oauth"
8
8
  require 'oauth/signature/rsa/sha1'
9
9
  require "forwardable"
10
10
  require "active_support/all"
11
+ require "tempfile"
11
12
 
12
13
  require File.join(File.dirname(__FILE__), 'oauth', 'oauth_consumer')
13
14
 
@@ -33,6 +33,12 @@ class CreateInvoiceTest < Test::Unit::TestCase
33
33
  example_invoice = dummy_invoice.dup
34
34
  assert_equal true, example_invoice.valid?, "invoice is invalid - errors:\n\t#{example_invoice.errors.map { | error | "#{error[0]} #{error[1]}"}.join("\n\t")}"
35
35
  end
36
+
37
+ def test_create_invoice_invalid_with_invalid_invoice_type
38
+ example_invoice = dummy_invoice.dup
39
+ example_invoice.invoice_type = "ABC"
40
+ assert_equal false, example_invoice.valid?
41
+ end
36
42
 
37
43
  private
38
44
 
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class CreatePaymentsTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
6
+ def setup
7
+ @gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
8
+
9
+ if STUB_XERO_CALLS
10
+ @gateway.xero_url = "DUMMY_URL"
11
+
12
+ @gateway.stubs(:http_put).with {|client, url, body, params| url =~ /Invoices$/ }.returns(get_file_as_string("create_invoice.xml"))
13
+ @gateway.stubs(:http_put).with {|client, url, params| url =~ /Payments$/ }.returns(get_file_as_string("create_payments.xml"))
14
+ end
15
+ end
16
+
17
+ def test_create_payment
18
+ example_invoice = dummy_invoice.dup
19
+
20
+ result = @gateway.create_invoice(example_invoice)
21
+
22
+ payment = XeroGateway::Payment.new(
23
+ :invoice_id => result.invoice.invoice_id,
24
+ :amount => 500,
25
+ :reference => "Test Payment",
26
+ :date => Time.now,
27
+ :code => "601"
28
+ )
29
+
30
+ result = @gateway.create_payment(payment)
31
+
32
+ assert_kind_of XeroGateway::Response, result
33
+ assert result.success?
34
+ end
35
+ end
@@ -2,18 +2,25 @@ require File.dirname(__FILE__) + '/../test_helper'
2
2
 
3
3
  class GetInvoiceTest < Test::Unit::TestCase
4
4
  include TestHelper
5
-
5
+
6
+ INVOICE_GET_URL = /Invoices(\/[0-9a-z\-]+)?$/i
7
+
6
8
  def setup
7
9
  @gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
8
-
10
+
9
11
  if STUB_XERO_CALLS
10
12
  @gateway.xero_url = "DUMMY_URL"
11
-
12
- @gateway.stubs(:http_get).with {|client, url, params| url =~ /Invoices(\/[0-9a-z\-]+)?$/i }.returns(get_file_as_string("invoice.xml"))
13
- @gateway.stubs(:http_put).with {|client, url, body, params| url =~ /Invoices$/ }.returns(get_file_as_string("create_invoice.xml"))
13
+
14
+ @gateway.stubs(:http_get).with do |client, url, params, headers|
15
+ url =~ INVOICE_GET_URL && headers["Accept"] == "application/pdf"
16
+ end.returns(get_file_as_string("get_invoice.pdf"))
17
+
18
+ @gateway.stubs(:http_get).with {|client, url, params, headers| url =~ INVOICE_GET_URL && headers["Accept"].blank? }.returns(get_file_as_string("invoice.xml"))
19
+ @gateway.stubs(:http_put).with {|client, url, body, params| url =~ /Invoices$/ }.returns(get_file_as_string("create_invoice.xml"))
20
+
14
21
  end
15
22
  end
16
-
23
+
17
24
  def test_get_invoice
18
25
  # Make sure there is an invoice in Xero to retrieve
19
26
  invoice = @gateway.create_invoice(dummy_invoice).invoice
@@ -21,28 +28,36 @@ class GetInvoiceTest < Test::Unit::TestCase
21
28
  result = @gateway.get_invoice(invoice.invoice_id)
22
29
  assert result.success?
23
30
  assert !result.request_params.nil?
24
- assert !result.response_xml.nil?
31
+ assert !result.response_xml.nil?
25
32
  assert_equal result.invoice.invoice_number, invoice.invoice_number
26
33
 
27
34
  result = @gateway.get_invoice(invoice.invoice_number)
28
35
  assert result.success?
29
36
  assert !result.request_params.nil?
30
- assert !result.response_xml.nil?
37
+ assert !result.response_xml.nil?
31
38
  assert_equal result.invoice.invoice_id, invoice.invoice_id
32
39
  end
33
-
40
+
34
41
  def test_line_items_downloaded_set_correctly
35
42
  # Make sure there is an invoice in Xero to retrieve.
36
43
  example_invoice = @gateway.create_invoice(dummy_invoice).invoice
37
-
44
+
38
45
  # No line items.
39
46
  response = @gateway.get_invoice(example_invoice.invoice_id)
40
47
  assert_equal(true, response.success?)
41
-
48
+
42
49
  invoice = response.invoice
43
50
  assert_kind_of(XeroGateway::LineItem, invoice.line_items.first)
44
51
  assert_kind_of(XeroGateway::Invoice, invoice)
45
52
  assert_equal(true, invoice.line_items_downloaded?)
46
53
  end
47
-
54
+
55
+ def test_get_invoice_pdf
56
+ # Make sure there is an invoice in Xero to retrieve
57
+ example_invoice = @gateway.create_invoice(dummy_invoice).invoice
58
+
59
+ pdf_tempfile = @gateway.get_invoice(example_invoice.invoice_id, :pdf)
60
+ assert_equal get_file_as_string("get_invoice.pdf"), File.open(pdf_tempfile.path).read
61
+ end
62
+
48
63
  end
@@ -6,28 +6,28 @@ class GatewayTest < Test::Unit::TestCase
6
6
  def setup
7
7
  @gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
8
8
  end
9
-
9
+
10
10
  context "with error handling" do
11
-
11
+
12
12
  should "handle token expired" do
13
13
  XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("token_expired"), :code => "401"))
14
-
14
+
15
15
  assert_raises XeroGateway::OAuth::TokenExpired do
16
16
  @gateway.get_accounts
17
17
  end
18
18
  end
19
-
19
+
20
20
  should "handle invalid request tokens" do
21
21
  XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("invalid_request_token"), :code => "401"))
22
-
22
+
23
23
  assert_raises XeroGateway::OAuth::TokenInvalid do
24
24
  @gateway.get_accounts
25
25
  end
26
26
  end
27
-
27
+
28
28
  should "handle invalid consumer key" do
29
29
  XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("invalid_consumer_key"), :code => "401"))
30
-
30
+
31
31
  assert_raises XeroGateway::OAuth::TokenInvalid do
32
32
  @gateway.get_accounts
33
33
  end
@@ -48,10 +48,10 @@ class GatewayTest < Test::Unit::TestCase
48
48
  @gateway.get_accounts
49
49
  end
50
50
  end
51
-
51
+
52
52
  should "handle ApiExceptions" do
53
53
  XeroGateway::OAuth.any_instance.stubs(:put).returns(stub(:plain_body => get_file_as_string("api_exception.xml"), :code => "400"))
54
-
54
+
55
55
  assert_raises XeroGateway::ApiException do
56
56
  @gateway.create_invoice(XeroGateway::Invoice.new)
57
57
  end
@@ -86,17 +86,17 @@ class GatewayTest < Test::Unit::TestCase
86
86
 
87
87
  assert_raises XeroGateway::UnparseableResponse do
88
88
  @gateway.create_invoice(XeroGateway::Invoice.new)
89
- end
89
+ end
90
90
  end
91
-
91
+
92
92
  end
93
-
93
+
94
94
  def test_unknown_error_handling
95
95
  if STUB_XERO_CALLS
96
96
  @gateway.xero_url = "DUMMY_URL"
97
- @gateway.stubs(:http_get).with {|client, url, params| url =~ /Invoices\/AN_INVALID_ID$/ }.returns(get_file_as_string("unknown_error.xml"))
97
+ @gateway.stubs(:http_get).with {|client, url, params| url =~ /Invoices\/AN_INVALID_ID$/ }.returns(get_file_as_string("unknown_error.xml"))
98
98
  end
99
-
99
+
100
100
  result = @gateway.get_invoice("AN_INVALID_ID")
101
101
  assert !result.success?
102
102
  assert_equal 1, result.errors.size
@@ -109,7 +109,7 @@ class GatewayTest < Test::Unit::TestCase
109
109
  @gateway.xero_url = "DUMMY_URL"
110
110
  @gateway.stubs(:http_get).with {|client, url, params| url =~ /Invoices\/UNKNOWN_INVOICE_NO$/ }.returns(get_file_as_string("invoice_not_found_error.xml"))
111
111
  end
112
-
112
+
113
113
  result = @gateway.get_invoice("UNKNOWN_INVOICE_NO")
114
114
  assert !result.success?
115
115
  assert_equal 1, result.errors.size
@@ -240,8 +240,9 @@ class InvoiceTest < Test::Unit::TestCase
240
240
  end
241
241
 
242
242
  def test_optional_params
243
- invoice = create_test_invoice(:url => 'http://example.com')
243
+ invoice = create_test_invoice(:url => 'http://example.com', :branding_theme_id => 'a94a78db-5cc6-4e26-a52b-045237e56e6e')
244
244
  assert_equal 'http://example.com', invoice.url
245
+ assert_equal 'a94a78db-5cc6-4e26-a52b-045237e56e6e', invoice.branding_theme_id
245
246
  end
246
247
 
247
248
  private
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper.rb')
2
+
3
+ class PaymentTest < Test::Unit::TestCase
4
+
5
+ # Tests that a payment can be converted into XML that Xero can understand, and then converted back to a payment
6
+ def test_build_and_parse_xml
7
+ payment = create_test_payment
8
+
9
+ # Generate the XML message
10
+ payment_as_xml = payment.to_xml
11
+
12
+ # Parse the XML message and retrieve the account element
13
+ payment_element = REXML::XPath.first(REXML::Document.new(payment_as_xml), "/Payment")
14
+
15
+ # Build a new account from the XML
16
+ result_payment = XeroGateway::Payment.from_xml(payment_element)
17
+
18
+ # Check the details
19
+ assert_equal payment, result_payment
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def create_test_payment
26
+ XeroGateway::Payment.new.tap do |payment|
27
+ payment.invoice_id = "a99a9aaa-9999-99a9-9aa9-aaaaaa9a9999"
28
+ payment.amount = 100.0
29
+ payment.date = Time.now.beginning_of_day
30
+ payment.reference = "Invoice Payment"
31
+ payment.code = 200
32
+ end
33
+ end
34
+ end
data/xero_gateway.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "xero_gateway"
3
- s.version = "2.0.19"
4
- s.date = "2012-07-13"
3
+ s.version = "2.1.0"
4
+ s.date = "2012-11-19"
5
5
  s.summary = "Enables ruby based applications to communicate with the Xero API"
6
6
  s.email = "tim@connorsoftware.com"
7
7
  s.homepage = "http://github.com/tlconnor/xero_gateway"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xero_gateway
3
3
  version: !ruby/object:Gem::Version
4
- hash: 41
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
+ - 1
8
9
  - 0
9
- - 19
10
- version: 2.0.19
10
+ version: 2.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tim Connor
@@ -16,8 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2012-07-13 00:00:00 +12:00
20
- default_executable:
19
+ date: 2012-11-19 00:00:00 Z
21
20
  dependencies:
22
21
  - !ruby/object:Gem::Dependency
23
22
  name: builder
@@ -118,6 +117,7 @@ files:
118
117
  - test/integration/create_credit_note_test.rb
119
118
  - test/integration/create_invoice_test.rb
120
119
  - test/integration/create_manual_journal_test.rb
120
+ - test/integration/create_payments_test.rb
121
121
  - test/integration/get_accounts_test.rb
122
122
  - test/integration/get_bank_transaction_test.rb
123
123
  - test/integration/get_bank_transactions_test.rb
@@ -148,10 +148,10 @@ files:
148
148
  - test/unit/manual_journal_test.rb
149
149
  - test/unit/oauth_test.rb
150
150
  - test/unit/organisation_test.rb
151
+ - test/unit/payment_test.rb
151
152
  - test/unit/tax_rate_test.rb
152
153
  - test/unit/tracking_category_test.rb
153
154
  - lib/xero_gateway/ca-certificates.crt
154
- has_rdoc: true
155
155
  homepage: http://github.com/tlconnor/xero_gateway
156
156
  licenses: []
157
157
 
@@ -181,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
181
  requirements: []
182
182
 
183
183
  rubyforge_project:
184
- rubygems_version: 1.6.2
184
+ rubygems_version: 1.8.15
185
185
  signing_key:
186
186
  specification_version: 3
187
187
  summary: Enables ruby based applications to communicate with the Xero API