avalara 0.0.1

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.
Files changed (47) hide show
  1. data/.gitignore +1 -0
  2. data/.rvmrc +1 -0
  3. data/CHANGELOG +5 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +71 -0
  6. data/LICENSE +20 -0
  7. data/README +5 -0
  8. data/avalara.gemspec +28 -0
  9. data/lib/avalara.rb +101 -0
  10. data/lib/avalara/api.rb +23 -0
  11. data/lib/avalara/configuration.rb +29 -0
  12. data/lib/avalara/errors.rb +8 -0
  13. data/lib/avalara/parser.rb +41 -0
  14. data/lib/avalara/request.rb +11 -0
  15. data/lib/avalara/request/address.rb +19 -0
  16. data/lib/avalara/request/detail_level.rb +13 -0
  17. data/lib/avalara/request/invoice.rb +46 -0
  18. data/lib/avalara/request/line.rb +21 -0
  19. data/lib/avalara/response.rb +11 -0
  20. data/lib/avalara/response/invoice.rb +48 -0
  21. data/lib/avalara/response/message.rb +13 -0
  22. data/lib/avalara/response/tax_address.rb +16 -0
  23. data/lib/avalara/response/tax_detail.rb +16 -0
  24. data/lib/avalara/response/tax_line.rb +25 -0
  25. data/lib/avalara/types.rb +8 -0
  26. data/lib/avalara/types/date.rb +10 -0
  27. data/lib/avalara/types/stash.rb +28 -0
  28. data/lib/avalara/version.rb +5 -0
  29. data/spec/avalara.yml.example +2 -0
  30. data/spec/factories/addresses.rb +13 -0
  31. data/spec/factories/detail_levels.rb +7 -0
  32. data/spec/factories/invoices.rb +17 -0
  33. data/spec/factories/lines.rb +14 -0
  34. data/spec/fixtures/net/geographical_tax_no_sales.yml +95 -0
  35. data/spec/fixtures/net/get_tax/failure.yml +68 -0
  36. data/spec/fixtures/net/get_tax/success.yml +106 -0
  37. data/spec/models/avalara/configuration_spec.rb +55 -0
  38. data/spec/models/avalara/request/address_spec.rb +24 -0
  39. data/spec/models/avalara/request/detail_level_spec.rb +18 -0
  40. data/spec/models/avalara/request/invoice_spec.rb +33 -0
  41. data/spec/models/avalara/request/line_spec.rb +25 -0
  42. data/spec/models/avalara_spec.rb +204 -0
  43. data/spec/spec_helper.rb +17 -0
  44. data/spec/support/avalara.rb +38 -0
  45. data/spec/support/factory_girl.rb +20 -0
  46. data/spec/support/vcr.rb +14 -0
  47. metadata +204 -0
@@ -0,0 +1,106 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :post
5
+ uri: https://%{API_USERNAME}:%{API_PASSWORD}@rest.avalara.net:443/1.0/tax/get
6
+ body: |-
7
+ {
8
+ "CustomerCode": 1,
9
+ "DocDate": "2012-01-01",
10
+ "CompanyCode": 83,
11
+ "Lines": [
12
+ {
13
+ "LineNo": "1",
14
+ "DestinationCode": "1",
15
+ "OriginCode": "1",
16
+ "Qty": "1",
17
+ "Amount": 10
18
+ }
19
+ ],
20
+ "Addresses": [
21
+ {
22
+ "AddressCode": "1",
23
+ "Line1": "435 Ericksen Avenue Northeast",
24
+ "Line2": "#250",
25
+ "PostalCode": "98110"
26
+ }
27
+ ]
28
+ }
29
+ headers:
30
+ date:
31
+ - Thu, 02 Feb 2012 15:20:51 GMT
32
+ user-agent:
33
+ - avalara/0.0.1 (Rubygems; Ruby 1.9.2 x86_64-darwin11.0.0)
34
+ content-length:
35
+ - "373"
36
+ response: !ruby/struct:VCR::Response
37
+ status: !ruby/struct:VCR::ResponseStatus
38
+ code: 200
39
+ message: OK
40
+ headers:
41
+ cache-control:
42
+ - private
43
+ content-type:
44
+ - text/json; charset=utf-8
45
+ server:
46
+ - Microsoft-IIS/7.5
47
+ x-aspnet-version:
48
+ - 4.0.30319
49
+ x-powered-by:
50
+ - ASP.NET
51
+ date:
52
+ - Thu, 02 Feb 2012 15:20:47 GMT
53
+ content-length:
54
+ - "885"
55
+ body: |
56
+ {
57
+ "DocCode": "1402892f-c3f3-4786-b976-622a0c622cf4",
58
+ "DocDate": "2012-01-01",
59
+ "Timestamp": "2012-02-02T15:20:47.5076773Z",
60
+ "TotalAmount": "10",
61
+ "TotalDiscount": "0",
62
+ "TotalExemption": "10",
63
+ "TotalTaxable": "0",
64
+ "TotalTax": "0",
65
+ "TotalTaxCalculated": "0",
66
+ "TaxDate": "2012-01-01",
67
+ "TaxLines": [
68
+ {
69
+ "LineNo": "1",
70
+ "TaxCode": "P0000000",
71
+ "Taxability": "true",
72
+ "Taxable": "0",
73
+ "Rate": "0",
74
+ "Tax": "0",
75
+ "Discount": "0",
76
+ "TaxCalculated": "0",
77
+ "Exemption": "10",
78
+ "TaxDetails": [
79
+ {
80
+ "Country": "US",
81
+ "Region": "WA",
82
+ "JurisType": "State",
83
+ "Taxable": "0",
84
+ "Rate": "0",
85
+ "Tax": "0",
86
+ "JurisName": "WASHINGTON",
87
+ "TaxName": "WA STATE TAX"}
88
+ ]
89
+ }
90
+ ]
91
+ ,
92
+ "TaxAddresses": [
93
+ {
94
+ "Address": "435 ERICKSEN AVE NE STE 250",
95
+ "AddressCode": "1",
96
+ "City": "BAINBRIDGE ISLAND",
97
+ "Country": "US",
98
+ "PostalCode": "98110-2876",
99
+ "Region": "WA",
100
+ "TaxRegionId": "2109716",
101
+ "JurisCode": "5303503736"}
102
+ ]
103
+ ,
104
+ "ResultCode": "Success"}
105
+
106
+ http_version: "1.1"
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Avalara::Configuration do
6
+ let(:configuration) { Avalara::Configuration.new }
7
+
8
+ context '#endpoint' do
9
+ it 'defaults to https://rest.avalara.net' do
10
+ configuration.endpoint.should == 'https://rest.avalara.net'
11
+ end
12
+
13
+ it 'may be overridden' do
14
+ expect {
15
+ configuration.endpoint = 'https://example.local/'
16
+ }.to change(configuration, :endpoint).to('https://example.local/')
17
+ end
18
+ end
19
+
20
+ context '#version' do
21
+ it 'defaults to 1.0' do
22
+ configuration.version.should == '1.0'
23
+ end
24
+
25
+ it 'may be overridden' do
26
+ expect {
27
+ configuration.version = '2.0'
28
+ }.to change(configuration, :version).to('2.0')
29
+ end
30
+ end
31
+
32
+ context '#username' do
33
+ it 'is unset by default' do
34
+ configuration.username.should be_nil
35
+ end
36
+
37
+ it 'may be set' do
38
+ expect {
39
+ configuration.username = 'abcdefg'
40
+ }.to change(configuration, :username).to('abcdefg')
41
+ end
42
+ end
43
+
44
+ context '#password' do
45
+ it 'is unset by default' do
46
+ configuration.password.should be_nil
47
+ end
48
+
49
+ it 'may be set' do
50
+ expect {
51
+ configuration.password = 'abcdefg'
52
+ }.to change(configuration, :password).to('abcdefg')
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Avalara::Request::Address do
6
+ let(:params) { Factory.attributes_for(:address) }
7
+ let(:address) { Factory.build_via_new(:address) }
8
+
9
+ context 'sets all attributes' do
10
+ subject { address }
11
+
12
+ its(:AddressCode) { should == params[:address_code] }
13
+ its(:Line1) { should == params[:line_1] }
14
+ its(:Line2) { should == params[:line_2] }
15
+ its(:Line3) { should == params[:line_3] }
16
+ its(:City) { should == params[:city] }
17
+ its(:Region) { should == params[:region] }
18
+ its(:Country) { should == params[:country] }
19
+ its(:PostalCode) { should == params[:postal_code] }
20
+ its(:Latitude) { should == params[:latitude] }
21
+ its(:Longitude) { should == params[:longitude] }
22
+ its(:TaxRegionId) { should == params[:tax_region_id] }
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Avalara::Request::DetailLevel do
6
+ let(:params) { Factory.attributes_for(:detail_level) }
7
+ let(:detail_level) { Factory.build_via_new(:detail_level) }
8
+
9
+ context 'sets all attributes' do
10
+ subject { detail_level }
11
+
12
+ its(:Line) { should == params[:line] }
13
+ its(:Summary) { should == params[:summary] }
14
+ its(:Document) { should == params[:document] }
15
+ its(:Tax) { should == params[:tax] }
16
+ its(:Diagnostic) { should == params[:diagnostic] }
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Avalara::Request::Invoice do
6
+ let(:params) { Factory.attributes_for(:invoice) }
7
+ let(:invoice) { Factory.build_via_new(:invoice) }
8
+
9
+ context 'sets all attributes' do
10
+ subject { invoice }
11
+
12
+ its(:CustomerCode) { should == params[:customer_code] }
13
+ its(:DocDate) { should == Avalara::Types::Date.coerce(params[:doc_date]) }
14
+ its(:CompanyCode) { should == params[:company_code] }
15
+ its(:Commit) { should == params[:commit] }
16
+ its(:CustomerUsageType) { should == params[:customer_usage_type] }
17
+ its(:Discount) { should == params[:discount] }
18
+ its(:DocCode) { should == params[:doc_code] }
19
+ its(:PurchaseOrderNo) { should == params[:purchase_order_no] }
20
+ its(:ExemptionNo) { should == params[:exemption_no] }
21
+ its(:DetailLevel) { should == params[:detail_level] }
22
+ its(:DocType) { should == params[:doc_type] }
23
+ its(:PaymentDate) { should == params[:payment_date] }
24
+ its(:Lines) { should == params[:lines] }
25
+ its(:Addresses) { should == params[:addresses] }
26
+ its(:ReferenceCode) { should == params[:reference_code] }
27
+ end
28
+
29
+ context 'converts nested objects to json' do
30
+ subject { invoice.to_json }
31
+ it { should_not be_nil }
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Avalara::Request::Line do
6
+ let(:params) { Factory.attributes_for(:line) }
7
+ let(:line) { Factory.build_via_new(:line) }
8
+
9
+ context 'sets all attributes' do
10
+ subject { line }
11
+
12
+ its(:LineNo) { should == params[:line_no] }
13
+ its(:DestinationCode) { should == params[:destination_code] }
14
+ its(:OriginCode) { should == params[:origin_code] }
15
+ its(:ItemCode) { should == params[:item_code] }
16
+ its(:TaxCode) { should == params[:tax_code] }
17
+ its(:CustomerUsageType) { should == params[:customer_usage_type] }
18
+ its(:Qty) { should == params[:qty] }
19
+ its(:Amount) { should == params[:amount] }
20
+ its(:Discounted) { should == params[:discounted] }
21
+ its(:TaxIncluded) { should == params[:tax_included] }
22
+ its(:Ref1) { should == params[:ref_1] }
23
+ its(:Ref2) { should == params[:ref_2] }
24
+ end
25
+ end
@@ -0,0 +1,204 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Avalara do
6
+ maintain_contactology_configuration
7
+
8
+ let(:configuration) { Avalara.configuration }
9
+
10
+ describe '.configuration' do
11
+ it 'yields a Avalara::Configuration instance' do
12
+ Avalara.configuration do |yielded|
13
+ yielded.should be_kind_of Avalara::Configuration
14
+ end
15
+ end
16
+
17
+ it 'yields the same configuration instance across multiple calls' do
18
+ Avalara.configuration do |config|
19
+ Avalara.configuration do |config2|
20
+ config.object_id.should == config2.object_id
21
+ end
22
+ end
23
+ end
24
+
25
+ it 'returns the configuration when queried' do
26
+ Avalara.configuration do |config|
27
+ Avalara.configuration.object_id.should == config.object_id
28
+ end
29
+ end
30
+
31
+ it 'may be explicitly overridden' do
32
+ configuration = Avalara::Configuration.new
33
+ expect {
34
+ Avalara.configuration = configuration
35
+ }.to change(Avalara, :configuration).to(configuration)
36
+ end
37
+
38
+ it 'raises an ArgumentError when set to a non-Configuration object' do
39
+ expect {
40
+ Avalara.configuration = 'bad'
41
+ }.to raise_error(ArgumentError)
42
+ end
43
+ end
44
+
45
+ describe '.endpoint' do
46
+ it 'returns the configuration endpoint' do
47
+ Avalara.endpoint.should == configuration.endpoint
48
+ end
49
+
50
+ it 'overrides the configuration endpoint' do
51
+ expect {
52
+ Avalara.endpoint = 'https://example.local/'
53
+ }.to change(configuration, :endpoint).to('https://example.local/')
54
+ end
55
+ end
56
+
57
+ describe '.username' do
58
+ it 'returns the configuration username' do
59
+ configuration.username = 'username'
60
+ Avalara.username.should == configuration.username
61
+ end
62
+
63
+ it 'overrides the configuration username' do
64
+ expect {
65
+ Avalara.username = 'username'
66
+ }.to change(configuration, :username).to('username')
67
+ end
68
+ end
69
+
70
+ describe '.password' do
71
+ it 'returns the configuration password' do
72
+ configuration.password = 'password'
73
+ Avalara.password.should == configuration.password
74
+ end
75
+
76
+ it 'overrides the configuration password' do
77
+ expect {
78
+ Avalara.password = 'password'
79
+ }.to change(configuration, :password).to('password')
80
+ end
81
+ end
82
+
83
+ describe '.version' do
84
+ it 'returns the configuration version' do
85
+ configuration.version = 'version'
86
+ Avalara.version.should == configuration.version
87
+ end
88
+
89
+ it 'overrides the configuration version' do
90
+ expect {
91
+ Avalara.version = 'version'
92
+ }.to change(configuration, :version).to('version')
93
+ end
94
+ end
95
+
96
+ describe '.get_tax' do
97
+ let(:doc_date) { Date.parse("January 1, 2012") }
98
+ let(:invoice) { Factory.build_via_new(:invoice, doc_date: doc_date) }
99
+ let(:request) { Avalara.get_tax(invoice) }
100
+ subject { request }
101
+
102
+ context 'failure' do
103
+ let(:invoice) { Factory.build_via_new(:invoice, customer_code: nil) }
104
+ use_vcr_cassette 'get_tax/failure'
105
+
106
+ it 'rasises an error' do
107
+ expect { subject }.to raise_error(Avalara::ApiError)
108
+ end
109
+
110
+ context 'the returned error' do
111
+ subject do
112
+ begin
113
+ request
114
+ rescue Avalara::ApiError => e
115
+ e.message.messages.first
116
+ end
117
+ end
118
+
119
+ its(:details) { should == "This value must be specified." }
120
+ its(:refers_to) { should == "CustomerCode" }
121
+ its(:severity) { should == "Error" }
122
+ its(:source) { should == "Avalara.AvaTax.Services" }
123
+ its(:summary) { should == "CustomerCode is required." }
124
+ end
125
+ end
126
+
127
+ context 'on timeout' do
128
+ it 'raises an avalara timeout error' do
129
+ Avalara::API.should_receive(:post).and_raise(Timeout::Error)
130
+ expect { subject }.to raise_error(Avalara::TimeoutError)
131
+ end
132
+ end
133
+
134
+ context 'success' do
135
+ use_vcr_cassette 'get_tax/success'
136
+
137
+ it { should be_kind_of Avalara::Response::Invoice }
138
+
139
+ its(:doc_code) { should_not be_nil }
140
+ its(:doc_date) { should == "2012-01-01" }
141
+ its(:result_code) { should == "Success" }
142
+ its(:tax_date) { should_not be_nil }
143
+ its(:timestamp) { should_not be_nil }
144
+ its(:total_amount) { should == "10" }
145
+ its(:total_discount) { should == "0" }
146
+ its(:total_exemption) { should == "10" }
147
+ its(:total_tax) { should == "0" }
148
+ its(:total_tax_calculated) { should == "0" }
149
+
150
+ it 'returns 1 tax line' do
151
+ subject.tax_lines.length.should == 1
152
+ end
153
+
154
+ it 'returns 1 tax address' do
155
+ subject.tax_addresses.length.should == 1
156
+ end
157
+
158
+ context 'the returned tax line' do
159
+ let(:tax_line) { request.tax_lines.first }
160
+ subject { tax_line }
161
+
162
+ its(:line_no) { should == "1" }
163
+ its(:tax_code) { should == "P0000000" }
164
+ its(:taxability) { should == "true" }
165
+ its(:taxable) { should == "0" }
166
+ its(:rate) { should == "0" }
167
+ its(:tax) { should == "0" }
168
+ its(:discount) { should == "0" }
169
+ its(:tax_calculated) { should == "0" }
170
+ its(:exemption) { should == "10" }
171
+
172
+ it 'returns 1 tax detail' do
173
+ subject.tax_details.length.should == 1
174
+ end
175
+
176
+ context 'the returned tax detail' do
177
+ subject { tax_line.tax_details.first }
178
+
179
+ its(:taxable) { should == "0" }
180
+ its(:rate) { should == "0" }
181
+ its(:tax) { should == "0" }
182
+ its(:region) { should == "WA" }
183
+ its(:country) { should == "US" }
184
+ its(:juris_type) { should == "State" }
185
+ its(:juris_name) { should == "WASHINGTON" }
186
+ its(:tax_name) { should == "WA STATE TAX" }
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ describe '.geographical_tax' do
193
+ let(:latitude) { '47.627935' }
194
+ let(:longitude) { '-122.51702' }
195
+ let(:sales_amount) { 100 }
196
+
197
+ subject { Avalara.geographical_tax(latitude, longitude, sales_amount) }
198
+ use_vcr_cassette 'geographical_tax_no_sales'
199
+
200
+ it "returns a rate of 0" do
201
+ expect { subject }.to raise_error(Avalara::NotImplementedError)
202
+ end
203
+ end
204
+ end