corgibytes-tax_cloud 0.9.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.
- 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
|