xero_gateway 2.0.19 → 2.1.0

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