suretax 0.1.0

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 (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
+