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.
Files changed (90) hide show
  1. checksums.yaml +17 -0
  2. data/.circleci/config.yml +81 -0
  3. data/.circleci/setup-rubygems.sh +9 -0
  4. data/.gitignore +67 -0
  5. data/.rubocop.yml +6 -0
  6. data/.rubocop_todo.yml +66 -0
  7. data/.travis.yml +7 -0
  8. data/CHANGELOG.rdoc +48 -0
  9. data/CONTRIBUTORS.txt +23 -0
  10. data/Dockerfile +9 -0
  11. data/Gemfile +22 -0
  12. data/LICENSE +23 -0
  13. data/README.md +190 -0
  14. data/Rakefile +29 -0
  15. data/docker-compose.yml +10 -0
  16. data/examples/.env.example +2 -0
  17. data/examples/lookup_example.rb +139 -0
  18. data/lib/config/locales/en.yml +34 -0
  19. data/lib/hash.rb +8 -0
  20. data/lib/savon_soap_xml.rb +32 -0
  21. data/lib/tasks/tax_cloud.rake +18 -0
  22. data/lib/tasks/tax_code_groups.rake +37 -0
  23. data/lib/tasks/tax_codes.rake +43 -0
  24. data/lib/tax_cloud.rb +70 -0
  25. data/lib/tax_cloud/address.rb +50 -0
  26. data/lib/tax_cloud/cart_item.rb +26 -0
  27. data/lib/tax_cloud/client.rb +60 -0
  28. data/lib/tax_cloud/configuration.rb +28 -0
  29. data/lib/tax_cloud/errors.rb +6 -0
  30. data/lib/tax_cloud/errors/api_error.rb +17 -0
  31. data/lib/tax_cloud/errors/missing_config_error.rb +13 -0
  32. data/lib/tax_cloud/errors/missing_config_option_error.rb +19 -0
  33. data/lib/tax_cloud/errors/soap_error.rb +30 -0
  34. data/lib/tax_cloud/errors/tax_cloud_error.rb +83 -0
  35. data/lib/tax_cloud/errors/unexpected_soap_response_error.rb +19 -0
  36. data/lib/tax_cloud/record.rb +14 -0
  37. data/lib/tax_cloud/responses.rb +13 -0
  38. data/lib/tax_cloud/responses/authorized.rb +10 -0
  39. data/lib/tax_cloud/responses/authorized_with_capture.rb +10 -0
  40. data/lib/tax_cloud/responses/base.rb +82 -0
  41. data/lib/tax_cloud/responses/captured.rb +10 -0
  42. data/lib/tax_cloud/responses/cart_item.rb +23 -0
  43. data/lib/tax_cloud/responses/generic.rb +31 -0
  44. data/lib/tax_cloud/responses/lookup.rb +38 -0
  45. data/lib/tax_cloud/responses/ping.rb +10 -0
  46. data/lib/tax_cloud/responses/returned.rb +10 -0
  47. data/lib/tax_cloud/responses/tax_code_groups.rb +30 -0
  48. data/lib/tax_cloud/responses/tax_codes.rb +30 -0
  49. data/lib/tax_cloud/responses/tax_codes_by_group.rb +30 -0
  50. data/lib/tax_cloud/responses/verify_address.rb +26 -0
  51. data/lib/tax_cloud/tax_code.rb +11 -0
  52. data/lib/tax_cloud/tax_code_constants.rb +560 -0
  53. data/lib/tax_cloud/tax_code_group.rb +28 -0
  54. data/lib/tax_cloud/tax_code_group_constants.rb +31 -0
  55. data/lib/tax_cloud/tax_code_groups.rb +25 -0
  56. data/lib/tax_cloud/tax_codes.rb +25 -0
  57. data/lib/tax_cloud/transaction.rb +118 -0
  58. data/lib/tax_cloud/version.rb +4 -0
  59. data/tax_cloud.gemspec +23 -0
  60. data/test/cassettes/authorized.yml +826 -0
  61. data/test/cassettes/authorized_with_capture.yml +826 -0
  62. data/test/cassettes/authorized_with_localized_time.yml +826 -0
  63. data/test/cassettes/captured.yml +872 -0
  64. data/test/cassettes/get_tic_groups.yml +783 -0
  65. data/test/cassettes/get_tics.yml +1079 -0
  66. data/test/cassettes/get_tics_by_group.yml +776 -0
  67. data/test/cassettes/invalid_soap_call.yml +778 -0
  68. data/test/cassettes/lookup.yml +780 -0
  69. data/test/cassettes/lookup_ny.yml +780 -0
  70. data/test/cassettes/ping.yml +776 -0
  71. data/test/cassettes/ping_with_invalid_credentials.yml +774 -0
  72. data/test/cassettes/ping_with_invalid_response.yml +774 -0
  73. data/test/cassettes/returned.yml +872 -0
  74. data/test/cassettes/verify_bad_address.yml +772 -0
  75. data/test/cassettes/verify_good_address.yml +772 -0
  76. data/test/helper.rb +17 -0
  77. data/test/test_address.rb +54 -0
  78. data/test/test_cart_item.rb +15 -0
  79. data/test/test_client.rb +27 -0
  80. data/test/test_configuration_optional_keys.rb +42 -0
  81. data/test/test_configuration_required_keys.rb +31 -0
  82. data/test/test_lookup_response.rb +20 -0
  83. data/test/test_setup.rb +17 -0
  84. data/test/test_soap.rb +11 -0
  85. data/test/test_tax_code_groups.rb +29 -0
  86. data/test/test_tax_codes.rb +17 -0
  87. data/test/test_transaction.rb +78 -0
  88. data/test/test_transaction_ny.rb +25 -0
  89. data/test/vcr_setup.rb +9 -0
  90. metadata +174 -0
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+ require 'vcr'
5
+
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'lib' << 'test'
8
+ test.pattern = 'test/test_*.rb'
9
+ # REM
10
+ #test.verbose = false
11
+ end
12
+
13
+ RDoc::Task.new do |rd|
14
+ README = 'README.rdoc'.freeze
15
+ rd.main = README
16
+ rd.rdoc_files.include(README, 'CHANGELOG.rdoc', 'LICENSE.rdoc', 'lib/**/*.rb')
17
+ rd.rdoc_dir = 'doc'
18
+ rd.title = 'tax_cloud'
19
+ end
20
+
21
+ load 'lib/tasks/tax_cloud.rake'
22
+ load 'lib/tasks/tax_codes.rake'
23
+ load 'lib/tasks/tax_code_groups.rake'
24
+ load 'vcr/tasks/vcr.rake'
25
+
26
+ require 'rubocop/rake_task'
27
+ RuboCop::RakeTask.new(:rubocop)
28
+
29
+ task default: [:rubocop, :test]
@@ -0,0 +1,10 @@
1
+ version: '3.7'
2
+ services:
3
+ app:
4
+ build: .
5
+ volumes:
6
+ - .:/app
7
+ - bundler-data:/usr/local/bundle
8
+
9
+ volumes:
10
+ bundler-data:
@@ -0,0 +1,2 @@
1
+ TAX_CLOUD_LOGIN_ID=
2
+ TAX_CLOUD_API_KEY=
@@ -0,0 +1,139 @@
1
+ ##
2
+ # An example of doing a tax lookup that hits TaxCloud. To run:
3
+ #
4
+ # bundle exec ruby lookup_example.rb
5
+ #
6
+ # Make sure you copy the .env.example to .env an update the
7
+ # values in it before running. I also recommend no running
8
+ # this script against a live TaxCloud site. Run it only against
9
+ # test sites.
10
+ ##
11
+
12
+ require 'dotenv/load'
13
+ require 'securerandom'
14
+
15
+ require_relative '../lib/tax_cloud'
16
+
17
+ ##
18
+ # Load the TaxCloud settings from the environment file.\
19
+ #
20
+ def load_config
21
+ TaxCloud.configure do |config|
22
+ config.api_login_id = ENV['TAX_CLOUD_LOGIN_ID']
23
+ config.api_key = ENV['TAX_CLOUD_API_KEY']
24
+ config.open_timeout = 10
25
+ config.read_timeout = 10
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Display the current TaxCloud settings.
31
+ #
32
+ def display_config
33
+ TaxCloud.configure do |config|
34
+ puts "API Login ID: #{config.api_login_id}"
35
+ puts "API Key: #{config.api_key}"
36
+ puts "Open timeout: #{config.open_timeout}"
37
+ puts "Read timeout: #{config.read_timeout}"
38
+ end
39
+ end
40
+
41
+ ##
42
+ # Create a test transaction with one cart item.
43
+ #
44
+ # Will create a new customer and cart each time using GUIDs for
45
+ # the customer and cart IDs. This prevents collisions with
46
+ # existing customer/carts when running the script multiple times.
47
+ #
48
+ def create_test_transaction
49
+ origin = TaxCloud::Address.new(
50
+ :address1 => '162 East Avenue',
51
+ :address2 => 'Third Floor',
52
+ :city => 'Norwalk',
53
+ :state => 'CT',
54
+ :zip5 => '06851')
55
+
56
+ destination = TaxCloud::Address.new(
57
+ :address1 => '3121 West Government Way',
58
+ :address2 => 'Suite 2B',
59
+ :city => 'Seattle',
60
+ :state => 'WA',
61
+ :zip5 => '98199')
62
+
63
+ transaction = TaxCloud::Transaction.new(
64
+ :customer_id => SecureRandom.uuid,
65
+ :cart_id => SecureRandom.uuid,
66
+ :origin => origin,
67
+ :destination => destination)
68
+
69
+ transaction.cart_items << TaxCloud::CartItem.new(
70
+ :index => 0,
71
+ :item_id => 'SKU-100',
72
+ :tic => TaxCloud::TaxCodes::GENERAL,
73
+ :price => 10.00,
74
+ :quantity => 1)
75
+
76
+ transaction
77
+ end
78
+
79
+ ##
80
+ # Displays information about the transaction:
81
+ #
82
+ # - Customer ID
83
+ # - Cart ID
84
+ # - Origin Address
85
+ # - Destination Address
86
+ # - Cart Items
87
+ #
88
+ # @param [TaxCloud::Transaction] transaction
89
+ #
90
+ def display_transaction(transaction)
91
+ puts "Customer ID: #{transaction.customer_id}"
92
+ puts "Cart ID: #{transaction.cart_id}"
93
+ puts "Origin: #{transaction.origin.to_hash}"
94
+ puts "Destination: #{transaction.destination.to_hash}"
95
+ puts "Cart Items: #{transaction.cart_items.map(&:to_hash)}"
96
+ end
97
+
98
+ ##
99
+ # Display the total tax and tax for each cart item.
100
+ #
101
+ # @param [TaxCloud::Responses::Lookup] lookup
102
+ def display_tax(lookup)
103
+ puts "Total tax amount: #{lookup.tax_amount}"
104
+
105
+ lookup.cart_items.each do |cart_item|
106
+ puts "Tax cart index #{cart_item.cart_item_index}: #{cart_item.tax_amount}"
107
+ end
108
+ end
109
+
110
+
111
+ ##
112
+ # Run an example lookup
113
+ ##
114
+ puts 'Loading config information from .env file...'
115
+ load_config
116
+ puts 'Config loaded'
117
+ puts
118
+
119
+ puts 'The TaxCloud config is:'
120
+ display_config
121
+ puts
122
+
123
+ puts 'Checking config...'
124
+ TaxCloud.configuration.check!
125
+ puts 'Check passed.'
126
+ puts
127
+
128
+ puts 'Pinging TaxCloud server...'
129
+ puts TaxCloud.client.ping
130
+
131
+ puts 'Cart values:'
132
+ transaction = create_test_transaction
133
+ display_transaction(transaction)
134
+ puts
135
+
136
+ puts 'Tax lookup:'
137
+ lookup = transaction.lookup
138
+ display_tax(lookup)
139
+ puts
@@ -0,0 +1,34 @@
1
+ en:
2
+ taxcloud:
3
+ errors:
4
+ messages:
5
+ missing_config:
6
+ message: "Missing configuration."
7
+ summary: "TaxCloud requires an API login ID and key."
8
+ resolution: "Create a TaxCloud merchant account at http://www.taxcloud.net.
9
+ Add a website to your TaxCloud account. This will generate an API ID and
10
+ API Key that you will need to use the service. Configure the TaxCloud
11
+ gem. For example, add the following to `config/initializers/tax_cloud.rb`.\n
12
+ \_\_TaxCloud.configure do |config|\n
13
+ \_\_\_config.api_login_id = 'your_tax_cloud_api_login_id'\n
14
+ \_\_\_config.api_key = 'your_tax_cloud_api_key'\n
15
+ \_\_\_config.usps_username = 'your_usps_username' # optional\n
16
+ \_\_end\n"
17
+ missing_config_option:
18
+ message: "Missing configuration option: %{name}."
19
+ summary: "A configuration option was not provided when configuring TaxCloud."
20
+ resolution: "Review your `TaxCloud.configure` code. Are you using an environment
21
+ variable that has not been set?"
22
+ soap_error:
23
+ message: "%{message}"
24
+ summary: "The server returned a %{code} error."
25
+ resolution: "Review the parameters passed to the request."
26
+ unexpected_soap_response:
27
+ message: "Expected a value for `%{key}` in `%{chain}` in the TaxCloud response."
28
+ summary: "TaxCloud returned an unexpected response: `%{raw}`."
29
+ resolution: "This is possibly a bug in the tax_cloud gem.\n
30
+ Please report it in https://github.com/drewtempelmeyer/tax_cloud/issues."
31
+ api_error:
32
+ message: "%{message}"
33
+ summary: "The TaxCloud server returned an error in the response: `%{raw}`"
34
+ resolution: "Check the request parameters."
data/lib/hash.rb ADDED
@@ -0,0 +1,8 @@
1
+ class Hash #:nodoc: all
2
+ # Downcase the keys. Use this because <tt>TaxCloud::Address.verify</tt> requires downcased keys
3
+ def downcase_keys
4
+ each_with_object({}) do |(k, v), h|
5
+ h[k.downcase] = v
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ # Hack Savon to work properly with arrays
2
+ module Savon #:nodoc:
3
+ module SOAP #:nodoc:
4
+ class XML #:nodoc:
5
+
6
+ private
7
+
8
+ old_add_namespaces_to_body = self.instance_method(:add_namespaces_to_body)
9
+
10
+ def add_namespaces_to_body(hash, path = [input[1].to_s])
11
+ return unless hash
12
+ return hash if hash.kind_of? Array
13
+ return hash.to_s unless hash.kind_of? Hash
14
+
15
+ hash.inject({}) do |newhash, (key, value)|
16
+ camelcased_key = Gyoku::XMLKey.create(key)
17
+ newpath = path + [camelcased_key]
18
+
19
+ if used_namespaces[newpath]
20
+ newhash.merge(
21
+ "#{used_namespaces[newpath]}:#{camelcased_key}" =>
22
+ add_namespaces_to_body(value, types[newpath] ? [types[newpath]] : newpath)
23
+ )
24
+ else
25
+ newhash.merge(key => value)
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ require 'tax_cloud'
2
+
3
+ namespace :tax_cloud do
4
+ desc 'Configure TaxCloud.'
5
+ task :configure do
6
+ unless TaxCloud.configured?
7
+ TaxCloud.configure do |config|
8
+ config.api_login_id = ENV['TAXCLOUD_API_LOGIN_ID']
9
+ config.api_key = ENV['TAXCLOUD_API_KEY']
10
+ config.usps_username = ENV['TAXCLOUD_USPS_USERNAME']
11
+ end
12
+ Savon.configure do |config|
13
+ config.log = false
14
+ end
15
+ HTTPI.log = false
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ namespace :tax_cloud do
2
+ desc 'Generate tax code groups.'
3
+ task tax_code_groups: :configure do
4
+ filename = 'lib/tax_cloud/tax_code_group_constants.rb'
5
+
6
+ begin
7
+ groups = TaxCloud::TaxCode::Groups.all.values
8
+
9
+ File.open filename, 'wt' do |f|
10
+ f.write "module TaxCloud\n"
11
+ f.write " class TaxCode\n"
12
+ f.write " # Tax Code Groups.\n"
13
+ f.write " class Groups\n"
14
+
15
+ groups.each do |group|
16
+ puts " #{group.description}"
17
+ code = group.description.upcase
18
+ code.gsub! /[^A-Z0-9]/, '_'
19
+ code.gsub! /_$/, ''
20
+ code.gsub! /\_+/, '_'
21
+ f.write " \# #{group.description}\n"
22
+ f.write " #{code} = #{group.group_id}\n"
23
+ end
24
+
25
+ f.write " end\n"
26
+ f.write " end\n"
27
+ f.write "end\n"
28
+ end
29
+
30
+ puts "Done, #{filename}."
31
+ rescue => e
32
+ puts 'ERROR: Unable to generate a new list of tax codes.'
33
+ puts e.message
34
+ raise e
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ namespace :tax_cloud do
2
+ desc 'Generate tax codes.'
3
+ task tax_codes: :configure do
4
+ filename = 'lib/tax_cloud/tax_code_constants.rb'
5
+
6
+ begin
7
+ groups = TaxCloud::TaxCode::Groups.all.values
8
+ groups_and_tax_codes = Hash[groups.map do |group|
9
+ [group, group.tax_codes.values]
10
+ end]
11
+ File.open filename, 'wt' do |f|
12
+ f.write "module TaxCloud\n"
13
+ f.write " # Tax Codes.\n"
14
+ f.write " class TaxCodes\n"
15
+ f.write "\n"
16
+ codes = {}
17
+ groups_and_tax_codes.each_pair do |group, tax_codes|
18
+ puts " #{group.description}"
19
+ f.write " \# #{group.description}\n\n"
20
+ tax_codes.each do |tax_code|
21
+ code = tax_code.description.upcase
22
+ code.gsub! /[^A-Z0-9]/, '_'
23
+ code.gsub! /\_+$/, ''
24
+ code.gsub! /\_+/, '_'
25
+ # avoid duplicates
26
+ code_id = codes[code] ? "#{code}_#{codes[code]}" : code
27
+ codes[code] = (codes[code] || 0) + 1
28
+ f.write " \# #{group.description}: #{tax_code.description}\n"
29
+ f.write " #{code_id} = #{tax_code.ticid}\n"
30
+ end
31
+ f.write "\n"
32
+ end
33
+ f.write " end\n"
34
+ f.write "end\n"
35
+ end
36
+ puts "Done, #{filename}."
37
+ rescue => e
38
+ puts 'ERROR: Unable to generate a new list of tax codes.'
39
+ puts e.message
40
+ raise e
41
+ end
42
+ end
43
+ end
data/lib/tax_cloud.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'savon'
2
+ require 'i18n'
3
+ require 'hash'
4
+ require 'savon_soap_xml'
5
+
6
+ require 'active_support'
7
+ require 'active_support/core_ext'
8
+
9
+ require 'tax_cloud/version'
10
+ require 'tax_cloud/errors'
11
+ require 'tax_cloud/responses'
12
+ require 'tax_cloud/record'
13
+ require 'tax_cloud/transaction'
14
+ require 'tax_cloud/address'
15
+ require 'tax_cloud/cart_item'
16
+ require 'tax_cloud/tax_code'
17
+ require 'tax_cloud/tax_code_group'
18
+ require 'tax_cloud/tax_codes'
19
+ require 'tax_cloud/tax_code_constants'
20
+ require 'tax_cloud/tax_code_groups'
21
+ require 'tax_cloud/tax_code_group_constants'
22
+ require 'tax_cloud/configuration'
23
+ require 'tax_cloud/client'
24
+
25
+ I18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml')
26
+
27
+ # TaxCloud is a web service to calculate and track sales tax for your ecommerce platform. Integration is easy to use.
28
+ # For information on configuring and using the TaxCloud API, look at the <tt>README</tt> file.
29
+ module TaxCloud
30
+ # WSDL location for TaxCloud API.
31
+ WSDL_URL = 'https://api.taxcloud.net/1.0/?wsdl'.freeze
32
+
33
+ # TaxCloud API version.
34
+ API_VERSION = '1.0'.freeze
35
+
36
+ class << self
37
+ # TaxCloud gem configuration.
38
+ attr_accessor :configuration
39
+
40
+ # Returns true if the gem has been configured.
41
+ def configured?
42
+ configuration.present?
43
+ end
44
+
45
+ # Configure the gem.
46
+ def configure
47
+ self.configuration ||= Configuration.new
48
+ yield configuration
49
+ end
50
+
51
+ # Reset the current configuration.
52
+ def reset!
53
+ self.configuration = nil
54
+ @client = nil
55
+ end
56
+
57
+ # The configured SOAP client to the TaxCloud service.
58
+ def client
59
+ check_configuration!
60
+ @client ||= TaxCloud::Client.new
61
+ end
62
+
63
+ private
64
+
65
+ def check_configuration!
66
+ raise TaxCloud::Errors::MissingConfig.new unless self.configuration
67
+ self.configuration.check!
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ module TaxCloud #:nodoc:
2
+ # An <tt>Address</tt> defines an address in the United States.
3
+ class Address < Record
4
+ # First line of address.
5
+ attr_accessor :address1
6
+ # Second line of adress.
7
+ attr_accessor :address2
8
+ # City.
9
+ attr_accessor :city
10
+ # State.
11
+ attr_accessor :state
12
+ # 5-digit Zip Code.
13
+ attr_accessor :zip5
14
+ # 4-digit Zip Code.
15
+ attr_accessor :zip4
16
+
17
+ # Verify this address.
18
+ #
19
+ # Returns a verified TaxCloud::Address.
20
+ def verify
21
+ params = to_hash.downcase_keys
22
+ if TaxCloud.configuration.usps_username
23
+ params = params.merge(
24
+ 'uspsUserID' => TaxCloud.configuration.usps_username
25
+ )
26
+ end
27
+ response = TaxCloud.client.request(:verify_address, params)
28
+ TaxCloud::Responses::VerifyAddress.parse(response)
29
+ end
30
+
31
+ # Complete zip code.
32
+ # Returns a 9-digit Zip Code, when available.
33
+ def zip
34
+ return nil unless zip5 && !zip5.empty?
35
+ [zip5, zip4].select { |z| z && !z.empty? }.join('-')
36
+ end
37
+
38
+ # Convert the object to a usable hash for SOAP requests
39
+ def to_hash
40
+ {
41
+ 'Address1' => address1,
42
+ 'Address2' => address2,
43
+ 'City' => city,
44
+ 'State' => state,
45
+ 'Zip5' => zip5,
46
+ 'Zip4' => zip4
47
+ }
48
+ end
49
+ end
50
+ end