suretax 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.travis.yml +14 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +74 -0
  6. data/Gemfile.travis +12 -0
  7. data/LICENSE.txt +22 -0
  8. data/NOTES.md +3 -0
  9. data/README.md +58 -0
  10. data/Rakefile +1 -0
  11. data/circle.yml +7 -0
  12. data/lib/suretax.rb +24 -0
  13. data/lib/suretax/api.rb +7 -0
  14. data/lib/suretax/api/cancel_request.rb +64 -0
  15. data/lib/suretax/api/group.rb +16 -0
  16. data/lib/suretax/api/item_message.rb +13 -0
  17. data/lib/suretax/api/request.rb +133 -0
  18. data/lib/suretax/api/request_item.rb +84 -0
  19. data/lib/suretax/api/response.rb +53 -0
  20. data/lib/suretax/api/tax.rb +25 -0
  21. data/lib/suretax/api/tax_amount.rb +46 -0
  22. data/lib/suretax/concerns.rb +7 -0
  23. data/lib/suretax/concerns/validatable.rb +208 -0
  24. data/lib/suretax/configuration.rb +84 -0
  25. data/lib/suretax/connection.rb +48 -0
  26. data/lib/suretax/constants.rb +7 -0
  27. data/lib/suretax/constants/regulatory_codes.rb +11 -0
  28. data/lib/suretax/constants/response_groups.rb +8 -0
  29. data/lib/suretax/constants/sales_type_codes.rb +8 -0
  30. data/lib/suretax/constants/tax_situs_codes.rb +12 -0
  31. data/lib/suretax/constants/transaction_type_codes.rb +505 -0
  32. data/lib/suretax/response.rb +70 -0
  33. data/lib/suretax/version.rb +3 -0
  34. data/spec/lib/suretax/api/group_spec.rb +50 -0
  35. data/spec/lib/suretax/api/request_item_spec.rb +54 -0
  36. data/spec/lib/suretax/api/request_item_validations_spec.rb +237 -0
  37. data/spec/lib/suretax/api/request_spec.rb +197 -0
  38. data/spec/lib/suretax/api/request_validations_spec.rb +384 -0
  39. data/spec/lib/suretax/api/response_spec.rb +165 -0
  40. data/spec/lib/suretax/api/tax_amount_spec.rb +37 -0
  41. data/spec/lib/suretax/api/tax_spec.rb +59 -0
  42. data/spec/lib/suretax/configuration_spec.rb +97 -0
  43. data/spec/lib/suretax/connection_spec.rb +77 -0
  44. data/spec/lib/suretax/response_spec.rb +136 -0
  45. data/spec/spec_helper.rb +45 -0
  46. data/spec/support/cancellation_helper.rb +31 -0
  47. data/spec/support/connection_shared_examples.rb +37 -0
  48. data/spec/support/request_helper.rb +309 -0
  49. data/spec/support/suretax_helper.rb +27 -0
  50. data/spec/support/validations_shared_examples.rb +28 -0
  51. data/suretax.gemspec +33 -0
  52. metadata +281 -0
@@ -0,0 +1,70 @@
1
+ require 'json'
2
+
3
+ module Suretax
4
+
5
+ class Response
6
+ attr_reader :response, :body, :status, :api
7
+
8
+ def initialize(api_response)
9
+ @response = api_response
10
+ sanitized_body = remove_xml_brackets(api_response.body)
11
+ if sanitized_body == 'invalid request'
12
+ @body = sanitized_body
13
+ @status = 400
14
+ @api = nil
15
+ log_response
16
+
17
+ return self
18
+ end
19
+ @body = JSON.parse(sanitized_body)
20
+ @api = Api::Response.new(@body)
21
+ @status = map_response_code_to_http_status(api.status)
22
+
23
+ log_response
24
+ end
25
+
26
+ def success?
27
+ status == 200
28
+ end
29
+
30
+ private
31
+
32
+ def extract_json_from_urlencoded_string_regex
33
+ # http://rubular.com/r/0w73fy4Ldk
34
+ /\A<\?xml.*<string[^>]+>(?<json_string>.+)<\/string>\z/m
35
+ end
36
+
37
+ def remove_xml_brackets(response_string)
38
+ if matches = response_string.match(extract_json_from_urlencoded_string_regex)
39
+ matches['json_string']
40
+ else
41
+ response_string
42
+ end
43
+ end
44
+
45
+ def map_response_code_to_http_status(api_status_code)
46
+ case api_status_code
47
+ when '9999'
48
+ 200
49
+ when '9001'
50
+ 409
51
+ else
52
+ 400
53
+ end
54
+ end
55
+
56
+ def log_response
57
+ logger.debug "\nSureTax Response Received:\n#{@body.inspect}" if logger
58
+ end
59
+
60
+ def logger
61
+ configuration.logger
62
+ end
63
+
64
+ def configuration
65
+ Suretax.configuration
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,3 @@
1
+ module Suretax
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Suretax::Api::Group do
4
+
5
+ let(:group) { Suretax::Api::Group.new(group_hash) }
6
+
7
+ context 'with a v01 API response' do
8
+ let(:group_hash) { valid_test_response_body['GroupList'].first }
9
+
10
+ it 'should have a state' do
11
+ expect(group.state).to eql('CA')
12
+ end
13
+
14
+ it 'should have an invoice number' do
15
+ expect(group.invoice).to eql('1')
16
+ end
17
+
18
+ it 'should have a customer number' do
19
+ expect(group.customer).to eql('000000007')
20
+ end
21
+
22
+ it 'should have a list of taxes' do
23
+ expect(group.taxes.count).to eql(8)
24
+ end
25
+ end
26
+
27
+ context 'with a v03 API response' do
28
+ let(:group_hash) { valid_v03_response_body['GroupList'].first }
29
+
30
+ it 'should have a state' do
31
+ expect(group.state).to eql('CA')
32
+ end
33
+
34
+ it 'should have an invoice number' do
35
+ expect(group.invoice).to eql('1')
36
+ end
37
+
38
+ it 'should have a line number' do
39
+ expect(group.line).to eql('1')
40
+ end
41
+
42
+ it 'should have a customer number' do
43
+ expect(group.customer).to eql('000000007')
44
+ end
45
+
46
+ it 'should have a list of taxes' do
47
+ expect(group.taxes.count).to eql(8)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Suretax::Api::RequestItem do
4
+
5
+ let(:args) { suretax_valid_request_item_params }
6
+ let(:subject) { Suretax::Api::RequestItem.new(args) }
7
+
8
+ describe '#tax_exemption_codes' do
9
+
10
+ it 'should have two codes' do
11
+ expect(subject.tax_exemption_codes.count).to eql(2)
12
+ end
13
+ end
14
+
15
+ describe '#params' do
16
+ {
17
+ bill_to_number: "8585260000",
18
+ customer_number: "000000007",
19
+ invoice_number: "1",
20
+ line_number: "1",
21
+ orig_number: "8585260000",
22
+ p_to_p_plus_four: "",
23
+ p_to_p_zipcode: "",
24
+ plus_four: "",
25
+ regulatory_code: "99",
26
+ revenue: "40",
27
+ sales_type_code: "R",
28
+ seconds: "55",
29
+ tax_included_code: "0",
30
+ tax_situs_rule: "01",
31
+ term_number: "8585260000",
32
+ trans_date: "2013-12-01T00:00:00",
33
+ trans_type_code: "010101",
34
+ unit_type: "00",
35
+ units: "1",
36
+ zipcode: ""
37
+ }.each_pair do |key,value|
38
+ it "##{key} should return the correct value" do
39
+ subject.send("#{key.to_s}=",value)
40
+ expect(subject.send(key)).to eql(value)
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#params' do
46
+ let(:valid_params) do
47
+ valid_encoded_test_request_body['ItemList'].first
48
+ end
49
+
50
+ it 'should return a valid parameters hash' do
51
+ expect(subject.params).to eql(valid_params)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Suretax API Request Item Validations" do
4
+
5
+ let(:request_item) do
6
+ Suretax::Api::RequestItem.new(suretax_valid_request_item_params)
7
+ end
8
+
9
+ describe '#customer_number' do
10
+ it 'should not be valid when it is not a number' do
11
+ request_item.customer_number = ''
12
+
13
+ expect(request_item.errors.any?).to eq true
14
+ expect(request_item.errors.messages).to eq [%Q{Invalid customer_number: ''}]
15
+ end
16
+
17
+ it 'should not be valid when the length is wrong' do
18
+ request_item.customer_number = '1' * 11
19
+
20
+ expect(request_item.errors.any?).to eq true
21
+ expect(request_item.errors.messages).to eq [%Q{Invalid customer_number: #{'1' * 11}}]
22
+ end
23
+
24
+ it 'should not be valid when it is not a number' do
25
+ request_item.customer_number = 'a' * 9
26
+
27
+ expect(request_item.errors.any?).to eq true
28
+ expect(request_item.errors.messages).to eq [%Q{Invalid customer_number: #{'a' * 9}}]
29
+ end
30
+
31
+ it 'should be valid when it is a nine-digit number' do
32
+ expect(request_item.errors.any?).to eq false
33
+ end
34
+ end
35
+
36
+ describe '#invoice_number' do
37
+ %w{123 a1234 abcdef}.each do |value|
38
+ it "can be '#{value}'" do
39
+ request_item.invoice_number = value
40
+
41
+ expect(request_item.errors.any?).to eq false
42
+ end
43
+ end
44
+
45
+ it 'cannot be longer than 20 characters' do
46
+ request_item.invoice_number = '1' * 21
47
+
48
+ expect(request_item.errors.any?).to eq true
49
+ expect(request_item.errors.messages).to eq [%Q{Invalid invoice_number: #{'1' * 21}}]
50
+ end
51
+
52
+ it 'can be blank' do
53
+ request_item.invoice_number = nil
54
+
55
+ expect(request_item.errors.any?).to eq false
56
+ end
57
+ end
58
+
59
+ describe '#line_number' do
60
+ it 'must not be more than 20 characters in length' do
61
+ request_item.line_number = '1' * 21
62
+
63
+ expect(request_item.errors.any?).to eq true
64
+ expect(request_item.errors.messages).to eq [%Q{Invalid line_number: #{'1' * 21}}]
65
+ end
66
+
67
+ it 'can be blank' do
68
+ request_item.line_number = nil
69
+
70
+ expect(request_item.errors.any?).to eq false
71
+ end
72
+
73
+ %w{1 40 580 12345678901234567890}.each do |number|
74
+ it "can be '#{number}'" do
75
+ request_item.line_number = number
76
+
77
+ expect(request_item.errors.any?).to eq false
78
+ end
79
+ end
80
+
81
+ %w{a D0 S10 _}.each do |bad_content|
82
+ it "cannot be '#{bad_content}'" do
83
+ request_item.line_number = bad_content
84
+
85
+ expect(request_item.errors.any?).to eq true
86
+ expect(request_item.errors.messages).to eq [%Q{Invalid line_number: #{bad_content}}]
87
+ end
88
+ end
89
+ end
90
+
91
+ context 'phone number fields' do
92
+ %w{orig_number term_number bill_to_number}.each do |phone_method|
93
+
94
+ describe "##{phone_method}" do
95
+ it_should_behave_like 'optional phone number' do
96
+ let(:subject) { phone_method }
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+
103
+ describe '#tax_situs_rule' do
104
+ it 'must be present' do
105
+ request_item.tax_situs_rule = nil
106
+
107
+ expect(request_item.errors.any?).to eq true
108
+ expect(request_item.errors.messages).to eq [%Q{Invalid tax_situs_rule: nil}]
109
+ end
110
+
111
+ %w{01 02 03 04 05 06 07 14}.each do |number|
112
+ it "can be '#{number}'" do
113
+ request_item.tax_situs_rule = number
114
+
115
+ expect(request_item.errors.any?).to eq false
116
+ end
117
+ end
118
+
119
+ %w{a D0 S10 _}.each do |bad_content|
120
+ it "cannot be '#{bad_content}'" do
121
+ request_item.tax_situs_rule = bad_content
122
+
123
+ expect(request_item.errors.any?).to eq true
124
+ expect(request_item.errors.messages).to eq [%Q{Invalid tax_situs_rule: #{bad_content}}]
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '#trans_type_code' do
130
+ context 'when present' do
131
+ it 'should pass validation' do
132
+ request_item.trans_type_code = '010101'
133
+
134
+ expect(request_item.errors.any?).to eq false
135
+ end
136
+ end
137
+
138
+ context 'when absent' do
139
+ it 'should fail validation' do
140
+ request_item.trans_type_code = nil
141
+
142
+ expect(request_item.errors.any?).to eq true
143
+ expect(request_item.errors.messages).to eq [%Q{Invalid trans_type_code: nil}]
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#sales_type_code' do
149
+ context 'when present' do
150
+ it 'should allow valid codes' do
151
+ %w{R B I L}.each do |code|
152
+ request_item.sales_type_code = code
153
+
154
+ expect(request_item.errors.any?).to eq false
155
+ end
156
+ end
157
+
158
+ it 'should not allow invalid codes' do
159
+ %w{A X Y Z}.each do |code|
160
+ request_item.sales_type_code = code
161
+
162
+ expect(request_item.errors.any?).to eq true
163
+ expect(request_item.errors.messages).to eq [%Q{Invalid sales_type_code: #{code}}]
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'when absent' do
169
+ it 'should fail validation' do
170
+ request_item.sales_type_code = nil
171
+
172
+ expect(request_item.errors.any?).to eq true
173
+ expect(request_item.errors.messages).to eq [%Q{Invalid sales_type_code: nil}]
174
+ end
175
+ end
176
+ end
177
+
178
+ describe '#regulatory_code' do
179
+ context 'when present' do
180
+ it 'should allow valid codes' do
181
+ %w{00 01 02 03 04 05 99}.each do |code|
182
+ request_item.regulatory_code = code
183
+
184
+ expect(request_item.errors.any?).to eq false
185
+ end
186
+ end
187
+
188
+ it 'should not allow invalid codes' do
189
+ %w{44 77 4 f -}.each do |code|
190
+ request_item.regulatory_code = code
191
+
192
+ expect(request_item.errors.any?).to eq true
193
+ expect(request_item.errors.messages).to eq [%Q{Invalid regulatory_code: #{code}}]
194
+ end
195
+ end
196
+ end
197
+
198
+ context 'when absent' do
199
+ it 'should fail validation' do
200
+ request_item.regulatory_code = nil
201
+
202
+ expect(request_item.errors.any?).to eq true
203
+ expect(request_item.errors.messages).to eq [%Q{Invalid regulatory_code: nil}]
204
+ end
205
+ end
206
+ end
207
+
208
+ describe '#tax_exemption_codes' do
209
+ context 'when present' do
210
+ context 'and the list has content' do
211
+ it 'should pass validation' do
212
+ request_item.tax_exemption_codes = ['00']
213
+
214
+ expect(request_item.errors.any?).to eq false
215
+ end
216
+ end
217
+
218
+ context 'and the list is empty' do
219
+ it 'should fail validation' do
220
+ request_item.tax_exemption_codes = []
221
+
222
+ expect(request_item.errors.any?).to eq true
223
+ expect(request_item.errors.messages).to eq [%Q{Invalid tax_exemption_codes: []}]
224
+ end
225
+ end
226
+ end
227
+
228
+ context 'when absent' do
229
+ it 'should fail validation' do
230
+ request_item.tax_exemption_codes = nil
231
+
232
+ expect(request_item.errors.any?).to eq true
233
+ expect(request_item.errors.messages).to eq [%Q{Invalid tax_exemption_codes: nil}]
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+
3
+ describe Suretax::Api::Request do
4
+
5
+ let(:args) { suretax_valid_request_params }
6
+ let(:api_request) { Suretax::Api::Request.new(args) }
7
+ let(:valid_params) { valid_encoded_test_request_body }
8
+
9
+ describe 'accessors' do
10
+ {
11
+ business_unit: "testing",
12
+ client_number: suretax_client_number,
13
+ client_tracking: "track",
14
+ data_month: "7",
15
+ data_year: "2013",
16
+ industry_exemption: "",
17
+ response_group: "03",
18
+ response_type: "D6",
19
+ return_file_code: "0",
20
+ total_revenue: "40",
21
+ validation_key: suretax_key
22
+ }.each_pair do |key,value|
23
+
24
+ it "##{key} should return the correct value" do
25
+ api_request.send("#{key.to_s}=",value)
26
+ expect(api_request.send(key)).to eql(value)
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ context 'configuration' do
33
+ it 'should use Suretax.configuration by default' do
34
+ req = Suretax::Api::Request.new
35
+ expect(req.client_number).to eql(suretax_client_number)
36
+ end
37
+
38
+ it 'should allow the default configuration to be overridden' do
39
+ client_number = '1122334455'
40
+
41
+ req = Suretax::Api::Request.new({client_number: client_number})
42
+
43
+ expect(req.client_number).to eql(client_number)
44
+ end
45
+ end
46
+
47
+ context 'defaults' do
48
+ describe '#client_number' do
49
+ it 'should have :client_number set to the configuration default' do
50
+ req = Suretax::Api::Request.new
51
+ expect(req.client_number).to eql(suretax_client_number)
52
+ end
53
+
54
+ it 'should allow you to override the default' do
55
+ client_no = '9911991199'
56
+ req = Suretax::Api::Request.new({ client_number: client_no })
57
+ expect(req.client_number).to eql(client_no)
58
+ end
59
+ end
60
+
61
+ describe '#validation_key' do
62
+ it 'should have :validation_key set to the configuration default' do
63
+ req = Suretax::Api::Request.new
64
+ expect(req.validation_key).to eql(suretax_key)
65
+ end
66
+
67
+ it 'should allow you to override the default' do
68
+ key = '9911991199-abdce-000000000'
69
+ req = Suretax::Api::Request.new({ validation_key: key })
70
+ expect(req.validation_key).to eql(key)
71
+ end
72
+ end
73
+
74
+ describe '#return_file_code' do
75
+ it 'should have :return_file_code set to "0"' do
76
+ req = Suretax::Api::Request.new
77
+ expect(req.return_file_code).to eql('0')
78
+ end
79
+
80
+ it 'should allow you to override the default' do
81
+ req = Suretax::Api::Request.new({ return_file_code: 'Q' })
82
+ expect(req.return_file_code).to eql('Q')
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ describe '#params' do
89
+ it 'should return a valid parameters hash' do
90
+ expect(api_request.params).to eql(valid_params)
91
+ end
92
+ end
93
+
94
+ describe "#submit" do
95
+ subject{ api_request.submit }
96
+ let(:api_path) { suretax_post_path }
97
+
98
+ before(:each) do
99
+ stub_request(:post, "#{suretax_url}#{api_path}").to_return(
100
+ status: 200,
101
+ body: suretax_wrap_response(valid_test_response_body.to_json)
102
+ )
103
+ end
104
+
105
+ it "should return a Suretax::Api::Response" do
106
+ should be_a_kind_of(Suretax::Api::Response)
107
+ end
108
+
109
+ its('groups.size'){ should >= 1 }
110
+
111
+ it "should have taxes" do
112
+ subject.groups.map(&:taxes).should_not be_empty
113
+ end
114
+
115
+ it "should have a transaction number" do
116
+ subject.transaction.should_not be_nil
117
+ subject.transaction.size.should_not be_zero
118
+ end
119
+ end
120
+
121
+ describe "#rollback" do
122
+ subject{ api_request.rollback }
123
+
124
+ context "before submitting the request" do
125
+ before(:each){ api_request.send(:response).should be_nil }
126
+ it{ should be_nil }
127
+ end
128
+
129
+ context "after submitting the request" do
130
+ before(:each) do
131
+ stub_request(:post, "#{suretax_url}#{suretax_post_path}").to_return(
132
+ status: 200,
133
+ body: suretax_wrap_response(valid_test_response_body.to_json)
134
+ )
135
+ api_request.submit
136
+ end
137
+
138
+ it "should issue a cancel request" do
139
+ Suretax::Api::CancelRequest.should_receive(:new).
140
+ with(transaction: api_request.response.transaction,
141
+ client_number: api_request.client_number,
142
+ validation_key: api_request.validation_key,
143
+ client_tracking: api_request.client_tracking).
144
+ and_return( double(Suretax::Api::CancelRequest, submit: true) )
145
+ subject
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#data_month' do
151
+ subject { Suretax::Api::Request.new(options) }
152
+
153
+ context 'when the value is supplied' do
154
+ let(:options) { { data_month: '04' } }
155
+ its(:data_month) { should eql '04' }
156
+ end
157
+
158
+ context 'when the value is not supplied' do
159
+ let(:options) { { } }
160
+
161
+ context 'and Suretax is configured for production' do
162
+ before(:each) { Suretax.configuration.stub(:test?).and_return(false) }
163
+ its(:data_month) { should eql Date.today.strftime('%m') }
164
+ end
165
+
166
+ context 'and Suretax is not configured for production' do
167
+ before(:each) { Suretax.configuration.stub(:test?).and_return(true) }
168
+ its(:data_month) { should eql Date.today.prev_month.strftime('%m') }
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '#data_year' do
174
+ subject { Suretax::Api::Request.new(options) }
175
+
176
+ context 'when the value is supplied' do
177
+ let(:options) { { data_year: '2012' } }
178
+ its(:data_year) { should eql '2012' }
179
+ end
180
+
181
+ context 'when the value is not supplied' do
182
+ before(:each) { Date.stub(:today) { Date.new(2014, 01, 01) } }
183
+ let(:options) { { } }
184
+
185
+ context 'and Suretax is configured for production' do
186
+ before(:each) { Suretax.configuration.stub(:test?).and_return(false) }
187
+ its(:data_year) { should eql Date.today.strftime('%Y') }
188
+ end
189
+
190
+ context 'and Suretax is not configured for production' do
191
+ before(:each) { Suretax.configuration.stub(:test?).and_return(true) }
192
+ its(:data_year) { should eql Date.today.prev_month.strftime('%Y') }
193
+ end
194
+ end
195
+ end
196
+ end
197
+