tax_cloud 0.1.4 → 0.2.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 (77) hide show
  1. data/.travis.yml +9 -0
  2. data/CHANGELOG.rdoc +28 -0
  3. data/CONTRIBUTORS.txt +20 -0
  4. data/Gemfile +1 -1
  5. data/LICENSE.rdoc +22 -0
  6. data/README.rdoc +112 -28
  7. data/Rakefile +10 -2
  8. data/lib/config/locales/en.yml +34 -0
  9. data/lib/hash.rb +8 -6
  10. data/lib/savon_soap_xml.rb +33 -0
  11. data/lib/tasks/tax_cloud.rake +20 -0
  12. data/lib/tasks/tax_code_groups.rake +39 -0
  13. data/lib/tasks/tax_codes.rake +45 -0
  14. data/lib/tax_cloud.rb +43 -16
  15. data/lib/tax_cloud/address.rb +30 -17
  16. data/lib/tax_cloud/cart_item.rb +13 -19
  17. data/lib/tax_cloud/client.rb +48 -0
  18. data/lib/tax_cloud/configuration.rb +17 -2
  19. data/lib/tax_cloud/errors.rb +6 -0
  20. data/lib/tax_cloud/errors/api_error.rb +18 -0
  21. data/lib/tax_cloud/errors/missing_config_error.rb +13 -0
  22. data/lib/tax_cloud/errors/missing_config_option_error.rb +19 -0
  23. data/lib/tax_cloud/errors/soap_error.rb +33 -0
  24. data/lib/tax_cloud/errors/tax_cloud_error.rb +86 -0
  25. data/lib/tax_cloud/errors/unexpected_soap_response_error.rb +23 -0
  26. data/lib/tax_cloud/record.rb +14 -0
  27. data/lib/tax_cloud/responses.rb +13 -0
  28. data/lib/tax_cloud/responses/authorized.rb +10 -0
  29. data/lib/tax_cloud/responses/authorized_with_capture.rb +10 -0
  30. data/lib/tax_cloud/responses/base.rb +88 -0
  31. data/lib/tax_cloud/responses/captured.rb +11 -0
  32. data/lib/tax_cloud/responses/cart_item.rb +24 -0
  33. data/lib/tax_cloud/responses/generic.rb +35 -0
  34. data/lib/tax_cloud/responses/lookup.rb +41 -0
  35. data/lib/tax_cloud/responses/ping.rb +10 -0
  36. data/lib/tax_cloud/responses/returned.rb +10 -0
  37. data/lib/tax_cloud/responses/tax_code_groups.rb +33 -0
  38. data/lib/tax_cloud/responses/tax_codes.rb +33 -0
  39. data/lib/tax_cloud/responses/tax_codes_by_group.rb +33 -0
  40. data/lib/tax_cloud/responses/verify_address.rb +29 -0
  41. data/lib/tax_cloud/tax_code.rb +11 -0
  42. data/lib/tax_cloud/tax_code_constants.rb +562 -0
  43. data/lib/tax_cloud/tax_code_group.rb +30 -0
  44. data/lib/tax_cloud/tax_code_group_constants.rb +31 -0
  45. data/lib/tax_cloud/tax_code_groups.rb +28 -0
  46. data/lib/tax_cloud/tax_codes.rb +24 -47
  47. data/lib/tax_cloud/transaction.rb +39 -34
  48. data/lib/tax_cloud/version.rb +3 -3
  49. data/tax_cloud.gemspec +7 -5
  50. data/test/cassettes/authorized.yml +70 -45
  51. data/test/cassettes/authorized_with_capture.yml +70 -45
  52. data/test/cassettes/captured.yml +101 -67
  53. data/test/cassettes/get_tic_groups.yml +656 -0
  54. data/test/cassettes/get_tics.yml +952 -0
  55. data/test/cassettes/get_tics_by_group.yml +49 -0
  56. data/test/cassettes/invalid_soap_call.yml +651 -0
  57. data/test/cassettes/lookup.yml +644 -25
  58. data/test/cassettes/lookup_ny.yml +651 -0
  59. data/test/cassettes/ping.yml +647 -0
  60. data/test/cassettes/ping_with_invalid_credentials.yml +647 -0
  61. data/test/cassettes/ping_with_invalid_response.yml +647 -0
  62. data/test/cassettes/returned.yml +101 -67
  63. data/test/cassettes/verify_bad_address.yml +578 -976
  64. data/test/cassettes/verify_good_address.yml +36 -23
  65. data/test/helper.rb +4 -19
  66. data/test/test_address.rb +25 -7
  67. data/test/test_client.rb +29 -0
  68. data/test/test_configuration.rb +33 -0
  69. data/test/test_setup.rb +18 -0
  70. data/test/test_soap.rb +13 -0
  71. data/test/test_tax_code_groups.rb +31 -0
  72. data/test/test_tax_codes.rb +19 -0
  73. data/test/test_transaction.rb +22 -11
  74. data/test/test_transaction_ny.rb +27 -0
  75. data/test/vcr_setup.rb +9 -0
  76. metadata +134 -24
  77. data/lib/savon_xml_override.rb +0 -30
@@ -1,44 +1,71 @@
1
1
  require 'savon'
2
- require 'savon_xml_override'
2
+ require 'i18n'
3
+ require 'hash'
4
+ require 'savon_soap_xml'
5
+ require 'active_support/core_ext'
6
+
3
7
  require 'tax_cloud/version'
8
+ require 'tax_cloud/errors'
9
+ require 'tax_cloud/responses'
10
+ require 'tax_cloud/record'
4
11
  require 'tax_cloud/transaction'
5
12
  require 'tax_cloud/address'
6
13
  require 'tax_cloud/cart_item'
14
+ require 'tax_cloud/tax_code'
15
+ require 'tax_cloud/tax_code_group'
7
16
  require 'tax_cloud/tax_codes'
17
+ require 'tax_cloud/tax_code_constants'
18
+ require 'tax_cloud/tax_code_groups'
19
+ require 'tax_cloud/tax_code_group_constants'
8
20
  require 'tax_cloud/configuration'
9
- require 'hash'
21
+ require 'tax_cloud/client'
22
+
23
+ I18n.load_path << File.join(File.dirname(__FILE__), "config", "locales", "en.yml")
10
24
 
11
25
  # TaxCloud is a web service to calculate and track sales tax for your ecommerce platform. Integration is easy to use.
12
26
  # For information on configuring and using the TaxCloud API, look at the <tt>README</tt> file.
13
27
  module TaxCloud
14
- # WSDL location for TaxCloud
28
+
29
+ # WSDL location for TaxCloud API.
15
30
  WSDL_URL = 'https://api.taxcloud.net/1.0/?wsdl'
16
- # TaxCloud API version
31
+
32
+ # TaxCloud API version.
17
33
  API_VERSION = '1.0'
18
34
 
19
35
  class << self
36
+
37
+ # TaxCloud gem configuration.
20
38
  attr_accessor :configuration
21
39
 
22
- # Configure the variables
40
+ # Returns true if the gem has been configured.
41
+ def configured?
42
+ !! self.configuration
43
+ end
44
+
45
+ # Configure the gem.
23
46
  def configure
24
47
  self.configuration ||= Configuration.new
25
48
  yield configuration
26
49
  end
27
50
 
28
- # Method to define and retrieve the SOAP methods
29
- def client
30
- @@client ||= Savon::Client.new do
31
- wsdl.document = TaxCloud::WSDL_URL
32
- end
51
+ # Reset the current configuration.
52
+ def reset!
53
+ self.configuration = nil
33
54
  end
34
55
 
35
- # Authorization hash to use with all SOAP requests
36
- def auth_params
37
- {
38
- 'apiLoginID' => configuration.api_login_id,
39
- 'apiKey' => configuration.api_key
40
- }
56
+ # The configured SOAP client to the TaxCloud service.
57
+ def client
58
+ check_configuration!
59
+ @@client ||= TaxCloud::Client.new
41
60
  end
61
+
62
+ private
63
+
64
+ def check_configuration!
65
+ raise TaxCloud::Errors::MissingConfig.new unless self.configuration
66
+ self.configuration.check!
67
+ end
68
+
42
69
  end
43
70
 
44
71
  end
@@ -1,24 +1,37 @@
1
- module TaxCloud
2
- # An address
3
- class Address
4
- attr_accessor :address1, :address2, :city, :state, :zip5, :zip4
1
+ module TaxCloud #:nodoc:
2
+ # An <tt>Address</tt> defines an address in the United States.
3
+ class Address < Record
5
4
 
6
- # Initialize the object with the given variables
7
- def initialize(attrs = {})
8
- attrs.each do |sym, val|
9
- self.send "#{sym}=", val
10
- end
11
- super
12
- end
5
+ # First line of address.
6
+ attr_accessor :address1
7
+ # Second line of adress.
8
+ attr_accessor :address2
9
+ # City.
10
+ attr_accessor :city
11
+ # State.
12
+ attr_accessor :state
13
+ # 5-digit Zip Code.
14
+ attr_accessor :zip5
15
+ # 4-digit Zip Code.
16
+ attr_accessor :zip4
13
17
 
14
- # Verify the address via TaxCloud
18
+ # Verify this address.
19
+ #
20
+ # Returns a verified TaxCloud::Address.
15
21
  def verify
16
- request_params = {
17
- 'apiLoginId' => TaxCloud.configuration.api_login_id,
18
- 'apiKey' => TaxCloud.configuration.api_key,
22
+ params = to_hash.downcase_keys
23
+ params = params.merge({
19
24
  'uspsUserID' => TaxCloud.configuration.usps_username
20
- }.merge(to_hash.downcase_keys!)
21
- response = TaxCloud.client.request :verify_address, :body => request_params
25
+ }) if TaxCloud.configuration.usps_username
26
+ response = TaxCloud.client.request(:verify_address, params)
27
+ TaxCloud::Responses::VerifyAddress.parse(response)
28
+ end
29
+
30
+ # Complete zip code.
31
+ # Returns a 9-digit Zip Code, when available.
32
+ def zip
33
+ return nil unless zip5 && zip5.length > 0
34
+ [ zip5, zip4 ].select { |z| z && z.length > 0 }.join("-")
22
35
  end
23
36
 
24
37
  # Convert the object to a usable hash for SOAP requests
@@ -1,25 +1,19 @@
1
- require 'builder'
2
-
3
- module TaxCloud
1
+ module TaxCloud #:nodoc:
4
2
  # A <tt>CartItem</tt> defines a single line item for the purchase. Used to calculate the tax amount.
5
- #
6
- # === Attributes
7
- # * <tt>index</tt> - The unique index number for the line item. Must be unique for the scope of the cart.
8
- # * <tt>item_id</tt> - The stock keeping unit (SKU) number
9
- # * <tt>tic</tt> - The taxable information code. See <tt>TaxCloud::TaxCodes</tt>.
10
- # * <tt>price</tt> - The price of the item. All prices are USD. Do not include currency symbol.
11
- # * <tt>quantity</tt> - The total number of items.
12
- class CartItem
13
- attr_accessor :index, :item_id, :tic, :price, :quantity
3
+ class CartItem < Record
14
4
 
15
- def initialize(attrs = {})
16
- attrs.each do |sym, val|
17
- self.send "#{sym}=", val
18
- end
19
- super
20
- end
5
+ # The unique index number for the line item. Must be unique for the scope of the cart.
6
+ attr_accessor :index
7
+ # The stock keeping unit (SKU) number.
8
+ attr_accessor :item_id
9
+ # The taxable information code. See TaxCloud::TaxCodes.
10
+ attr_accessor :tic
11
+ # The price of the item. All prices are USD. Do not include currency symbol.
12
+ attr_accessor :price
13
+ # The total number of items.
14
+ attr_accessor :quantity
21
15
 
22
- # Convert the object to a usable hash for SOAP requests
16
+ # Convert the object to a usable hash for SOAP requests.
23
17
  def to_hash
24
18
  {
25
19
  'Index' => index,
@@ -0,0 +1,48 @@
1
+ module TaxCloud #:nodoc:
2
+ # A <tt>Client</tt> communicates with the TaCloud service.
3
+ class Client < Savon::Client
4
+
5
+ # Create a new client.
6
+ def initialize
7
+ super TaxCloud::WSDL_URL
8
+ end
9
+
10
+ # Make a safe SOAP call.
11
+ # Will raise a TaxCloud::Errors::SoapError on error.
12
+ #
13
+ # === Parameters
14
+ # [method] SOAP method.
15
+ # [body] Body content.
16
+ def request(method, body = {})
17
+ safe do
18
+ super method, :body => body.merge(auth_params)
19
+ end
20
+ end
21
+
22
+ # Ping the TaxCloud service.
23
+ #
24
+ # Returns "OK" or raises an error if the TaxCloud service is unreachable.
25
+ def ping
26
+ TaxCloud::Responses::Ping.parse request(:ping)
27
+ end
28
+
29
+ private
30
+
31
+ # Authorization hash to use with all SOAP requests
32
+ def auth_params
33
+ {
34
+ 'apiLoginID' => TaxCloud.configuration.api_login_id,
35
+ 'apiKey' => TaxCloud.configuration.api_key
36
+ }
37
+ end
38
+
39
+ def safe &block
40
+ begin
41
+ yield
42
+ rescue Savon::SOAP::Fault => e
43
+ raise TaxCloud::Errors::SoapError.new(e)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -1,5 +1,20 @@
1
- module TaxCloud
1
+ module TaxCloud #:nodoc:
2
+ # TaxCloud gem configuration.
2
3
  class Configuration
3
- attr_accessor :api_login_id, :api_key, :usps_username
4
+
5
+ # TaxCloud login ID.
6
+ attr_accessor :api_login_id
7
+ # TaxCloud API key.
8
+ attr_accessor :api_key
9
+ # Optional USPS username.
10
+ attr_accessor :usps_username
11
+
12
+ # Check the configuration.
13
+ #
14
+ # Will raise a TaxCloud::Errors::MissingConfigOption if any of the API login ID or the API key are missing.
15
+ def check!
16
+ raise TaxCloud::Errors::MissingConfigOption.new('api_login_id') unless self.api_login_id && self.api_login_id.strip.length > 0
17
+ raise TaxCloud::Errors::MissingConfigOption.new('api_key') unless self.api_key && self.api_key.strip.length > 0
18
+ end
4
19
  end
5
20
  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,18 @@
1
+ # encoding: utf-8
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
+ }))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
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
+ # encoding: utf-8
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,33 @@
1
+ # encoding: utf-8
2
+ module TaxCloud #:nodoc:
3
+ module Errors #:nodoc:
4
+ # This error is raised when a SOAP call fails.
5
+ class SoapError < TaxCloudError
6
+
7
+ # Original SOAP failt.
8
+ attr_reader :fault
9
+
10
+ # Create the new error.
11
+ # === Parameters
12
+ # [e] SOAP response.
13
+ def initialize(e)
14
+ @fault = e
15
+ e.to_hash.tap do |fault|
16
+ fault_code = fault[:fault][:faultcode]
17
+ fault_string = parse_fault(fault[:fault][:faultstring])
18
+ super(compose_message("soap_error", {
19
+ :message => fault_string,
20
+ :code => fault_code
21
+ }))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def parse_fault(fault_string)
28
+ fault_string.lines.first.strip
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ module TaxCloud #:nodoc:
3
+ module Errors #:nodoc:
4
+
5
+ # Default parent TaxCloud error for all custom errors. This handles the base
6
+ # key for the translations and provides the convenience method for
7
+ # translating the messages.
8
+ #
9
+ # Generously borrowed from Mongoid[https://github.com/mongoid/mongoid/blob/master/lib/mongoid/errors/mongoid_error.rb].
10
+ class TaxCloudError < StandardError
11
+
12
+ # Problem occurred.
13
+ attr_reader :problem
14
+
15
+ # Summary of the problem.
16
+ attr_reader :summary
17
+
18
+ # Suggested problem resolution.
19
+ attr_reader :resolution
20
+
21
+ # Compose the message.
22
+ # === Parameters
23
+ # [key] Lookup key in the translation table.
24
+ # [attributes] The objects to pass to create the message.
25
+ def compose_message(key, attributes = {})
26
+ @problem = create_problem(key, attributes)
27
+ @summary = create_summary(key, attributes)
28
+ @resolution = create_resolution(key, attributes)
29
+
30
+ "\nProblem:\n #{@problem}"+
31
+ "\nSummary:\n #{@summary}"+
32
+ "\nResolution:\n #{@resolution}"
33
+ end
34
+
35
+ private
36
+
37
+ BASE_KEY = "taxcloud.errors.messages" #:nodoc:
38
+
39
+ # Given the key of the specific error and the options hash, translate the
40
+ # message.
41
+ #
42
+ # === Parameters
43
+ # [key] The key of the error in the locales.
44
+ # [options] The objects to pass to create the message.
45
+ #
46
+ # Returns a localized error message string.
47
+ def translate(key, options)
48
+ ::I18n.translate("#{BASE_KEY}.#{key}", { :locale => :en }.merge(options)).strip
49
+ end
50
+
51
+ # Create the problem.
52
+ #
53
+ # === Parameters
54
+ # [key] The error key.
55
+ # [attributes] The attributes to interpolate.
56
+ #
57
+ # Returns the problem.
58
+ def create_problem(key, attributes)
59
+ translate("#{key}.message", attributes)
60
+ end
61
+
62
+ # Create the summary.
63
+ #
64
+ # === Parameters
65
+ # [key] The error key.
66
+ # [attributes] The attributes to interpolate.
67
+ #
68
+ # Returns the summary.
69
+ def create_summary(key, attributes)
70
+ translate("#{key}.summary", attributes)
71
+ end
72
+
73
+ # Create the resolution.
74
+ #
75
+ # === Parameters
76
+ # [key] The error key.
77
+ # [attributes] The attributes to interpolate.
78
+ #
79
+ # Returns the resolution.
80
+ def create_resolution(key, attributes)
81
+ translate("#{key}.resolution", attributes)
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module TaxCloud #:nodoc:
3
+ module Errors #:nodoc:
4
+
5
+ # This error is raised when TaxCloud returns an
6
+ # unexpected SOAP response.
7
+ class UnexpectedSoapResponse < TaxCloudError
8
+
9
+ # === Parameters
10
+ # [raw] Raw data from the SOAP response.
11
+ # [key] Expected key in the SOAP response.
12
+ # [chain] Complete SOAP response chain in which the key could not be found.
13
+ def initialize(raw, key, chain)
14
+ super(compose_message("unexpected_soap_response", {
15
+ :key => key,
16
+ :raw => raw,
17
+ :chain => chain
18
+ }))
19
+ end
20
+
21
+ end
22
+ end
23
+ end