xero_gateway-float 2.0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +12 -0
- data/LICENSE +14 -0
- data/README.textile +357 -0
- data/Rakefile +14 -0
- data/examples/oauth.rb +25 -0
- data/examples/partner_app.rb +36 -0
- data/init.rb +1 -0
- data/lib/oauth/oauth_consumer.rb +14 -0
- data/lib/xero_gateway.rb +39 -0
- data/lib/xero_gateway/account.rb +95 -0
- data/lib/xero_gateway/accounts_list.rb +87 -0
- data/lib/xero_gateway/address.rb +96 -0
- data/lib/xero_gateway/bank_transaction.rb +178 -0
- data/lib/xero_gateway/ca-certificates.crt +2560 -0
- data/lib/xero_gateway/contact.rb +206 -0
- data/lib/xero_gateway/credit_note.rb +222 -0
- data/lib/xero_gateway/currency.rb +56 -0
- data/lib/xero_gateway/dates.rb +30 -0
- data/lib/xero_gateway/error.rb +18 -0
- data/lib/xero_gateway/exceptions.rb +46 -0
- data/lib/xero_gateway/gateway.rb +622 -0
- data/lib/xero_gateway/http.rb +138 -0
- data/lib/xero_gateway/http_encoding_helper.rb +49 -0
- data/lib/xero_gateway/invoice.rb +236 -0
- data/lib/xero_gateway/line_item.rb +125 -0
- data/lib/xero_gateway/line_item_calculations.rb +55 -0
- data/lib/xero_gateway/money.rb +16 -0
- data/lib/xero_gateway/oauth.rb +87 -0
- data/lib/xero_gateway/organisation.rb +75 -0
- data/lib/xero_gateway/partner_app.rb +30 -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 +41 -0
- data/lib/xero_gateway/tax_rate.rb +63 -0
- data/lib/xero_gateway/tracking_category.rb +87 -0
- data/test/integration/accounts_list_test.rb +109 -0
- data/test/integration/create_bank_transaction_test.rb +38 -0
- data/test/integration/create_contact_test.rb +66 -0
- data/test/integration/create_credit_note_test.rb +49 -0
- data/test/integration/create_invoice_test.rb +49 -0
- data/test/integration/get_accounts_test.rb +23 -0
- data/test/integration/get_bank_transaction_test.rb +51 -0
- data/test/integration/get_bank_transactions_test.rb +88 -0
- data/test/integration/get_contact_test.rb +28 -0
- data/test/integration/get_contacts_test.rb +40 -0
- data/test/integration/get_credit_note_test.rb +48 -0
- data/test/integration/get_credit_notes_test.rb +90 -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 +92 -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 +27 -0
- data/test/integration/update_bank_transaction_test.rb +31 -0
- data/test/integration/update_contact_test.rb +31 -0
- data/test/integration/update_invoice_test.rb +31 -0
- data/test/test_helper.rb +179 -0
- data/test/unit/account_test.rb +47 -0
- data/test/unit/bank_transaction_test.rb +126 -0
- data/test/unit/contact_test.rb +97 -0
- data/test/unit/credit_note_test.rb +284 -0
- data/test/unit/currency_test.rb +31 -0
- data/test/unit/gateway_test.rb +119 -0
- data/test/unit/invoice_test.rb +326 -0
- data/test/unit/oauth_test.rb +116 -0
- data/test/unit/organisation_test.rb +38 -0
- data/test/unit/tax_rate_test.rb +38 -0
- data/test/unit/tracking_category_test.rb +52 -0
- data/xero_gateway.gemspec +15 -0
- metadata +164 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
module Dates
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def format_date(time)
|
9
|
+
return time.strftime("%Y-%m-%d")
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_date_time(time)
|
13
|
+
return time.strftime("%Y%m%d%H%M%S")
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_date(time)
|
17
|
+
Date.civil(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_date_time(time)
|
21
|
+
Time.local(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i, time[11..12].to_i, time[14..15].to_i, time[17..18].to_i)
|
22
|
+
end
|
23
|
+
|
24
|
+
#eg: 2011-09-14T10:15:03.893
|
25
|
+
def parse_utc_date_time(time)
|
26
|
+
Time.utc(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i, time[11..12].to_i, time[14..15].to_i, time[17..18].to_i)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class Error
|
3
|
+
attr_accessor :description, :date_time, :type, :message
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
params.each do |k,v|
|
7
|
+
self.send("#{k}=", v)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
[:description, :date_time, :type, :message].each do |field|
|
13
|
+
return false if send(field) != other.send(field)
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class ApiException < StandardError
|
3
|
+
|
4
|
+
def initialize(type, message, request_xml, response_xml)
|
5
|
+
@type = type
|
6
|
+
@message = message
|
7
|
+
@request_xml = request_xml
|
8
|
+
@response_xml = response_xml
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :request_xml, :response_xml
|
12
|
+
|
13
|
+
def message
|
14
|
+
"#{@type}: #{@message} \n Generated by the following XML: \n #{@response_xml}"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class UnparseableResponse < StandardError
|
20
|
+
|
21
|
+
def initialize(root_element_name)
|
22
|
+
@root_element_name = root_element_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"A root element of #{@root_element_name} was returned, and we don't understand that!"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class ObjectNotFound < StandardError
|
32
|
+
|
33
|
+
def initialize(api_endpoint)
|
34
|
+
@api_endpoint = api_endpoint
|
35
|
+
end
|
36
|
+
|
37
|
+
def message
|
38
|
+
"Couldn't find object for API Endpoint #{@api_endpoint}"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class InvoiceNotFoundError < StandardError; end
|
44
|
+
class BankTransactionNotFoundError < StandardError; end
|
45
|
+
class CreditNoteNotFoundError < StandardError; end
|
46
|
+
end
|
@@ -0,0 +1,622 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
|
3
|
+
class Gateway
|
4
|
+
include Http
|
5
|
+
include Dates
|
6
|
+
|
7
|
+
attr_accessor :client, :xero_url, :logger
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :authorization_expires_at
|
11
|
+
|
12
|
+
#
|
13
|
+
# The consumer key and secret here correspond to those provided
|
14
|
+
# to you by Xero inside the API Previewer.
|
15
|
+
def initialize(consumer_key, consumer_secret, options = {})
|
16
|
+
@xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
|
17
|
+
@client = OAuth.new(consumer_key, consumer_secret, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Retrieve all contacts from Xero
|
22
|
+
#
|
23
|
+
# Usage : get_contacts(:order => :name)
|
24
|
+
# get_contacts(:modified_since => Time)
|
25
|
+
#
|
26
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
27
|
+
def get_contacts(options = {})
|
28
|
+
request_params = {}
|
29
|
+
|
30
|
+
if !options[:updated_after].nil?
|
31
|
+
warn '[warning] :updated_after is depracated in XeroGateway#get_contacts. Use :modified_since'
|
32
|
+
options[:modified_since] = options.delete(:updated_after)
|
33
|
+
end
|
34
|
+
|
35
|
+
request_params[:ContactID] = options[:contact_id] if options[:contact_id]
|
36
|
+
request_params[:ContactNumber] = options[:contact_number] if options[:contact_number]
|
37
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
38
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
39
|
+
request_params[:where] = options[:where] if options[:where]
|
40
|
+
|
41
|
+
response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params)
|
42
|
+
|
43
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contacts'})
|
44
|
+
end
|
45
|
+
|
46
|
+
# Retrieve a contact from Xero
|
47
|
+
# Usage get_contact_by_id(contact_id)
|
48
|
+
def get_contact_by_id(contact_id)
|
49
|
+
get_contact(contact_id)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Retrieve a contact from Xero
|
53
|
+
# Usage get_contact_by_id(contact_id)
|
54
|
+
def get_contact_by_number(contact_number)
|
55
|
+
get_contact(nil, contact_number)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Factory method for building new Contact objects associated with this gateway.
|
59
|
+
def build_contact(contact = {})
|
60
|
+
case contact
|
61
|
+
when Contact then contact.gateway = self
|
62
|
+
when Hash then contact = Contact.new(contact.merge({:gateway => self}))
|
63
|
+
end
|
64
|
+
contact
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Creates a contact in Xero
|
69
|
+
#
|
70
|
+
# Usage :
|
71
|
+
#
|
72
|
+
# contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT #{Time.now.to_i}")
|
73
|
+
# contact.email = "whoever@something.com"
|
74
|
+
# contact.phone.number = "12345"
|
75
|
+
# contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
76
|
+
# contact.address.line_2 = "LINE 2 OF THE ADDRESS"
|
77
|
+
# contact.address.line_3 = "LINE 3 OF THE ADDRESS"
|
78
|
+
# contact.address.line_4 = "LINE 4 OF THE ADDRESS"
|
79
|
+
# contact.address.city = "WELLINGTON"
|
80
|
+
# contact.address.region = "WELLINGTON"
|
81
|
+
# contact.address.country = "NEW ZEALAND"
|
82
|
+
# contact.address.post_code = "6021"
|
83
|
+
#
|
84
|
+
# create_contact(contact)
|
85
|
+
def create_contact(contact)
|
86
|
+
save_contact(contact)
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Updates an existing Xero contact
|
91
|
+
#
|
92
|
+
# Usage :
|
93
|
+
#
|
94
|
+
# contact = xero_gateway.get_contact(some_contact_id)
|
95
|
+
# contact.email = "a_new_email_ddress"
|
96
|
+
#
|
97
|
+
# xero_gateway.update_contact(contact)
|
98
|
+
def update_contact(contact)
|
99
|
+
raise "contact_id or contact_number is required for updating contacts" if contact.contact_id.nil? and contact.contact_number.nil?
|
100
|
+
save_contact(contact)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Updates an array of contacts in a single API operation.
|
105
|
+
#
|
106
|
+
# Usage :
|
107
|
+
# contacts = [XeroGateway::Contact.new(:name => 'Joe Bloggs'), XeroGateway::Contact.new(:name => 'Jane Doe')]
|
108
|
+
# result = gateway.update_contacts(contacts)
|
109
|
+
#
|
110
|
+
# Will update contacts with matching contact_id, contact_number or name or create if they don't exist.
|
111
|
+
#
|
112
|
+
def update_contacts(contacts)
|
113
|
+
b = Builder::XmlMarkup.new
|
114
|
+
request_xml = b.Contacts {
|
115
|
+
contacts.each do | contact |
|
116
|
+
contact.to_xml(b)
|
117
|
+
end
|
118
|
+
}
|
119
|
+
|
120
|
+
response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
|
121
|
+
|
122
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'POST/contacts'})
|
123
|
+
response.contacts.each_with_index do | response_contact, index |
|
124
|
+
contacts[index].contact_id = response_contact.contact_id if response_contact && response_contact.contact_id
|
125
|
+
end
|
126
|
+
response
|
127
|
+
end
|
128
|
+
|
129
|
+
# Retrieves all invoices from Xero
|
130
|
+
#
|
131
|
+
# Usage : get_invoices
|
132
|
+
# get_invoices(:invoice_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
133
|
+
#
|
134
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
135
|
+
def get_invoices(options = {})
|
136
|
+
|
137
|
+
request_params = {}
|
138
|
+
|
139
|
+
request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id]
|
140
|
+
request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
|
141
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
142
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
143
|
+
|
144
|
+
request_params[:where] = options[:where] if options[:where]
|
145
|
+
|
146
|
+
response_xml = http_get(@client, "#{@xero_url}/Invoices", request_params)
|
147
|
+
|
148
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoices'})
|
149
|
+
end
|
150
|
+
|
151
|
+
# Retrieves a single invoice
|
152
|
+
#
|
153
|
+
# Usage : get_invoice("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
154
|
+
# get_invoice("OIT-12345") # By number
|
155
|
+
def get_invoice(invoice_id_or_number)
|
156
|
+
request_params = {}
|
157
|
+
|
158
|
+
url = "#{@xero_url}/Invoices/#{URI.escape(invoice_id_or_number)}"
|
159
|
+
|
160
|
+
response_xml = http_get(@client, url, request_params)
|
161
|
+
|
162
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoice'})
|
163
|
+
end
|
164
|
+
|
165
|
+
# Factory method for building new Invoice objects associated with this gateway.
|
166
|
+
def build_invoice(invoice = {})
|
167
|
+
case invoice
|
168
|
+
when Invoice then invoice.gateway = self
|
169
|
+
when Hash then invoice = Invoice.new(invoice.merge(:gateway => self))
|
170
|
+
end
|
171
|
+
invoice
|
172
|
+
end
|
173
|
+
|
174
|
+
# Creates an invoice in Xero based on an invoice object.
|
175
|
+
#
|
176
|
+
# Invoice and line item totals are calculated automatically.
|
177
|
+
#
|
178
|
+
# Usage :
|
179
|
+
#
|
180
|
+
# invoice = XeroGateway::Invoice.new({
|
181
|
+
# :invoice_type => "ACCREC",
|
182
|
+
# :due_date => 1.month.from_now,
|
183
|
+
# :invoice_number => "YOUR INVOICE NUMBER",
|
184
|
+
# :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
185
|
+
# :line_amount_types => "Inclusive"
|
186
|
+
# })
|
187
|
+
# invoice.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
188
|
+
# invoice.contact.phone.number = "12345"
|
189
|
+
# invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
190
|
+
# invoice.line_items << XeroGateway::LineItem.new(
|
191
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
192
|
+
# :unit_amount => 100,
|
193
|
+
# :tax_amount => 12.5,
|
194
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
195
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
196
|
+
# )
|
197
|
+
#
|
198
|
+
# create_invoice(invoice)
|
199
|
+
def create_invoice(invoice)
|
200
|
+
save_invoice(invoice)
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Updates an existing Xero invoice
|
205
|
+
#
|
206
|
+
# Usage :
|
207
|
+
#
|
208
|
+
# invoice = xero_gateway.get_invoice(some_invoice_id)
|
209
|
+
# invoice.due_date = Date.today
|
210
|
+
#
|
211
|
+
# xero_gateway.update_invoice(invoice)
|
212
|
+
|
213
|
+
def update_invoice(invoice)
|
214
|
+
raise "invoice_id is required for updating invoices" if invoice.invoice_id.nil?
|
215
|
+
save_invoice(invoice)
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Creates an array of invoices with a single API request.
|
220
|
+
#
|
221
|
+
# Usage :
|
222
|
+
# invoices = [XeroGateway::Invoice.new(...), XeroGateway::Invoice.new(...)]
|
223
|
+
# result = gateway.create_invoices(invoices)
|
224
|
+
#
|
225
|
+
def create_invoices(invoices)
|
226
|
+
b = Builder::XmlMarkup.new
|
227
|
+
request_xml = b.Invoices {
|
228
|
+
invoices.each do | invoice |
|
229
|
+
invoice.to_xml(b)
|
230
|
+
end
|
231
|
+
}
|
232
|
+
|
233
|
+
response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
|
234
|
+
|
235
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/invoices'})
|
236
|
+
response.invoices.each_with_index do | response_invoice, index |
|
237
|
+
invoices[index].invoice_id = response_invoice.invoice_id if response_invoice && response_invoice.invoice_id
|
238
|
+
end
|
239
|
+
response
|
240
|
+
end
|
241
|
+
|
242
|
+
# Retrieves all credit_notes from Xero
|
243
|
+
#
|
244
|
+
# Usage : get_credit_notes
|
245
|
+
# get_credit_notes(:credit_note_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
246
|
+
#
|
247
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
248
|
+
def get_credit_notes(options = {})
|
249
|
+
|
250
|
+
request_params = {}
|
251
|
+
|
252
|
+
request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id]
|
253
|
+
request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number]
|
254
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
255
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
256
|
+
|
257
|
+
request_params[:where] = options[:where] if options[:where]
|
258
|
+
|
259
|
+
response_xml = http_get(@client, "#{@xero_url}/CreditNotes", request_params)
|
260
|
+
|
261
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNotes'})
|
262
|
+
end
|
263
|
+
|
264
|
+
# Retrieves a single credit_note
|
265
|
+
#
|
266
|
+
# Usage : get_credit_note("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
267
|
+
# get_credit_note("OIT-12345") # By number
|
268
|
+
def get_credit_note(credit_note_id_or_number)
|
269
|
+
request_params = {}
|
270
|
+
|
271
|
+
url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}"
|
272
|
+
|
273
|
+
response_xml = http_get(@client, url, request_params)
|
274
|
+
|
275
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNote'})
|
276
|
+
end
|
277
|
+
|
278
|
+
# Factory method for building new CreditNote objects associated with this gateway.
|
279
|
+
def build_credit_note(credit_note = {})
|
280
|
+
case credit_note
|
281
|
+
when CreditNote then credit_note.gateway = self
|
282
|
+
when Hash then credit_note = CreditNote.new(credit_note.merge(:gateway => self))
|
283
|
+
end
|
284
|
+
credit_note
|
285
|
+
end
|
286
|
+
|
287
|
+
# Creates an credit_note in Xero based on an credit_note object.
|
288
|
+
#
|
289
|
+
# CreditNote and line item totals are calculated automatically.
|
290
|
+
#
|
291
|
+
# Usage :
|
292
|
+
#
|
293
|
+
# credit_note = XeroGateway::CreditNote.new({
|
294
|
+
# :credit_note_type => "ACCREC",
|
295
|
+
# :due_date => 1.month.from_now,
|
296
|
+
# :credit_note_number => "YOUR CREDIT_NOTE NUMBER",
|
297
|
+
# :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
298
|
+
# :line_amount_types => "Inclusive"
|
299
|
+
# })
|
300
|
+
# credit_note.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
301
|
+
# credit_note.contact.phone.number = "12345"
|
302
|
+
# credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
303
|
+
# credit_note.line_items << XeroGateway::LineItem.new(
|
304
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
305
|
+
# :unit_amount => 100,
|
306
|
+
# :tax_amount => 12.5,
|
307
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
308
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
309
|
+
# )
|
310
|
+
#
|
311
|
+
# create_credit_note(credit_note)
|
312
|
+
def create_credit_note(credit_note)
|
313
|
+
request_xml = credit_note.to_xml
|
314
|
+
response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml)
|
315
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_note'})
|
316
|
+
|
317
|
+
# Xero returns credit_notes inside an <CreditNotes> tag, even though there's only ever
|
318
|
+
# one for this request
|
319
|
+
response.response_item = response.credit_notes.first
|
320
|
+
|
321
|
+
if response.success? && response.credit_note && response.credit_note.credit_note_id
|
322
|
+
credit_note.credit_note_id = response.credit_note.credit_note_id
|
323
|
+
end
|
324
|
+
|
325
|
+
response
|
326
|
+
end
|
327
|
+
|
328
|
+
#
|
329
|
+
# Creates an array of credit_notes with a single API request.
|
330
|
+
#
|
331
|
+
# Usage :
|
332
|
+
# credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)]
|
333
|
+
# result = gateway.create_credit_notes(credit_notes)
|
334
|
+
#
|
335
|
+
def create_credit_notes(credit_notes)
|
336
|
+
b = Builder::XmlMarkup.new
|
337
|
+
request_xml = b.CreditNotes {
|
338
|
+
credit_notes.each do | credit_note |
|
339
|
+
credit_note.to_xml(b)
|
340
|
+
end
|
341
|
+
}
|
342
|
+
|
343
|
+
response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml, {})
|
344
|
+
|
345
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_notes'})
|
346
|
+
response.credit_notes.each_with_index do | response_credit_note, index |
|
347
|
+
credit_notes[index].credit_note_id = response_credit_note.credit_note_id if response_credit_note && response_credit_note.credit_note_id
|
348
|
+
end
|
349
|
+
response
|
350
|
+
end
|
351
|
+
|
352
|
+
# Creates a bank transaction in Xero based on a bank transaction object.
|
353
|
+
#
|
354
|
+
# Bank transaction and line item totals are calculated automatically.
|
355
|
+
#
|
356
|
+
# Usage :
|
357
|
+
#
|
358
|
+
# bank_transaction = XeroGateway::BankTransaction.new({
|
359
|
+
# :type => "RECEIVE",
|
360
|
+
# :date => 1.month.from_now,
|
361
|
+
# :reference => "YOUR INVOICE NUMBER",
|
362
|
+
# })
|
363
|
+
# bank_transaction.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
364
|
+
# bank_transaction.contact.phone.number = "12345"
|
365
|
+
# bank_transaction.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
366
|
+
# bank_transaction.line_items << XeroGateway::LineItem.new(
|
367
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
368
|
+
# :unit_amount => 100,
|
369
|
+
# :tax_amount => 12.5,
|
370
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
371
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
372
|
+
# )
|
373
|
+
# bank_transaction.bank_account = XeroGateway::Account.new(:code => 'BANK-ABC)
|
374
|
+
#
|
375
|
+
# create_bank_transaction(bank_transaction)
|
376
|
+
def create_bank_transaction(bank_transaction)
|
377
|
+
save_bank_transaction(bank_transaction)
|
378
|
+
end
|
379
|
+
|
380
|
+
#
|
381
|
+
# Updates an existing Xero bank transaction
|
382
|
+
#
|
383
|
+
# Usage :
|
384
|
+
#
|
385
|
+
# bank_transaction = xero_gateway.get_bank_transaction(some_bank_transaction_id)
|
386
|
+
# bank_transaction.due_date = Date.today
|
387
|
+
#
|
388
|
+
# xero_gateway.update_bank_transaction(bank_transaction)
|
389
|
+
def update_bank_transaction(bank_transaction)
|
390
|
+
raise "bank_transaction_id is required for updating bank transactions" if bank_transaction.bank_transaction_id.nil?
|
391
|
+
save_bank_transaction(bank_transaction)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Retrieves all bank transactions from Xero
|
395
|
+
#
|
396
|
+
# Usage : get_bank_transactions
|
397
|
+
# get_bank_transactions(:bank_transaction_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
398
|
+
#
|
399
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
400
|
+
def get_bank_transactions(options = {})
|
401
|
+
request_params = {}
|
402
|
+
request_params[:BankTransactionID] = options[:bank_transaction_id] if options[:bank_transaction_id]
|
403
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
404
|
+
request_params[:where] = options[:where] if options[:where]
|
405
|
+
|
406
|
+
response_xml = http_get(@client, "#{@xero_url}/BankTransactions", request_params)
|
407
|
+
|
408
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransactions'})
|
409
|
+
end
|
410
|
+
|
411
|
+
# Retrieves a single bank transaction
|
412
|
+
#
|
413
|
+
# Usage : get_bank_transaction("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
414
|
+
# get_bank_transaction("OIT-12345") # By number
|
415
|
+
def get_bank_transaction(bank_transaction_id)
|
416
|
+
request_params = {}
|
417
|
+
url = "#{@xero_url}/BankTransactions/#{URI.escape(bank_transaction_id)}"
|
418
|
+
response_xml = http_get(@client, url, request_params)
|
419
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransaction'})
|
420
|
+
end
|
421
|
+
|
422
|
+
#
|
423
|
+
# Gets all accounts for a specific organization in Xero.
|
424
|
+
#
|
425
|
+
def get_accounts
|
426
|
+
response_xml = http_get(@client, "#{xero_url}/Accounts")
|
427
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/accounts'})
|
428
|
+
end
|
429
|
+
|
430
|
+
#
|
431
|
+
# Returns a XeroGateway::AccountsList object that makes working with
|
432
|
+
# the Xero list of accounts easier and allows caching the results.
|
433
|
+
#
|
434
|
+
def get_accounts_list(load_on_init = true)
|
435
|
+
AccountsList.new(self, load_on_init)
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Gets all tracking categories for a specific organization in Xero.
|
440
|
+
#
|
441
|
+
def get_tracking_categories
|
442
|
+
response_xml = http_get(@client, "#{xero_url}/TrackingCategories")
|
443
|
+
|
444
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/TrackingCategories'})
|
445
|
+
end
|
446
|
+
|
447
|
+
#
|
448
|
+
# Gets Organisation details
|
449
|
+
#
|
450
|
+
def get_organisation
|
451
|
+
response_xml = http_get(@client, "#{xero_url}/Organisation")
|
452
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/organisation'})
|
453
|
+
end
|
454
|
+
|
455
|
+
#
|
456
|
+
# Gets all currencies for a specific organisation in Xero
|
457
|
+
#
|
458
|
+
def get_currencies
|
459
|
+
response_xml = http_get(@client, "#{xero_url}/Currencies")
|
460
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/currencies'})
|
461
|
+
end
|
462
|
+
|
463
|
+
#
|
464
|
+
# Gets all Tax Rates for a specific organisation in Xero
|
465
|
+
#
|
466
|
+
def get_tax_rates
|
467
|
+
response_xml = http_get(@client, "#{xero_url}/TaxRates")
|
468
|
+
parse_response(response_xml, {}, {:request_signature => 'GET/tax_rates'})
|
469
|
+
end
|
470
|
+
|
471
|
+
def get_report(report_name, request_params = {})
|
472
|
+
response_xml = http_get(@client, "#{@xero_url}/Reports/#{report_name}", request_params)
|
473
|
+
end
|
474
|
+
|
475
|
+
|
476
|
+
private
|
477
|
+
|
478
|
+
def get_contact(contact_id = nil, contact_number = nil)
|
479
|
+
request_params = contact_id ? { :contactID => contact_id } : { :contactNumber => contact_number }
|
480
|
+
response_xml = http_get(@client, "#{@xero_url}/Contacts/#{URI.escape(contact_id||contact_number)}", request_params)
|
481
|
+
|
482
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contact'})
|
483
|
+
end
|
484
|
+
|
485
|
+
|
486
|
+
# Create or update a contact record based on if it has a contact_id or contact_number.
|
487
|
+
def save_contact(contact)
|
488
|
+
request_xml = contact.to_xml
|
489
|
+
|
490
|
+
response_xml = nil
|
491
|
+
create_or_save = nil
|
492
|
+
if contact.contact_id.nil? && contact.contact_number.nil?
|
493
|
+
# Create new contact record.
|
494
|
+
response_xml = http_put(@client, "#{@xero_url}/Contacts", request_xml, {})
|
495
|
+
create_or_save = :create
|
496
|
+
else
|
497
|
+
# Update existing contact record.
|
498
|
+
response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
|
499
|
+
create_or_save = :save
|
500
|
+
end
|
501
|
+
|
502
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/contact"})
|
503
|
+
contact.contact_id = response.contact.contact_id if response.contact && response.contact.contact_id
|
504
|
+
response
|
505
|
+
end
|
506
|
+
|
507
|
+
# Create or update an invoice record based on if it has an invoice_id.
|
508
|
+
def save_invoice(invoice)
|
509
|
+
request_xml = invoice.to_xml
|
510
|
+
|
511
|
+
response_xml = nil
|
512
|
+
create_or_save = nil
|
513
|
+
if invoice.invoice_id.nil?
|
514
|
+
# Create new invoice record.
|
515
|
+
response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
|
516
|
+
create_or_save = :create
|
517
|
+
else
|
518
|
+
# Update existing invoice record.
|
519
|
+
response_xml = http_post(@client, "#{@xero_url}/Invoices", request_xml, {})
|
520
|
+
create_or_save = :save
|
521
|
+
end
|
522
|
+
|
523
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/invoice"})
|
524
|
+
|
525
|
+
# Xero returns invoices inside an <Invoices> tag, even though there's only ever
|
526
|
+
# one for this request
|
527
|
+
response.response_item = response.invoices.first
|
528
|
+
|
529
|
+
if response.success? && response.invoice && response.invoice.invoice_id
|
530
|
+
invoice.invoice_id = response.invoice.invoice_id
|
531
|
+
end
|
532
|
+
|
533
|
+
response
|
534
|
+
end
|
535
|
+
|
536
|
+
# Create or update a bank transaction record based on if it has an bank_transaction_id.
|
537
|
+
def save_bank_transaction(bank_transaction)
|
538
|
+
request_xml = bank_transaction.to_xml
|
539
|
+
response_xml = nil
|
540
|
+
create_or_save = nil
|
541
|
+
|
542
|
+
if bank_transaction.bank_transaction_id.nil?
|
543
|
+
# Create new bank transaction record.
|
544
|
+
response_xml = http_put(@client, "#{@xero_url}/BankTransactions", request_xml, {})
|
545
|
+
create_or_save = :create
|
546
|
+
else
|
547
|
+
# Update existing bank transaction record.
|
548
|
+
response_xml = http_post(@client, "#{@xero_url}/BankTransactions", request_xml, {})
|
549
|
+
create_or_save = :save
|
550
|
+
end
|
551
|
+
|
552
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/BankTransactions"})
|
553
|
+
|
554
|
+
# Xero returns bank transactions inside an <BankTransactions> tag, even though there's only ever
|
555
|
+
# one for this request
|
556
|
+
response.response_item = response.bank_transactions.first
|
557
|
+
|
558
|
+
if response.success? && response.bank_transaction && response.bank_transaction.bank_transaction_id
|
559
|
+
bank_transaction.bank_transaction_id = response.bank_transaction.bank_transaction_id
|
560
|
+
end
|
561
|
+
|
562
|
+
response
|
563
|
+
end
|
564
|
+
|
565
|
+
def parse_response(raw_response, request = {}, options = {})
|
566
|
+
|
567
|
+
response = XeroGateway::Response.new
|
568
|
+
|
569
|
+
doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
|
570
|
+
|
571
|
+
# check for responses we don't understand
|
572
|
+
raise UnparseableResponse.new(doc.root.name) unless doc.root.name == "Response"
|
573
|
+
|
574
|
+
response_element = REXML::XPath.first(doc, "/Response")
|
575
|
+
|
576
|
+
response_element.children.reject { |e| e.is_a? REXML::Text }.each do |element|
|
577
|
+
case(element.name)
|
578
|
+
when "ID" then response.response_id = element.text
|
579
|
+
when "Status" then response.status = element.text
|
580
|
+
when "ProviderName" then response.provider = element.text
|
581
|
+
when "DateTimeUTC" then response.date_time = element.text
|
582
|
+
when "Contact" then response.response_item = Contact.from_xml(element, self)
|
583
|
+
when "Invoice" then response.response_item = Invoice.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"})
|
584
|
+
when "BankTransaction"
|
585
|
+
response.response_item = BankTransaction.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
|
586
|
+
when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) }
|
587
|
+
when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) }
|
588
|
+
when "BankTransactions"
|
589
|
+
element.children.each do |child|
|
590
|
+
response.response_item << BankTransaction.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
|
591
|
+
end
|
592
|
+
when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) }
|
593
|
+
when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) }
|
594
|
+
when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) }
|
595
|
+
when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) }
|
596
|
+
when "Organisations" then response.response_item = Organisation.from_xml(element.children.first) # Xero only returns the Authorized Organisation
|
597
|
+
when "TrackingCategories" then element.children.each {|child| response.response_item << TrackingCategory.from_xml(child) }
|
598
|
+
when "Errors" then element.children.each { |error| parse_error(error, response) }
|
599
|
+
end
|
600
|
+
end if response_element
|
601
|
+
|
602
|
+
# If a single result is returned don't put it in an array
|
603
|
+
if response.response_item.is_a?(Array) && response.response_item.size == 1
|
604
|
+
response.response_item = response.response_item.first
|
605
|
+
end
|
606
|
+
|
607
|
+
response.request_params = request[:request_params]
|
608
|
+
response.request_xml = request[:request_xml]
|
609
|
+
response.response_xml = raw_response
|
610
|
+
response
|
611
|
+
end
|
612
|
+
|
613
|
+
def parse_error(error_element, response)
|
614
|
+
response.errors << Error.new(
|
615
|
+
:description => REXML::XPath.first(error_element, "Description").text,
|
616
|
+
:date_time => REXML::XPath.first(error_element, "//DateTime").text,
|
617
|
+
:type => REXML::XPath.first(error_element, "//ExceptionType").text,
|
618
|
+
:message => REXML::XPath.first(error_element, "//Message").text
|
619
|
+
)
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|