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.
- data/.gitignore +1 -0
- data/.rvmrc +1 -0
- data/CHANGELOG +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +71 -0
- data/LICENSE +20 -0
- data/README +5 -0
- data/avalara.gemspec +28 -0
- data/lib/avalara.rb +101 -0
- data/lib/avalara/api.rb +23 -0
- data/lib/avalara/configuration.rb +29 -0
- data/lib/avalara/errors.rb +8 -0
- data/lib/avalara/parser.rb +41 -0
- data/lib/avalara/request.rb +11 -0
- data/lib/avalara/request/address.rb +19 -0
- data/lib/avalara/request/detail_level.rb +13 -0
- data/lib/avalara/request/invoice.rb +46 -0
- data/lib/avalara/request/line.rb +21 -0
- data/lib/avalara/response.rb +11 -0
- data/lib/avalara/response/invoice.rb +48 -0
- data/lib/avalara/response/message.rb +13 -0
- data/lib/avalara/response/tax_address.rb +16 -0
- data/lib/avalara/response/tax_detail.rb +16 -0
- data/lib/avalara/response/tax_line.rb +25 -0
- data/lib/avalara/types.rb +8 -0
- data/lib/avalara/types/date.rb +10 -0
- data/lib/avalara/types/stash.rb +28 -0
- data/lib/avalara/version.rb +5 -0
- data/spec/avalara.yml.example +2 -0
- data/spec/factories/addresses.rb +13 -0
- data/spec/factories/detail_levels.rb +7 -0
- data/spec/factories/invoices.rb +17 -0
- data/spec/factories/lines.rb +14 -0
- data/spec/fixtures/net/geographical_tax_no_sales.yml +95 -0
- data/spec/fixtures/net/get_tax/failure.yml +68 -0
- data/spec/fixtures/net/get_tax/success.yml +106 -0
- data/spec/models/avalara/configuration_spec.rb +55 -0
- data/spec/models/avalara/request/address_spec.rb +24 -0
- data/spec/models/avalara/request/detail_level_spec.rb +18 -0
- data/spec/models/avalara/request/invoice_spec.rb +33 -0
- data/spec/models/avalara/request/line_spec.rb +25 -0
- data/spec/models/avalara_spec.rb +204 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/avalara.rb +38 -0
- data/spec/support/factory_girl.rb +20 -0
- data/spec/support/vcr.rb +14 -0
- 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
|