xero_gateway 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.textile +51 -0
- data/LICENSE +14 -0
- data/README.textile +289 -0
- data/Rakefile +14 -0
- data/examples/oauth.rb +25 -0
- data/init.rb +1 -0
- data/lib/xero_gateway/account.rb +78 -0
- data/lib/xero_gateway/accounts_list.rb +77 -0
- data/lib/xero_gateway/address.rb +97 -0
- data/lib/xero_gateway/ca-certificates.crt +2560 -0
- data/lib/xero_gateway/contact.rb +206 -0
- data/lib/xero_gateway/currency.rb +56 -0
- data/lib/xero_gateway/dates.rb +25 -0
- data/lib/xero_gateway/error.rb +18 -0
- data/lib/xero_gateway/exceptions.rb +41 -0
- data/lib/xero_gateway/gateway.rb +363 -0
- data/lib/xero_gateway/http.rb +128 -0
- data/lib/xero_gateway/http_encoding_helper.rb +49 -0
- data/lib/xero_gateway/invoice.rb +278 -0
- data/lib/xero_gateway/line_item.rb +123 -0
- data/lib/xero_gateway/money.rb +16 -0
- data/lib/xero_gateway/oauth.rb +56 -0
- data/lib/xero_gateway/organisation.rb +61 -0
- data/lib/xero_gateway/payment.rb +40 -0
- data/lib/xero_gateway/phone.rb +77 -0
- data/lib/xero_gateway/private_app.rb +17 -0
- data/lib/xero_gateway/response.rb +37 -0
- data/lib/xero_gateway/tax_rate.rb +63 -0
- data/lib/xero_gateway/tracking_category.rb +62 -0
- data/lib/xero_gateway.rb +33 -0
- data/test/integration/accounts_list_test.rb +109 -0
- data/test/integration/create_contact_test.rb +66 -0
- data/test/integration/create_invoice_test.rb +49 -0
- data/test/integration/get_accounts_test.rb +23 -0
- data/test/integration/get_contact_test.rb +28 -0
- data/test/integration/get_contacts_test.rb +40 -0
- data/test/integration/get_currencies_test.rb +25 -0
- data/test/integration/get_invoice_test.rb +48 -0
- data/test/integration/get_invoices_test.rb +90 -0
- data/test/integration/get_organisation_test.rb +24 -0
- data/test/integration/get_tax_rates_test.rb +25 -0
- data/test/integration/get_tracking_categories_test.rb +26 -0
- data/test/integration/update_contact_test.rb +31 -0
- data/test/stub_responses/accounts.xml +1 -0
- data/test/stub_responses/api_exception.xml +153 -0
- data/test/stub_responses/contact.xml +1 -0
- data/test/stub_responses/contacts.xml +2189 -0
- data/test/stub_responses/create_invoice.xml +64 -0
- data/test/stub_responses/currencies.xml +16 -0
- data/test/stub_responses/invalid_api_key_error.xml +1 -0
- data/test/stub_responses/invalid_consumer_key +1 -0
- data/test/stub_responses/invalid_request_token +1 -0
- data/test/stub_responses/invoice.xml +1 -0
- data/test/stub_responses/invoice_not_found_error.xml +1 -0
- data/test/stub_responses/invoices.xml +1 -0
- data/test/stub_responses/organisation.xml +14 -0
- data/test/stub_responses/tax_rates.xml +52 -0
- data/test/stub_responses/token_expired +1 -0
- data/test/stub_responses/tracking_categories.xml +1 -0
- data/test/stub_responses/unknown_error.xml +1 -0
- data/test/test_helper.rb +81 -0
- data/test/unit/account_test.rb +34 -0
- data/test/unit/contact_test.rb +97 -0
- data/test/unit/currency_test.rb +31 -0
- data/test/unit/gateway_test.rb +79 -0
- data/test/unit/invoice_test.rb +302 -0
- data/test/unit/oauth_test.rb +110 -0
- data/test/unit/organisation_test.rb +34 -0
- data/test/unit/tax_rate_test.rb +38 -0
- data/test/unit/tracking_category_test.rb +30 -0
- data/test/xsd/README +2 -0
- data/test/xsd/create_contact.xsd +61 -0
- data/test/xsd/create_invoice.xsd +107 -0
- data/xero_gateway.gemspec +87 -0
- metadata +172 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
module Http
|
3
|
+
OPEN_TIMEOUT = 10 unless defined? OPEN_TIMEOUT
|
4
|
+
READ_TIMEOUT = 60 unless defined? READ_TIMEOUT
|
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)
|
9
|
+
end
|
10
|
+
|
11
|
+
def http_post(client, url, body, extra_params = {})
|
12
|
+
http_request(client, :post, url, body, extra_params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def http_put(client, url, body, extra_params = {})
|
16
|
+
http_request(client, :put, url, body, extra_params)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def http_request(client, method, url, body, params = {})
|
22
|
+
# headers = {'Accept-Encoding' => 'gzip, deflate'}
|
23
|
+
|
24
|
+
headers = { 'charset' => 'utf-8' }
|
25
|
+
|
26
|
+
if method != :get
|
27
|
+
headers['Content-Type'] ||= "application/x-www-form-urlencoded"
|
28
|
+
end
|
29
|
+
|
30
|
+
# HAX. Xero completely misuse the If-Modified-Since HTTP header.
|
31
|
+
headers['If-Modified-Since'] = params.delete(:ModifiedAfter).utc.strftime("%Y-%m-%dT%H:%M:%S") if params[:ModifiedAfter]
|
32
|
+
|
33
|
+
if params.any?
|
34
|
+
url += "?" + params.map {|key,value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"}.join("&")
|
35
|
+
end
|
36
|
+
|
37
|
+
uri = URI.parse(url)
|
38
|
+
|
39
|
+
# # Only setup @cached_http once on first use as loading the CA file is quite expensive computationally.
|
40
|
+
# unless @cached_http && @cached_http.address == uri.host && @cached_http.port == uri.port
|
41
|
+
# @cached_http = Net::HTTP.new(uri.host, uri.port)
|
42
|
+
# @cached_http.open_timeout = OPEN_TIMEOUT
|
43
|
+
# @cached_http.read_timeout = READ_TIMEOUT
|
44
|
+
# @cached_http.use_ssl = true
|
45
|
+
#
|
46
|
+
# # Need to validate server's certificate against root certificate authority to prevent man-in-the-middle attacks.
|
47
|
+
# @cached_http.ca_file = ROOT_CA_FILE
|
48
|
+
# # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
49
|
+
# @cached_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
50
|
+
# @cached_http.verify_depth = 5
|
51
|
+
# end
|
52
|
+
|
53
|
+
logger.info("\n== [#{Time.now.to_s}] XeroGateway Request: #{uri.request_uri} ") if self.logger
|
54
|
+
|
55
|
+
response = case method
|
56
|
+
when :get then client.get(uri.request_uri, headers)
|
57
|
+
when :post then client.post(uri.request_uri, { :xml => body }, headers)
|
58
|
+
when :put then client.put(uri.request_uri, { :xml => body }, headers)
|
59
|
+
end
|
60
|
+
|
61
|
+
if self.logger
|
62
|
+
logger.info("== [#{Time.now.to_s}] XeroGateway Response (#{response.code})")
|
63
|
+
|
64
|
+
unless response.code.to_i == 200
|
65
|
+
logger.info("== #{uri.request_uri} Response Body \n\n #{response.plain_body} \n == End Response Body")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
case response.code.to_i
|
70
|
+
when 200
|
71
|
+
response.plain_body
|
72
|
+
when 400
|
73
|
+
handle_error!(response)
|
74
|
+
when 401
|
75
|
+
handle_oauth_error!(response)
|
76
|
+
when 404
|
77
|
+
handle_object_not_found!(response, url)
|
78
|
+
else
|
79
|
+
raise "Unknown response code: #{response.code.to_i}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_oauth_error!(response)
|
84
|
+
error_details = CGI.parse(response.plain_body)
|
85
|
+
description = error_details["oauth_problem_advice"].first
|
86
|
+
|
87
|
+
# see http://oauth.pbworks.com/ProblemReporting
|
88
|
+
# Xero only appears to return either token_expired or token_rejected
|
89
|
+
case (error_details["oauth_problem"].first)
|
90
|
+
when "token_expired" then raise OAuth::TokenExpired.new(description)
|
91
|
+
when "token_rejected" then raise OAuth::TokenInvalid.new(description)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_error!(response)
|
96
|
+
|
97
|
+
raw_response = response.plain_body
|
98
|
+
|
99
|
+
# Xero Gateway API Exceptions *claim* to be UTF-16 encoded, but fail REXML/Iconv parsing...
|
100
|
+
# So let's ignore that :)
|
101
|
+
raw_response.gsub! '<?xml version="1.0" encoding="utf-16"?>', ''
|
102
|
+
|
103
|
+
doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
|
104
|
+
|
105
|
+
if doc.root.name == "ApiException"
|
106
|
+
|
107
|
+
raise ApiException.new(doc.root.elements["Type"].text,
|
108
|
+
doc.root.elements["Message"].text,
|
109
|
+
raw_response)
|
110
|
+
|
111
|
+
else
|
112
|
+
|
113
|
+
raise "Unparseable 400 Response: #{raw_response}"
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
def handle_object_not_found!(response, request_url)
|
120
|
+
if request_url =~ /Invoices/
|
121
|
+
raise InvoiceNotFoundError.new("Invoice not found in Xero.")
|
122
|
+
else
|
123
|
+
raise ObjectNotFound.new(request_url)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Intended to extend the Net::HTTP response object
|
2
|
+
# and adds support for decoding gzip and deflate encoded pages
|
3
|
+
#
|
4
|
+
# Author: Jason Stirk <http://griffin.oobleyboo.com>
|
5
|
+
# Home: http://griffin.oobleyboo.com/projects/http_encoding_helper
|
6
|
+
# Created: 5 September 2007
|
7
|
+
# Last Updated: 23 November 2007
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# require 'net/http'
|
12
|
+
# require 'http_encoding_helper'
|
13
|
+
# headers={'Accept-Encoding' => 'gzip, deflate' }
|
14
|
+
# http = Net::HTTP.new('griffin.oobleyboo.com', 80)
|
15
|
+
# http.start do |h|
|
16
|
+
# request = Net::HTTP::Get.new('/', headers)
|
17
|
+
# response = http.request(request)
|
18
|
+
# content=response.plain_body # Method from our library
|
19
|
+
# puts "Transferred: #{response.body.length} bytes"
|
20
|
+
# puts "Compression: #{response['content-encoding']}"
|
21
|
+
# puts "Extracted: #{response.plain_body.length} bytes"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'zlib'
|
26
|
+
require 'stringio'
|
27
|
+
|
28
|
+
class Net::HTTPResponse
|
29
|
+
# Return the uncompressed content
|
30
|
+
def plain_body
|
31
|
+
encoding=self['content-encoding']
|
32
|
+
content=nil
|
33
|
+
if encoding then
|
34
|
+
case encoding
|
35
|
+
when 'gzip'
|
36
|
+
i=Zlib::GzipReader.new(StringIO.new(self.body))
|
37
|
+
content=i.read
|
38
|
+
when 'deflate'
|
39
|
+
i=Zlib::Inflate.new
|
40
|
+
content=i.inflate(self.body)
|
41
|
+
else
|
42
|
+
raise "Unknown encoding - #{encoding}"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
content=self.body
|
46
|
+
end
|
47
|
+
return content
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class Invoice
|
3
|
+
include Dates
|
4
|
+
include Money
|
5
|
+
|
6
|
+
class Error < RuntimeError; end
|
7
|
+
class NoGatewayError < Error; end
|
8
|
+
class InvalidLineItemError < Error; end
|
9
|
+
|
10
|
+
INVOICE_TYPE = {
|
11
|
+
'ACCREC' => 'Accounts Receivable',
|
12
|
+
'ACCPAY' => 'Accounts Payable'
|
13
|
+
} unless defined?(INVOICE_TYPE)
|
14
|
+
|
15
|
+
LINE_AMOUNT_TYPES = {
|
16
|
+
"Inclusive" => 'Invoice lines are inclusive tax',
|
17
|
+
"Exclusive" => 'Invoice lines are exclusive of tax (default)',
|
18
|
+
"NoTax" => 'Invoices lines have no tax'
|
19
|
+
} unless defined?(LINE_AMOUNT_TYPES)
|
20
|
+
|
21
|
+
INVOICE_STATUS = {
|
22
|
+
'AUTHORISED' => 'Approved invoices awaiting payment',
|
23
|
+
'DELETED' => 'Draft invoices that are deleted',
|
24
|
+
'DRAFT' => 'Invoices saved as draft or entered via API',
|
25
|
+
'PAID' => 'Invoices approved and fully paid',
|
26
|
+
'SUBMITTED' => 'Invoices entered by an employee awaiting approval',
|
27
|
+
'VOID' => 'Approved invoices that are voided'
|
28
|
+
} unless defined?(INVOICE_STATUS)
|
29
|
+
|
30
|
+
GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ unless defined?(GUID_REGEX)
|
31
|
+
|
32
|
+
# Xero::Gateway associated with this invoice.
|
33
|
+
attr_accessor :gateway
|
34
|
+
|
35
|
+
# Any errors that occurred when the #valid? method called.
|
36
|
+
attr_reader :errors
|
37
|
+
|
38
|
+
# Represents whether the line_items have been downloaded when getting from GET /API.XRO/2.0/INVOICES
|
39
|
+
attr_accessor :line_items_downloaded
|
40
|
+
|
41
|
+
# All accessible fields
|
42
|
+
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
|
43
|
+
|
44
|
+
|
45
|
+
def initialize(params = {})
|
46
|
+
@errors ||= []
|
47
|
+
@payments ||= []
|
48
|
+
|
49
|
+
# Check if the line items have been downloaded.
|
50
|
+
@line_items_downloaded = (params.delete(:line_items_downloaded) == true)
|
51
|
+
|
52
|
+
params = {
|
53
|
+
:line_amount_types => "Inclusive"
|
54
|
+
}.merge(params)
|
55
|
+
|
56
|
+
params.each do |k,v|
|
57
|
+
self.send("#{k}=", v)
|
58
|
+
end
|
59
|
+
|
60
|
+
@line_items ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
# Validate the Address record according to what will be valid by the gateway.
|
64
|
+
#
|
65
|
+
# Usage:
|
66
|
+
# address.valid? # Returns true/false
|
67
|
+
#
|
68
|
+
# Additionally sets address.errors array to an array of field/error.
|
69
|
+
def valid?
|
70
|
+
@errors = []
|
71
|
+
|
72
|
+
if !invoice_id.nil? && invoice_id !~ GUID_REGEX
|
73
|
+
@errors << ['invoice_id', 'must be blank or a valid Xero GUID']
|
74
|
+
end
|
75
|
+
|
76
|
+
if invoice_status && !INVOICE_STATUS[invoice_status]
|
77
|
+
@errors << ['invoice_status', "must be one of #{INVOICE_STATUS.keys.join('/')}"]
|
78
|
+
end
|
79
|
+
|
80
|
+
if line_amount_types && !LINE_AMOUNT_TYPES[line_amount_types]
|
81
|
+
@errors << ['line_amount_types', "must be one of #{LINE_AMOUNT_TYPES.keys.join('/')}"]
|
82
|
+
end
|
83
|
+
|
84
|
+
unless date
|
85
|
+
@errors << ['invoice_date', "can't be blank"]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Make sure contact is valid.
|
89
|
+
unless @contact && @contact.valid?
|
90
|
+
@errors << ['contact', 'is invalid']
|
91
|
+
end
|
92
|
+
|
93
|
+
# Make sure all line_items are valid.
|
94
|
+
unless line_items.all? { | line_item | line_item.valid? }
|
95
|
+
@errors << ['line_items', "at least one line item invalid"]
|
96
|
+
end
|
97
|
+
|
98
|
+
@errors.size == 0
|
99
|
+
end
|
100
|
+
|
101
|
+
# Helper method to create the associated contact object.
|
102
|
+
def build_contact(params = {})
|
103
|
+
self.contact = gateway ? gateway.build_contact(params) : Contact.new(params)
|
104
|
+
end
|
105
|
+
|
106
|
+
def contact
|
107
|
+
@contact ||= build_contact
|
108
|
+
end
|
109
|
+
|
110
|
+
# Helper method to create a new associated line_item.
|
111
|
+
# Usage:
|
112
|
+
# invoice.add_line_item({:description => "Bob's Widgets", :quantity => 1, :unit_amount => 120})
|
113
|
+
def add_line_item(params = {})
|
114
|
+
line_item = nil
|
115
|
+
case params
|
116
|
+
when Hash then line_item = LineItem.new(params)
|
117
|
+
when LineItem then line_item = params
|
118
|
+
else raise InvalidLineItemError
|
119
|
+
end
|
120
|
+
|
121
|
+
@line_items << line_item
|
122
|
+
|
123
|
+
line_item
|
124
|
+
end
|
125
|
+
|
126
|
+
# Deprecated (but API for setter remains).
|
127
|
+
#
|
128
|
+
# As sub_total must equal SUM(line_item.line_amount) for the API call to pass, this is now
|
129
|
+
# automatically calculated in the sub_total method.
|
130
|
+
def sub_total=(value)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Calculate the sub_total as the SUM(line_item.line_amount).
|
134
|
+
def sub_total
|
135
|
+
line_items.inject(BigDecimal.new('0')) { | sum, line_item | sum + BigDecimal.new(line_item.line_amount.to_s) }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Deprecated (but API for setter remains).
|
139
|
+
#
|
140
|
+
# As total_tax must equal SUM(line_item.tax_amount) for the API call to pass, this is now
|
141
|
+
# automatically calculated in the total_tax method.
|
142
|
+
def total_tax=(value)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Calculate the total_tax as the SUM(line_item.tax_amount).
|
146
|
+
def total_tax
|
147
|
+
line_items.inject(BigDecimal.new('0')) { | sum, line_item | sum + BigDecimal.new(line_item.tax_amount.to_s) }
|
148
|
+
end
|
149
|
+
|
150
|
+
# Deprecated (but API for setter remains).
|
151
|
+
#
|
152
|
+
# As total must equal sub_total + total_tax for the API call to pass, this is now
|
153
|
+
# automatically calculated in the total method.
|
154
|
+
def total=(value)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Calculate the toal as sub_total + total_tax.
|
158
|
+
def total
|
159
|
+
sub_total + total_tax
|
160
|
+
end
|
161
|
+
|
162
|
+
# Helper method to check if the invoice is accounts payable.
|
163
|
+
def accounts_payable?
|
164
|
+
invoice_type == 'ACCPAY'
|
165
|
+
end
|
166
|
+
|
167
|
+
# Helper method to check if the invoice is accounts receivable.
|
168
|
+
def accounts_receivable?
|
169
|
+
invoice_type == 'ACCREC'
|
170
|
+
end
|
171
|
+
|
172
|
+
# Whether or not the line_items have been downloaded (GET/invoices does not download line items).
|
173
|
+
def line_items_downloaded?
|
174
|
+
@line_items_downloaded
|
175
|
+
end
|
176
|
+
|
177
|
+
# If line items are not downloaded, then attempt a download now (if this record was found to begin with).
|
178
|
+
def line_items
|
179
|
+
if line_items_downloaded?
|
180
|
+
@line_items
|
181
|
+
|
182
|
+
# There is an invoice_is so we can assume this record was loaded from Xero.
|
183
|
+
# attempt to download the line_item records.
|
184
|
+
elsif invoice_id =~ GUID_REGEX
|
185
|
+
raise NoGatewayError unless @gateway
|
186
|
+
|
187
|
+
response = @gateway.get_invoice(invoice_id)
|
188
|
+
raise InvoiceNotFoundError, "Invoice with ID #{invoice_id} not found in Xero." unless response.success? && response.invoice.is_a?(XeroGateway::Invoice)
|
189
|
+
|
190
|
+
@line_items = response.invoice.line_items
|
191
|
+
@line_items_downloaded = true
|
192
|
+
|
193
|
+
@line_items
|
194
|
+
|
195
|
+
# Otherwise, this is a new invoice, so return the line_items reference.
|
196
|
+
else
|
197
|
+
@line_items
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def ==(other)
|
202
|
+
["invoice_number", "invoice_type", "invoice_status", "reference", "currency_code", "line_amount_types", "contact", "line_items"].each do |field|
|
203
|
+
return false if send(field) != other.send(field)
|
204
|
+
end
|
205
|
+
|
206
|
+
["date", "due_date"].each do |field|
|
207
|
+
return false if send(field).to_s != other.send(field).to_s
|
208
|
+
end
|
209
|
+
return true
|
210
|
+
end
|
211
|
+
|
212
|
+
# General purpose createsave method.
|
213
|
+
# If contact_id and contact_number are nil then create, otherwise, attempt to save.
|
214
|
+
def save
|
215
|
+
create
|
216
|
+
end
|
217
|
+
|
218
|
+
# Creates this invoice record (using gateway.create_invoice) with the associated gateway.
|
219
|
+
# If no gateway set, raise a Xero::Invoice::NoGatewayError exception.
|
220
|
+
def create
|
221
|
+
raise NoGatewayError unless gateway
|
222
|
+
gateway.create_invoice(self)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Alias create as save as this is currently the only write action.
|
226
|
+
alias_method :save, :create
|
227
|
+
|
228
|
+
def to_xml(b = Builder::XmlMarkup.new)
|
229
|
+
b.Invoice {
|
230
|
+
b.Type self.invoice_type
|
231
|
+
contact.to_xml(b)
|
232
|
+
b.Date Invoice.format_date(self.date || Date.today)
|
233
|
+
b.DueDate Invoice.format_date(self.due_date) if self.due_date
|
234
|
+
b.Status self.invoice_status if self.invoice_status
|
235
|
+
b.InvoiceNumber self.invoice_number if invoice_number
|
236
|
+
b.Reference self.reference if self.reference
|
237
|
+
b.CurrencyCode self.currency_code if self.currency_code
|
238
|
+
b.LineAmountTypes self.line_amount_types
|
239
|
+
b.LineItems {
|
240
|
+
self.line_items.each do |line_item|
|
241
|
+
line_item.to_xml(b)
|
242
|
+
end
|
243
|
+
}
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
247
|
+
#TODO UpdatedDateUTC
|
248
|
+
def self.from_xml(invoice_element, gateway = nil, options = {})
|
249
|
+
invoice = Invoice.new(options.merge({:gateway => gateway}))
|
250
|
+
invoice_element.children.each do |element|
|
251
|
+
case(element.name)
|
252
|
+
when "InvoiceID" then invoice.invoice_id = element.text
|
253
|
+
when "InvoiceNumber" then invoice.invoice_number = element.text
|
254
|
+
when "Type" then invoice.invoice_type = element.text
|
255
|
+
when "CurrencyCode" then invoice.currency_code = element.text
|
256
|
+
when "Type" then invoice.invoice_type = element.text
|
257
|
+
when "Contact" then invoice.contact = Contact.from_xml(element)
|
258
|
+
when "Date" then invoice.date = parse_date(element.text)
|
259
|
+
when "DueDate" then invoice.due_date = parse_date(element.text)
|
260
|
+
when "Status" then invoice.invoice_status = element.text
|
261
|
+
when "Reference" then invoice.reference = element.text
|
262
|
+
when "LineAmountTypes" then invoice.line_amount_types = element.text
|
263
|
+
when "LineItems" then element.children.each {|line_item| invoice.line_items_downloaded = true; invoice.line_items << LineItem.from_xml(line_item) }
|
264
|
+
when "SubTotal" then invoice.sub_total = BigDecimal.new(element.text)
|
265
|
+
when "TotalTax" then invoice.total_tax = BigDecimal.new(element.text)
|
266
|
+
when "Total" then invoice.total = BigDecimal.new(element.text)
|
267
|
+
when "InvoiceID" then invoice.invoice_id = element.text
|
268
|
+
when "InvoiceNumber" then invoice.invoice_number = element.text
|
269
|
+
when "Payments" then element.children.each { | payment | invoice.payments << Payment.from_xml(payment) }
|
270
|
+
when "AmountDue" then invoice.amount_due = BigDecimal.new(element.text)
|
271
|
+
when "AmountPaid" then invoice.amount_paid = BigDecimal.new(element.text)
|
272
|
+
when "AmountCredited" then invoice.amount_credited = BigDecimal.new(element.text)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
invoice
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|