xero_gateway 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -11
  3. data/README.md +212 -0
  4. data/Rakefile +0 -1
  5. data/lib/oauth/oauth_consumer.rb +25 -9
  6. data/lib/xero_gateway.rb +5 -1
  7. data/lib/xero_gateway/address.rb +29 -27
  8. data/lib/xero_gateway/bank_transaction.rb +11 -3
  9. data/lib/xero_gateway/base_record.rb +97 -0
  10. data/lib/xero_gateway/contact_group.rb +87 -0
  11. data/lib/xero_gateway/currency.rb +8 -54
  12. data/lib/xero_gateway/dates.rb +4 -0
  13. data/lib/xero_gateway/exceptions.rb +14 -13
  14. data/lib/xero_gateway/gateway.rb +90 -5
  15. data/lib/xero_gateway/http.rb +16 -8
  16. data/lib/xero_gateway/invoice.rb +9 -3
  17. data/lib/xero_gateway/item.rb +27 -0
  18. data/lib/xero_gateway/line_item_calculations.rb +3 -3
  19. data/lib/xero_gateway/manual_journal.rb +5 -2
  20. data/lib/xero_gateway/organisation.rb +22 -73
  21. data/lib/xero_gateway/payment.rb +22 -9
  22. data/lib/xero_gateway/phone.rb +2 -0
  23. data/lib/xero_gateway/report.rb +95 -0
  24. data/lib/xero_gateway/response.rb +12 -7
  25. data/lib/xero_gateway/tax_rate.rb +13 -61
  26. data/lib/xero_gateway/tracking_category.rb +39 -13
  27. data/lib/xero_gateway/version.rb +3 -0
  28. data/test/integration/get_items_test.rb +25 -0
  29. data/test/integration/get_payments_test.rb +54 -0
  30. data/test/test_helper.rb +51 -16
  31. data/test/unit/address_test.rb +34 -0
  32. data/test/unit/bank_transaction_test.rb +7 -0
  33. data/test/unit/contact_group_test.rb +47 -0
  34. data/test/unit/contact_test.rb +35 -16
  35. data/test/unit/credit_note_test.rb +2 -2
  36. data/test/unit/gateway_test.rb +170 -1
  37. data/test/unit/invoice_test.rb +27 -7
  38. data/test/unit/item_test.rb +51 -0
  39. data/test/unit/organisation_test.rb +1 -0
  40. data/test/unit/payment_test.rb +18 -11
  41. data/test/unit/report_test.rb +78 -0
  42. data/xero_gateway.gemspec +29 -13
  43. metadata +176 -89
  44. data/README.textile +0 -357
  45. data/init.rb +0 -1
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: df1d3e6fc6dce9cf79cc5e5c8098f635a50a07d4
4
+ data.tar.gz: 17287f49b11ab9698ae48b61196afa9445af8598
5
+ SHA512:
6
+ metadata.gz: fb668a27cc5f55d0e5257387572a8511e80fed505a5b81202ea148ace725ce85c5f2d2d5d45f78fe1cff366d302577ea5440465fd7c1379ced7212351d8aa23a
7
+ data.tar.gz: 81225482469435edbec27331e3b93fcde586e2605142519714f55c27c4ad13090a0b11632d780ca1b5cde78475d66818135c673cb4fe6b3e2d3a2660b7f2a4d4
data/Gemfile CHANGED
@@ -1,12 +1,4 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- gem 'builder', '>= 2.1.2'
4
- gem 'oauth', '>= 0.3.6'
5
- gem 'activesupport'
6
-
7
- group :test do
8
- gem 'i18n' # For fixing undocumented active_support dependency
9
- gem 'mocha'
10
- gem 'shoulda'
11
- gem 'libxml-ruby'
12
- end
3
+ # Specify your gem's dependencies in omniauth-mashapeid.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ Xero API wrapper [![Build Status](https://travis-ci.org/xero-gateway/xero_gateway.svg?branch=master)](https://travis-ci.org/xero-gateway/xero_gateway) [![Gem Version](https://badge.fury.io/rb/xero_gateway.svg)](https://badge.fury.io/rb/xero_gateway)
2
+ ================
3
+
4
+ # Getting Started
5
+
6
+ This is a Ruby gem for communicating with the Xero API.
7
+ You can find more information about the Xero API at <https://developer.xero.com>.
8
+
9
+ ## Installation
10
+
11
+ Just add the `xero_gateway` gem to your Gemfile, like so:
12
+
13
+ ```ruby
14
+ gem 'xero_gateway'
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ gateway = XeroGateway::Gateway.new(YOUR_OAUTH_CONSUMER_KEY, YOUR_OAUTH_CONSUMER_SECRET)
21
+ ```
22
+
23
+ ### Authenticating with OAuth
24
+
25
+ The Xero Gateway uses [OAuth 1.0a](https://oauth.net/core/1.0a/) for authentication. Xero Gateway
26
+ implements OAuth in a very similar manner to the [Twitter gem by John Nunemaker](http://github.com/jnunemaker/twitter)
27
+ , so if you've used that before this will all seem familiar.
28
+
29
+ 1. **Get a Consumer Key & Secret**
30
+
31
+ First off, you'll need to get a Consumer Key/Secret pair for your
32
+ application from Xero.\
33
+ Head to <http://api.xero.com>, log in and then click My Applications
34
+ &gt; Add Application.
35
+
36
+ If you want to create a private application (that accesses your own Xero
37
+ account rather than your users), you'll need to generate an RSA keypair
38
+ and an X509 certificate. This can be done with OpenSSL as below:
39
+
40
+ ```
41
+ openssl genrsa –out privatekey.pem 1024
42
+ openssl req –newkey rsa:1024 –x509 –key privatekey.pem –out publickey.cer –days 365
43
+ openssl pkcs12 –export –out public_privatekey.pfx –inkey privatekey.pem –in publickey.cer
44
+ ```
45
+
46
+ On the right-hand-side of your application's page there's a box titled
47
+ "OAuth Credentials". Use the Key and Secret from this box in order to
48
+ set up a new Gateway instance.
49
+
50
+ (If you're unsure about the Callback URL, specify nothing - it will
51
+ become clear a bit later)
52
+
53
+ 2. **Create a Xero Gateway in your App**
54
+
55
+ ```ruby
56
+ gateway = XeroGateway::Gateway.new(YOUR_OAUTH_CONSUMER_KEY, YOUR_OAUTH_CONSUMER_SECRET)
57
+ ```
58
+
59
+ or for Private applications
60
+
61
+ ```ruby
62
+ require 'xero_gateway'
63
+ gateway = XeroGateway::PrivateApp.new(YOUR_OAUTH_CONSUMER_KEY, YOUR_OAUTH_CONSUMER_SECRET, PATH_TO_YOUR_PRIVATE_KEY)
64
+ ```
65
+
66
+ 3. **Creating a Request Token**
67
+
68
+ You'll then need to get a Request Token from Xero.
69
+
70
+ ```ruby
71
+ request_token = gateway.request_token
72
+ ```
73
+
74
+ You should keep this around - you'll need it to exchange for an Access
75
+ Token later. (If you're using Rails, this means storing it in the
76
+ session or something similar)
77
+
78
+ Next, you need to redirect your user to the authorization url for this
79
+ request token. In Rails, that looks something like this:
80
+
81
+ ```ruby
82
+ redirect_to request_token.authorize_url
83
+ ```
84
+
85
+ You may also provide a callback parameter, which is the URL within your
86
+ app the user will be redirected to. See next section for more
87
+ information on what parameters Xero sends with this request.
88
+
89
+ ```ruby
90
+ request_token = request_token(oauth_callback: "http://yourapp.comcom/xero/callback")
91
+ redirect_to request_token.authorize_url
92
+ ```
93
+
94
+ 4. **Retrieving an Access Token**
95
+
96
+ If you've specified a Callback URL when setting up your application or
97
+ provided an oauth\_callback parameter on your request token, your user
98
+ will be redirected to that URL with an OAuth Verifier as a GET
99
+ parameter. You can then exchange your Request Token for an Access Token
100
+ like this (assuming Rails, once again):
101
+
102
+ ```ruby
103
+ gateway.authorize_from_request(request_token.token, request_token.secret, oauth_verifier: params[:oauth_verifier])
104
+ ```
105
+
106
+ (If you haven't specified a Callback URL, the user will be presented
107
+ with a numeric verifier which they must copy+paste into your
108
+ application; see examples/oauth.rb for an example)
109
+
110
+ Now you can access Xero API methods:
111
+
112
+ ```ruby
113
+ gateway.get_contacts
114
+ # => #<XeroGateway::Response:0x007fd367181388 ...
115
+ ```
116
+
117
+ ### Storing Access Tokens
118
+
119
+ You can also store the Access Token/Secret pair so that you can access
120
+ the API without user intervention. Currently, these access tokens are
121
+ only valid for 30 minutes, and will raise a
122
+ `XeroGateway::OAuth::TokenExpired` exception if you attempt to access the
123
+ API beyond the token's expiry time.
124
+
125
+ ```ruby
126
+ access_token, access_secret = gateway.access_token
127
+ ```
128
+
129
+ You can authorize a `Gateway` instance later on using the
130
+ `authorize_from_access` method:
131
+
132
+ ```ruby
133
+ gateway = XeroGateway::Gateway.new(XERO_CONSUMER_KEY, XERO_CONSUMER_SECRET)
134
+ gateway.authorize_from_access(your_stored_token.access_token, your_stored_token.access_secret)
135
+ ```
136
+
137
+ ## Examples
138
+
139
+ Open examples/oauth.rb and change CONSUMER\_KEY and CONSUMER\_SECRET to
140
+ the values for a Test OAuth Application in order to see an example of
141
+ OAuth at work.
142
+
143
+ If you're working with Rails, a controller similar to this might come in
144
+ handy:
145
+
146
+ ```ruby
147
+ class XeroSessionsController < ApplicationController
148
+
149
+ before_action :get_xero_gateway
150
+
151
+ def new
152
+ session[:request_token] = @xero_gateway.request_token.token
153
+ session[:request_secret] = @xero_gateway.request_token.secret
154
+
155
+ redirect_to @xero_gateway.request_token.authorize_url
156
+ end
157
+
158
+ def create
159
+ @xero_gateway.authorize_from_request(session[:request_token], session[:request_secret],
160
+ oauth_verifier: params[:oauth_verifier])
161
+
162
+ session[:xero_auth] = { access_token: @xero_gateway.access_token.token,
163
+ access_secret: @xero_gateway.access_token.secret }
164
+
165
+ session.data.delete(:request_token)
166
+ session.data.delete(:request_secret)
167
+ end
168
+
169
+ def destroy
170
+ session.data.delete(:xero_auth)
171
+ end
172
+
173
+ private
174
+
175
+ def get_xero_gateway
176
+ @xero_gateway = XeroGateway::Gateway.new(YOUR_OAUTH_CONSUMER_KEY, YOUR_OAUTH_CONSUMER_SECRET)
177
+ end
178
+
179
+ end
180
+ ```
181
+
182
+ Note that I'm just storing the Access Token + Secret in the session here
183
+ - you could equally store them in the database if you felt like
184
+ refreshing them every 30 minutes ;)
185
+
186
+ ## API Methods
187
+
188
+ You can find a full listing of all implemented methods on [the wiki page](https://github.com/xero-gateway/xero_gateway/wiki/API-Methods).
189
+
190
+ ## Logging
191
+
192
+ You can specify a logger to use (so you can track down those tricky
193
+ exceptions) by using:
194
+
195
+ ```ruby
196
+ gateway.logger = ActiveSupport::BufferedLogger.new("log_file_name.log")
197
+ ```
198
+
199
+ Your logger simply needs to respond to `info`.
200
+
201
+ ## Contributing
202
+
203
+ We welcome contributions, thanks for pitching in! :sparkles:
204
+
205
+ 1. Fork the repo
206
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
207
+ 3. Make sure you have some tests, and they pass! (`bundle exec rake`)
208
+ 4. Commit your changes (`git commit -am 'Added some feature'`)
209
+ 5. Push to the branch (`git push origin my-new-feature`)
210
+ 6. Create new Pull Request
211
+
212
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
- require 'rdoc/task'
4
3
 
5
4
  desc 'Default: run unit tests.'
6
5
  task :default => :test
@@ -1,14 +1,30 @@
1
1
  module OAuth
2
2
  class Consumer
3
-
4
- def http_with_ssl_client_certificates(*args)
5
- @http ||= http_without_ssl_client_certificates(*args).tap do |http|
6
- http.cert = options[:ssl_client_cert]
7
- http.key = options[:ssl_client_key]
3
+
4
+ if RUBY_VERSION >= "2.0.0"
5
+
6
+ # we got Module#prepend, let's use it
7
+ module ClientCertificateExtensions
8
+ def http
9
+ super.tap do |http|
10
+ http.cert = options[:ssl_client_cert]
11
+ http.key = options[:ssl_client_key]
12
+ end
13
+ end
8
14
  end
15
+
16
+ prepend ClientCertificateExtensions
17
+
18
+ else
19
+ def http_with_ssl_client_certificates(*args)
20
+ @http ||= http_without_ssl_client_certificates(*args).tap do |http|
21
+ http.cert = options[:ssl_client_cert]
22
+ http.key = options[:ssl_client_key]
23
+ end
24
+ end
25
+
26
+ alias_method_chain :http, :ssl_client_certificates
9
27
  end
10
-
11
- alias_method_chain :http, :ssl_client_certificates
12
-
28
+
13
29
  end
14
- end
30
+ end
data/lib/xero_gateway.rb CHANGED
@@ -18,10 +18,12 @@ require File.join(File.dirname(__FILE__), 'xero_gateway', 'dates')
18
18
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'money')
19
19
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'line_item_calculations')
20
20
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'response')
21
+ require File.join(File.dirname(__FILE__), 'xero_gateway', 'base_record')
21
22
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'account')
22
23
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'accounts_list')
23
24
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'tracking_category')
24
25
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'contact')
26
+ require File.join(File.dirname(__FILE__), 'xero_gateway', 'contact_group')
25
27
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'line_item')
26
28
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'payment')
27
29
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'invoice')
@@ -30,13 +32,15 @@ require File.join(File.dirname(__FILE__), 'xero_gateway', 'credit_note')
30
32
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'journal_line')
31
33
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'manual_journal')
32
34
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'address')
35
+ require File.join(File.dirname(__FILE__), 'xero_gateway', 'report')
33
36
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'phone')
34
37
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'organisation')
35
38
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'tax_rate')
36
39
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'currency')
40
+ require File.join(File.dirname(__FILE__), 'xero_gateway', 'item')
37
41
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'error')
38
42
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'oauth')
39
43
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'exceptions')
40
44
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'gateway')
41
45
  require File.join(File.dirname(__FILE__), 'xero_gateway', 'private_app')
42
- require File.join(File.dirname(__FILE__), 'xero_gateway', 'partner_app')
46
+ require File.join(File.dirname(__FILE__), 'xero_gateway', 'partner_app')
@@ -1,44 +1,44 @@
1
1
  module XeroGateway
2
2
  class Address
3
-
3
+
4
4
  ADDRESS_TYPE = {
5
5
  'STREET' => 'Street',
6
6
  'POBOX' => 'PO Box'
7
7
  } unless defined?(ADDRESS_TYPE)
8
-
8
+
9
9
  # Any errors that occurred when the #valid? method called.
10
10
  attr_reader :errors
11
-
12
- attr_accessor :address_type, :line_1, :line_2, :line_3, :line_4, :city, :region, :post_code, :country
13
-
11
+
12
+ attr_accessor :address_type, :line_1, :line_2, :line_3, :line_4, :city, :region, :post_code, :country, :attention_to
13
+
14
14
  def initialize(params = {})
15
15
  @errors ||= []
16
-
16
+
17
17
  params = {
18
18
  :address_type => "POBOX"
19
19
  }.merge(params)
20
-
20
+
21
21
  params.each do |k,v|
22
22
  self.send("#{k}=", v)
23
23
  end
24
24
  end
25
-
25
+
26
26
  # Validate the Address record according to what will be valid by the gateway.
27
27
  #
28
- # Usage:
28
+ # Usage:
29
29
  # address.valid? # Returns true/false
30
- #
30
+ #
31
31
  # Additionally sets address.errors array to an array of field/error.
32
32
  def valid?
33
33
  @errors = []
34
-
34
+
35
35
  if address_type && !ADDRESS_TYPE[address_type]
36
36
  @errors << ['address_type', "must be one of #{ADDRESS_TYPE.keys.join('/')} and is currently #{address_type}"]
37
37
  end
38
-
38
+
39
39
  @errors.size == 0
40
- end
41
-
40
+ end
41
+
42
42
  def to_xml(b = Builder::XmlMarkup.new)
43
43
  b.Address {
44
44
  b.AddressType address_type
@@ -50,44 +50,46 @@ module XeroGateway
50
50
  b.Region region if region
51
51
  b.PostalCode post_code if post_code
52
52
  b.Country country if country
53
+ b.AttentionTo attention_to if attention_to
53
54
  }
54
55
  end
55
-
56
+
56
57
  def self.from_xml(address_element)
57
58
  address = Address.new
58
59
  address_element.children.each do |element|
59
60
  case(element.name)
60
- when "AddressType" then address.address_type = element.text
61
+ when "AddressType" then address.address_type = element.text
61
62
  when "AddressLine1" then address.line_1 = element.text
62
63
  when "AddressLine2" then address.line_2 = element.text
63
64
  when "AddressLine3" then address.line_3 = element.text
64
- when "AddressLine4" then address.line_4 = element.text
65
- when "City" then address.city = element.text
66
- when "Region" then address.region = element.text
67
- when "PostalCode" then address.post_code = element.text
68
- when "Country" then address.country = element.text
65
+ when "AddressLine4" then address.line_4 = element.text
66
+ when "City" then address.city = element.text
67
+ when "Region" then address.region = element.text
68
+ when "PostalCode" then address.post_code = element.text
69
+ when "Country" then address.country = element.text
70
+ when "AttentionTo" then address.attention_to = element.text
69
71
  end
70
72
  end
71
73
  address
72
74
  end
73
-
75
+
74
76
  def self.parse(string)
75
77
  address = Address.new
76
-
78
+
77
79
  parts = string.split("\r\n")
78
-
80
+
79
81
  if(parts.size > 3)
80
82
  parts = [parts.shift, parts.shift, parts.shift, parts.join(", ")]
81
83
  end
82
-
84
+
83
85
  parts.each_with_index do |line, index|
84
86
  address.send("line_#{index+1}=", line)
85
87
  end
86
88
  address
87
89
  end
88
-
90
+
89
91
  def ==(other)
90
- [:address_type, :line_1, :line_2, :line_3, :line_4, :city, :region, :post_code, :country].each do |field|
92
+ [:address_type, :line_1, :line_2, :line_3, :line_4, :city, :region, :post_code, :country, :attention_to].each do |field|
91
93
  return false if send(field) != other.send(field)
92
94
  end
93
95
  return true
@@ -25,7 +25,7 @@ module XeroGateway
25
25
  attr_accessor :line_items_downloaded
26
26
 
27
27
  # accessible fields
28
- attr_accessor :bank_transaction_id, :type, :date, :reference, :status, :contact, :line_items, :bank_account, :url, :is_reconciled
28
+ attr_accessor :bank_transaction_id, :type, :date, :reference, :status, :contact, :line_items, :bank_account, :url, :is_reconciled, :updated_at
29
29
 
30
30
  def initialize(params = {})
31
31
  @errors ||= []
@@ -98,6 +98,12 @@ module XeroGateway
98
98
  @line_items_downloaded
99
99
  end
100
100
 
101
+ %w(sub_total tax_total total).each do |line_item_total_type|
102
+ define_method("#{line_item_total_type}=") do |new_total|
103
+ instance_variable_set("@#{line_item_total_type}", new_total) unless line_items_downloaded?
104
+ end
105
+ end
106
+
101
107
  # If line items are not downloaded, then attempt a download now (if this record was found to begin with).
102
108
  def line_items
103
109
  if line_items_downloaded?
@@ -146,6 +152,7 @@ module XeroGateway
146
152
  bank_transaction_element.children.each do |element|
147
153
  case(element.name)
148
154
  when "BankTransactionID" then bank_transaction.bank_transaction_id = element.text
155
+ when "UpdatedDateUTC" then bank_transaction.updated_at = parse_date_time(element.text)
149
156
  when "Type" then bank_transaction.type = element.text
150
157
  # when "CurrencyCode" then invoice.currency_code = element.text
151
158
  when "Contact" then bank_transaction.contact = Contact.from_xml(element)
@@ -154,8 +161,9 @@ module XeroGateway
154
161
  when "Status" then bank_transaction.status = element.text
155
162
  when "Reference" then bank_transaction.reference = element.text
156
163
  when "LineItems" then element.children.each {|line_item| bank_transaction.line_items_downloaded = true; bank_transaction.line_items << LineItem.from_xml(line_item) }
157
- # when "SubTotal" then invoice.sub_total = BigDecimal.new(element.text)
158
- # when "TotalTax" then invoice.total_tax = BigDecimal.new(element.text)
164
+ when "Total" then bank_transaction.total = BigDecimal.new(element.text)
165
+ when "SubTotal" then bank_transaction.sub_total = BigDecimal.new(element.text)
166
+ when "TotalTax" then bank_transaction.total_tax = BigDecimal.new(element.text)
159
167
  # when "Total" then invoice.total = BigDecimal.new(element.text)
160
168
  # when "InvoiceID" then invoice.invoice_id = element.text
161
169
  # when "InvoiceNumber" then invoice.invoice_number = element.text