corgibytes-tax_cloud 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +17 -0
- data/.circleci/config.yml +81 -0
- data/.circleci/setup-rubygems.sh +9 -0
- data/.gitignore +67 -0
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +66 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.rdoc +48 -0
- data/CONTRIBUTORS.txt +23 -0
- data/Dockerfile +9 -0
- data/Gemfile +22 -0
- data/LICENSE +23 -0
- data/README.md +190 -0
- data/Rakefile +29 -0
- data/docker-compose.yml +10 -0
- data/examples/.env.example +2 -0
- data/examples/lookup_example.rb +139 -0
- data/lib/config/locales/en.yml +34 -0
- data/lib/hash.rb +8 -0
- data/lib/savon_soap_xml.rb +32 -0
- data/lib/tasks/tax_cloud.rake +18 -0
- data/lib/tasks/tax_code_groups.rake +37 -0
- data/lib/tasks/tax_codes.rake +43 -0
- data/lib/tax_cloud.rb +70 -0
- data/lib/tax_cloud/address.rb +50 -0
- data/lib/tax_cloud/cart_item.rb +26 -0
- data/lib/tax_cloud/client.rb +60 -0
- data/lib/tax_cloud/configuration.rb +28 -0
- data/lib/tax_cloud/errors.rb +6 -0
- data/lib/tax_cloud/errors/api_error.rb +17 -0
- data/lib/tax_cloud/errors/missing_config_error.rb +13 -0
- data/lib/tax_cloud/errors/missing_config_option_error.rb +19 -0
- data/lib/tax_cloud/errors/soap_error.rb +30 -0
- data/lib/tax_cloud/errors/tax_cloud_error.rb +83 -0
- data/lib/tax_cloud/errors/unexpected_soap_response_error.rb +19 -0
- data/lib/tax_cloud/record.rb +14 -0
- data/lib/tax_cloud/responses.rb +13 -0
- data/lib/tax_cloud/responses/authorized.rb +10 -0
- data/lib/tax_cloud/responses/authorized_with_capture.rb +10 -0
- data/lib/tax_cloud/responses/base.rb +82 -0
- data/lib/tax_cloud/responses/captured.rb +10 -0
- data/lib/tax_cloud/responses/cart_item.rb +23 -0
- data/lib/tax_cloud/responses/generic.rb +31 -0
- data/lib/tax_cloud/responses/lookup.rb +38 -0
- data/lib/tax_cloud/responses/ping.rb +10 -0
- data/lib/tax_cloud/responses/returned.rb +10 -0
- data/lib/tax_cloud/responses/tax_code_groups.rb +30 -0
- data/lib/tax_cloud/responses/tax_codes.rb +30 -0
- data/lib/tax_cloud/responses/tax_codes_by_group.rb +30 -0
- data/lib/tax_cloud/responses/verify_address.rb +26 -0
- data/lib/tax_cloud/tax_code.rb +11 -0
- data/lib/tax_cloud/tax_code_constants.rb +560 -0
- data/lib/tax_cloud/tax_code_group.rb +28 -0
- data/lib/tax_cloud/tax_code_group_constants.rb +31 -0
- data/lib/tax_cloud/tax_code_groups.rb +25 -0
- data/lib/tax_cloud/tax_codes.rb +25 -0
- data/lib/tax_cloud/transaction.rb +118 -0
- data/lib/tax_cloud/version.rb +4 -0
- data/tax_cloud.gemspec +23 -0
- data/test/cassettes/authorized.yml +826 -0
- data/test/cassettes/authorized_with_capture.yml +826 -0
- data/test/cassettes/authorized_with_localized_time.yml +826 -0
- data/test/cassettes/captured.yml +872 -0
- data/test/cassettes/get_tic_groups.yml +783 -0
- data/test/cassettes/get_tics.yml +1079 -0
- data/test/cassettes/get_tics_by_group.yml +776 -0
- data/test/cassettes/invalid_soap_call.yml +778 -0
- data/test/cassettes/lookup.yml +780 -0
- data/test/cassettes/lookup_ny.yml +780 -0
- data/test/cassettes/ping.yml +776 -0
- data/test/cassettes/ping_with_invalid_credentials.yml +774 -0
- data/test/cassettes/ping_with_invalid_response.yml +774 -0
- data/test/cassettes/returned.yml +872 -0
- data/test/cassettes/verify_bad_address.yml +772 -0
- data/test/cassettes/verify_good_address.yml +772 -0
- data/test/helper.rb +17 -0
- data/test/test_address.rb +54 -0
- data/test/test_cart_item.rb +15 -0
- data/test/test_client.rb +27 -0
- data/test/test_configuration_optional_keys.rb +42 -0
- data/test/test_configuration_required_keys.rb +31 -0
- data/test/test_lookup_response.rb +20 -0
- data/test/test_setup.rb +17 -0
- data/test/test_soap.rb +11 -0
- data/test/test_tax_code_groups.rb +29 -0
- data/test/test_tax_codes.rb +17 -0
- data/test/test_transaction.rb +78 -0
- data/test/test_transaction_ny.rb +25 -0
- data/test/vcr_setup.rb +9 -0
- metadata +174 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module TaxCloud #:nodoc:
|
2
|
+
# A <tt>CartItem</tt> defines a single line item for the purchase. Used to calculate the tax amount.
|
3
|
+
class CartItem < Record
|
4
|
+
# The unique index number for the line item. Must be unique for the scope of the cart.
|
5
|
+
attr_accessor :index
|
6
|
+
# The stock keeping unit (SKU) number.
|
7
|
+
attr_accessor :item_id
|
8
|
+
# The taxable information code. See TaxCloud::TaxCodes.
|
9
|
+
attr_accessor :tic
|
10
|
+
# The price of the item. All prices are USD. Do not include currency symbol.
|
11
|
+
attr_accessor :price
|
12
|
+
# The total number of items.
|
13
|
+
attr_accessor :quantity
|
14
|
+
|
15
|
+
# Convert the object to a usable hash for SOAP requests.
|
16
|
+
def to_hash
|
17
|
+
{
|
18
|
+
'Index' => index,
|
19
|
+
'ItemID' => item_id,
|
20
|
+
'TIC' => tic,
|
21
|
+
'Price' => price,
|
22
|
+
'Qty' => quantity
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module TaxCloud #:nodoc:
|
2
|
+
# A <tt>Client</tt> communicates with the TaxCloud service.
|
3
|
+
class Client < Savon::Client
|
4
|
+
# Create a new client.
|
5
|
+
def initialize
|
6
|
+
super TaxCloud::WSDL_URL
|
7
|
+
|
8
|
+
if client_params.key?(:read_timeout)
|
9
|
+
http.read_timeout = client_params[:read_timeout]
|
10
|
+
end
|
11
|
+
|
12
|
+
if client_params.key?(:open_timeout)
|
13
|
+
http.open_timeout = client_params[:open_timeout]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Make a safe SOAP call.
|
18
|
+
# Will raise a TaxCloud::Errors::SoapError on error.
|
19
|
+
#
|
20
|
+
# === Parameters
|
21
|
+
# [method] SOAP method.
|
22
|
+
# [body] Body content.
|
23
|
+
def request(method, body = {})
|
24
|
+
safe do
|
25
|
+
super method, :body => body.merge(auth_params)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Ping the TaxCloud service.
|
30
|
+
#
|
31
|
+
# Returns "OK" or raises an error if the TaxCloud service is unreachable.
|
32
|
+
def ping
|
33
|
+
TaxCloud::Responses::Ping.parse request(:ping)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Authorization hash to use with all SOAP requests
|
39
|
+
def auth_params
|
40
|
+
return {} unless TaxCloud.configuration
|
41
|
+
{
|
42
|
+
'apiLoginID' => TaxCloud.configuration.api_login_id,
|
43
|
+
'apiKey' => TaxCloud.configuration.api_key
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def client_params
|
48
|
+
{ wsdl: TaxCloud::WSDL_URL }.tap do |params|
|
49
|
+
params[:open_timeout] = TaxCloud.configuration.open_timeout if TaxCloud.configuration.open_timeout
|
50
|
+
params[:read_timeout] = TaxCloud.configuration.read_timeout if TaxCloud.configuration.read_timeout
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def safe
|
55
|
+
yield
|
56
|
+
rescue Savon::SOAP::Fault => e
|
57
|
+
raise TaxCloud::Errors::SoapError.new(e)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TaxCloud #:nodoc:
|
2
|
+
# TaxCloud gem configuration.
|
3
|
+
class Configuration
|
4
|
+
# TaxCloud login ID.
|
5
|
+
attr_accessor :api_login_id
|
6
|
+
# TaxCloud API key.
|
7
|
+
attr_accessor :api_key
|
8
|
+
# Optional USPS username.
|
9
|
+
attr_accessor :usps_username
|
10
|
+
# Savon client option open_timeout.
|
11
|
+
attr_accessor :open_timeout
|
12
|
+
# Savon client option read_timeout.
|
13
|
+
attr_accessor :read_timeout
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@open_timeout = 2
|
17
|
+
@read_timeout = 2
|
18
|
+
end
|
19
|
+
|
20
|
+
# Check the configuration.
|
21
|
+
#
|
22
|
+
# Will raise a TaxCloud::Errors::MissingConfigOption if any of the API login ID or the API key are missing.
|
23
|
+
def check!
|
24
|
+
raise TaxCloud::Errors::MissingConfigOption.new('api_login_id') unless api_login_id && !api_login_id.strip.empty?
|
25
|
+
raise TaxCloud::Errors::MissingConfigOption.new('api_key') unless api_key && !api_key.strip.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'tax_cloud/errors/tax_cloud_error'
|
2
|
+
require 'tax_cloud/errors/missing_config_error'
|
3
|
+
require 'tax_cloud/errors/missing_config_option_error'
|
4
|
+
require 'tax_cloud/errors/soap_error'
|
5
|
+
require 'tax_cloud/errors/unexpected_soap_response_error'
|
6
|
+
require 'tax_cloud/errors/api_error'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
module TaxCloud #:nodoc:
|
3
|
+
module Errors #:nodoc:
|
4
|
+
# This error is raised when the TaxCloud service
|
5
|
+
# returns an error from an API.
|
6
|
+
class ApiError < TaxCloudError
|
7
|
+
# === Parameters
|
8
|
+
# [message] Error message.
|
9
|
+
# [raw] Raw data from the SOAP response.
|
10
|
+
def initialize(message, raw)
|
11
|
+
super(compose_message('api_error',
|
12
|
+
message: message,
|
13
|
+
raw: raw))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module TaxCloud #:nodoc:
|
3
|
+
module Errors #:nodoc:
|
4
|
+
# This error is raised when attempting to create a new client
|
5
|
+
# without configuring TaxCloud.
|
6
|
+
class MissingConfig < TaxCloudError
|
7
|
+
# Create a new error.
|
8
|
+
def initialize
|
9
|
+
super(compose_message('missing_config'))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module TaxCloud #:nodoc:
|
3
|
+
module Errors #:nodoc:
|
4
|
+
# This error is raised when a configuration
|
5
|
+
# option is missing.
|
6
|
+
class MissingConfigOption < TaxCloudError
|
7
|
+
# === Parameters
|
8
|
+
# [name] The attempted config option name.
|
9
|
+
def initialize(name)
|
10
|
+
super(
|
11
|
+
compose_message(
|
12
|
+
'missing_config_option',
|
13
|
+
name: name
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module TaxCloud #:nodoc:
|
3
|
+
module Errors #:nodoc:
|
4
|
+
# This error is raised when a SOAP call fails.
|
5
|
+
class SoapError < TaxCloudError
|
6
|
+
# Original SOAP failt.
|
7
|
+
attr_reader :fault
|
8
|
+
|
9
|
+
# Create the new error.
|
10
|
+
# === Parameters
|
11
|
+
# [e] SOAP response.
|
12
|
+
def initialize(e)
|
13
|
+
@fault = e
|
14
|
+
e.to_hash.tap do |fault|
|
15
|
+
fault_code = fault[:fault][:faultcode]
|
16
|
+
fault_string = parse_fault(fault[:fault][:faultstring])
|
17
|
+
super(compose_message('soap_error',
|
18
|
+
message: fault_string,
|
19
|
+
code: fault_code))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parse_fault(fault_string)
|
26
|
+
fault_string.lines.first.strip
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
module TaxCloud #:nodoc:
|
3
|
+
module Errors #:nodoc:
|
4
|
+
# Default parent TaxCloud error for all custom errors. This handles the base
|
5
|
+
# key for the translations and provides the convenience method for
|
6
|
+
# translating the messages.
|
7
|
+
#
|
8
|
+
# Generously borrowed from Mongoid[https://github.com/mongoid/mongoid/blob/master/lib/mongoid/errors/mongoid_error.rb].
|
9
|
+
class TaxCloudError < StandardError
|
10
|
+
# Problem occurred.
|
11
|
+
attr_reader :problem
|
12
|
+
|
13
|
+
# Summary of the problem.
|
14
|
+
attr_reader :summary
|
15
|
+
|
16
|
+
# Suggested problem resolution.
|
17
|
+
attr_reader :resolution
|
18
|
+
|
19
|
+
# Compose the message.
|
20
|
+
# === Parameters
|
21
|
+
# [key] Lookup key in the translation table.
|
22
|
+
# [attributes] The objects to pass to create the message.
|
23
|
+
def compose_message(key, attributes = {})
|
24
|
+
@problem = create_problem(key, attributes)
|
25
|
+
@summary = create_summary(key, attributes)
|
26
|
+
@resolution = create_resolution(key, attributes)
|
27
|
+
|
28
|
+
"\nProblem:\n #{@problem}" \
|
29
|
+
"\nSummary:\n #{@summary}" \
|
30
|
+
"\nResolution:\n #{@resolution}"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
BASE_KEY = 'taxcloud.errors.messages'.freeze #:nodoc:
|
36
|
+
|
37
|
+
# Given the key of the specific error and the options hash, translate the
|
38
|
+
# message.
|
39
|
+
#
|
40
|
+
# === Parameters
|
41
|
+
# [key] The key of the error in the locales.
|
42
|
+
# [options] The objects to pass to create the message.
|
43
|
+
#
|
44
|
+
# Returns a localized error message string.
|
45
|
+
def translate(key, options)
|
46
|
+
::I18n.translate("#{BASE_KEY}.#{key}", { locale: :en }.merge(options)).strip
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create the problem.
|
50
|
+
#
|
51
|
+
# === Parameters
|
52
|
+
# [key] The error key.
|
53
|
+
# [attributes] The attributes to interpolate.
|
54
|
+
#
|
55
|
+
# Returns the problem.
|
56
|
+
def create_problem(key, attributes)
|
57
|
+
translate("#{key}.message", attributes)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create the summary.
|
61
|
+
#
|
62
|
+
# === Parameters
|
63
|
+
# [key] The error key.
|
64
|
+
# [attributes] The attributes to interpolate.
|
65
|
+
#
|
66
|
+
# Returns the summary.
|
67
|
+
def create_summary(key, attributes)
|
68
|
+
translate("#{key}.summary", attributes)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create the resolution.
|
72
|
+
#
|
73
|
+
# === Parameters
|
74
|
+
# [key] The error key.
|
75
|
+
# [attributes] The attributes to interpolate.
|
76
|
+
#
|
77
|
+
# Returns the resolution.
|
78
|
+
def create_resolution(key, attributes)
|
79
|
+
translate("#{key}.resolution", attributes)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module TaxCloud #:nodoc:
|
3
|
+
module Errors #:nodoc:
|
4
|
+
# This error is raised when TaxCloud returns an
|
5
|
+
# unexpected SOAP response.
|
6
|
+
class UnexpectedSoapResponse < TaxCloudError
|
7
|
+
# === Parameters
|
8
|
+
# [raw] Raw data from the SOAP response.
|
9
|
+
# [key] Expected key in the SOAP response.
|
10
|
+
# [chain] Complete SOAP response chain in which the key could not be found.
|
11
|
+
def initialize(raw, key, chain)
|
12
|
+
super(compose_message('unexpected_soap_response',
|
13
|
+
key: key,
|
14
|
+
raw: raw,
|
15
|
+
chain: chain))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module TaxCloud #:nodoc:
|
2
|
+
# A generic TaxCloud record.
|
3
|
+
class Record
|
4
|
+
# Initialize the record.
|
5
|
+
#
|
6
|
+
# === Parameters
|
7
|
+
# [attrs] Attributes defined for this record.
|
8
|
+
def initialize(attrs = {})
|
9
|
+
attrs.each do |sym, val|
|
10
|
+
send "#{sym}=", val
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'tax_cloud/responses/base'
|
2
|
+
require 'tax_cloud/responses/generic'
|
3
|
+
require 'tax_cloud/responses/ping'
|
4
|
+
require 'tax_cloud/responses/verify_address'
|
5
|
+
require 'tax_cloud/responses/cart_item'
|
6
|
+
require 'tax_cloud/responses/lookup'
|
7
|
+
require 'tax_cloud/responses/authorized'
|
8
|
+
require 'tax_cloud/responses/authorized_with_capture'
|
9
|
+
require 'tax_cloud/responses/captured'
|
10
|
+
require 'tax_cloud/responses/returned'
|
11
|
+
require 'tax_cloud/responses/tax_codes'
|
12
|
+
require 'tax_cloud/responses/tax_code_groups'
|
13
|
+
require 'tax_cloud/responses/tax_codes_by_group'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module TaxCloud #:nodoc:
|
2
|
+
module Responses #:nodoc:
|
3
|
+
# Response to a TaxCloud AuthorizedWithCapture API call.
|
4
|
+
#
|
5
|
+
# See https://api.taxcloud.net/1.0/TaxCloud.asmx?op=AuthorizedWithCapture.
|
6
|
+
class AuthorizedWithCapture < Generic
|
7
|
+
response_key :authorized_with_capture
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module TaxCloud #:nodoc:
|
2
|
+
module Responses #:nodoc:
|
3
|
+
# A base TaxCloud SOAP response.
|
4
|
+
class Base
|
5
|
+
# Raw response.
|
6
|
+
attr_accessor :raw
|
7
|
+
|
8
|
+
# === Parameters
|
9
|
+
# [savon_response] Response from a SOAP API call.
|
10
|
+
def initialize(savon_response)
|
11
|
+
@raw = savon_response.to_hash
|
12
|
+
parse!
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# === Parameters
|
17
|
+
# [value] Location of the response type in the SOAP XML.
|
18
|
+
def response_type(value)
|
19
|
+
set_dsl(:response_type, value)
|
20
|
+
end
|
21
|
+
|
22
|
+
# === Parameters
|
23
|
+
# [value] Location of the error message in the SOAP XML.
|
24
|
+
def error_message(value)
|
25
|
+
set_dsl(:error_message, value)
|
26
|
+
end
|
27
|
+
|
28
|
+
# === Parameters
|
29
|
+
# [value] Location of the error number in the SOAP XML.
|
30
|
+
def error_number(value)
|
31
|
+
set_dsl(:error_number, value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parse a SOAP response.
|
35
|
+
#
|
36
|
+
# === Parameters
|
37
|
+
# [savon_response] SOAP response.
|
38
|
+
def parse(savon_response)
|
39
|
+
new(savon_response)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Match an element in the SOAP response.
|
44
|
+
#
|
45
|
+
# === Parameters
|
46
|
+
# [chain] XML path to match.
|
47
|
+
def match(chain)
|
48
|
+
current_value = raw
|
49
|
+
chain.split('/').each do |key|
|
50
|
+
current_value = current_value[key.to_sym]
|
51
|
+
next if current_value
|
52
|
+
raise TaxCloud::Errors::UnexpectedSoapResponse.new(raw, key, chain)
|
53
|
+
end
|
54
|
+
current_value
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
class_attribute :dsl
|
60
|
+
|
61
|
+
class << self
|
62
|
+
def set_dsl(key, value)
|
63
|
+
self.dsl ||= {}
|
64
|
+
self.dsl[key] = value
|
65
|
+
self.dsl
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse!
|
70
|
+
if self.dsl[:response_type]
|
71
|
+
case match(self.dsl[:response_type])
|
72
|
+
when 'OK', 'Informational' then
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
elsif self.dsl[:error_number]
|
76
|
+
return true if match(self.dsl[:error_number]) == '0'
|
77
|
+
end
|
78
|
+
raise TaxCloud::Errors::ApiError.new(match(self.dsl[:error_message]), raw) if self.dsl[:error_message]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|