strikeiron 0.0.1

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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
@@ -0,0 +1 @@
1
+ 1.9.3-p194
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem update --system
4
+ - gem install bundler --pre
5
+ rvm:
6
+ - 1.9.2
7
+ - 1.9.3
8
+ - jruby-19mode
9
+ - jruby-head
10
+ - rbx-18mode
11
+ - rbx-19mode
12
+ script: rake test
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in strikeiron.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Drew Tempelmeyer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,71 @@
1
+ # strikeiron
2
+
3
+ strikeiron uses the Strikeiron Online Sales Tax API to calculate online sales tax to alleviate maintaining tax codes.
4
+
5
+ For more information on Strikeiron, Inc., go to http://www.strikeiron.com/.
6
+
7
+ [![Build Status](https://secure.travis-ci.org/drewtempelmeyer/strikeiron.png)](http://travis-ci.org/drewtempelmeyer/strikeiron)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'strikeiron'
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install strikeiron
22
+
23
+ ## Usage
24
+
25
+ Usage of strikeiron requires access to Strikeiron Online Sales Tax API.
26
+
27
+ #### 1. Configure the client
28
+
29
+ Strikeiron.configure do |config|
30
+ config.user_id = 'your_strikeiron_user_id'
31
+ config.password = 'your_password'
32
+ end
33
+
34
+ #### 2. Obtaining a list of categories
35
+
36
+ Performing the category lookup does not count against your hits. For performance reasons, I recommend you keep a cached copy. A database is a good start.
37
+
38
+ categories = Strikeiron.tax_categories
39
+ # => [{:category=>"Alcohol", :category_id=>"01051000"}, {:category=>"Alcoholic Beverages", :category_id=>"01050000"}, {:category=>"Beer", :category_id=>"01052000"}, {:category=>"Books", :category_id=>"01501500"}, {:category=>"Charges necessary to complete sale other than delivery and installation", :category_id=>"04800000"}, ...]
40
+
41
+ #### 3. Calculate sales tax
42
+
43
+ from = Strikeiron::Address.new(:street_address => 'One Microsoft Way', :city => 'Redmond', :state => 'WA', :zip_code => '98052')
44
+ to = Strikeiron::Address.new(:street_address => '902 Broadway', :city => 'New York', :state => 'NY', :zip_code => '10010')
45
+ items = [
46
+ Strikeiron::TaxValue.new(:category_id => '01151605', :amount => 239.41),
47
+ Strikeiron::TaxValue.new(:category_id => '01151605', :amount => 239.41)
48
+ ]
49
+
50
+ response = Strikeiron.sales_tax(
51
+ :from => from,
52
+ :to => to,
53
+ :tax_values => items
54
+ )
55
+ # => #<Strikeiron::TaxResult @from=#<Strikeiron::Address @street_address="ONE MICROSOFT WAY", @city="REDMOND", @state="WA", @zip_code="98052">, @to=#<Strikeiron::Address @street_address="902 BROADWAY", @city="NEW YORK", @state="NY", @zip_code="10010-6041">, @tax_values=[#<Strikeiron::TaxValue @category="Computer Software (both prewritten and non-prewritten) delivered electronically", @category_id="01151605", @tax_amount=21.25, @jurisdictions=[#<Strikeiron::Jurisdiction @fips="36", @name="New York", @tax_amount=9.57>, #<Strikeiron::Jurisdiction @fips="CTD51000", @name="METRO COMMUTER TRANS. DISTRICT", @tax_amount=0.89>, #<Strikeiron::Jurisdiction @fips="061", @name="NEW YORK", @tax_amount=10.77>]>, #<Strikeiron::TaxValue @category="Computer Software (both prewritten and non-prewritten) delivered electronically", @category_id="01151605", @tax_amount=21.25, @jurisdictions=[#<Strikeiron::Jurisdiction @fips="36", @name="New York", @tax_amount=9.57>, #<Strikeiron::Jurisdiction @fips="CTD51000", @name="METRO COMMUTER TRANS. DISTRICT", @tax_amount=0.89>, #<Strikeiron::Jurisdiction @fips="061", @name="NEW YORK", @tax_amount=10.77>]>], @total_tax=42.5>
56
+
57
+ #### 4. Profit
58
+
59
+ Hopefully.
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create new Pull Request
68
+
69
+ ## Furthermore
70
+
71
+ I am in no way associated with Strikeiron.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+ require "rdoc/task"
5
+
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'lib' << 'test'
8
+ test.pattern = 'test/test_*.rb'
9
+ test.verbose = false
10
+ end
11
+
12
+ RDoc::Task.new do |rd|
13
+ README = 'README.md'
14
+ rd.main = README
15
+ rd.rdoc_dir = 'doc'
16
+ rd.title = 'strikeiron'
17
+ rd.rdoc_files.include(README, 'lib/**/*.rb')
18
+ end
@@ -0,0 +1,109 @@
1
+ require "savon"
2
+ require "strikeiron/version"
3
+ require "strikeiron/configuration"
4
+ require "strikeiron/address"
5
+ require "strikeiron/tax_value"
6
+ require "strikeiron/jurisdiction"
7
+ require "strikeiron/tax_result"
8
+
9
+ # Strikeiron calculates online sales tax for your online service based on your local tax rules.
10
+ module Strikeiron
11
+
12
+ # The location of the Strikeiron Online Sales Tax WSDL
13
+ WSDL = 'https://wsparam.strikeiron.com/SpeedTaxSalesTax3?WSDL'
14
+
15
+ class << self
16
+ attr_accessor :configuration
17
+
18
+ # Configure Strikeiron for your account. See Configuration for more information.
19
+ def configure
20
+ self.configuration ||= Configuration.new
21
+ yield configuration
22
+ end
23
+
24
+ # Singleton Savon client
25
+ def client
26
+ @@client ||= Savon::Client.new do
27
+ wsdl.document = WSDL
28
+ end
29
+ end
30
+
31
+ # Get the calculated online sales tax for a product or service
32
+ #
33
+ # === Options
34
+ # * <tt>from</tt> - The origin Address (or your physical location). Required.
35
+ # * <tt>to</tt> - The destination Address of the customer. Required.
36
+ # * <tt>tax_values</tt> - An array of TaxValueRequest objects. Required.
37
+ def sales_tax(options = {})
38
+ options = options.inject({}) { |memo,(k, v)| memo[k.to_sym] = v; memo }
39
+ required_options = %w(from to tax_values)
40
+
41
+ # Raise an error if the required option is not defined
42
+ required_options.each { |val| raise ArgumentError, "Missing option :#{val}" if !options.include?(val.to_sym) }
43
+
44
+ response = client.request(:get_sales_tax_value) do |soap|
45
+ soap.body = {
46
+ 'UserID' => configuration.user_id,
47
+ 'Password' => configuration.password,
48
+ 'ShipFrom' => options[:from].to_soap,
49
+ 'ShipTo' => options[:to].to_soap,
50
+ 'TaxValueRequests' => { 'TaxValueRequest' => options[:tax_values].map(&:to_soap) }
51
+ }
52
+ end
53
+
54
+ response_code = response[:get_sales_tax_value_response][:get_sales_tax_value_result][:service_status][:status_nbr]
55
+
56
+ # Raise exceptions if there was an error when calculating the tax
57
+ case response_code.to_i
58
+ when 401
59
+ raise RuntimeError, 'Invalid From address.'
60
+ when 402
61
+ raise RuntimeError, 'Invalid To address.'
62
+ when 403
63
+ raise RuntimeError, 'Invalid Taxability category.'
64
+ when 500
65
+ raise RuntimeError, 'Internal Strikeiron server error.'
66
+ end
67
+
68
+ Strikeiron::TaxResult.from_soap(response[:get_sales_tax_value_response][:get_sales_tax_value_result][:service_result])
69
+ end
70
+
71
+ # Performs a request to obtain a list of all available category names and their corresponding ID number.
72
+ #
73
+ # === Example
74
+ # Strikeiron.tax_categories
75
+ # # => [{:category=>"Alcohol", :category_id=>"01051000"}, {:category=>"Alcoholic Beverages", :category_id=>"01050000"}, {:category=>"Beer", :category_id=>"01052000"}, {:category=>"Books", :category_id=>"01501500"}, {:category=>"Charges necessary to complete sale other than delivery and installation", :category_id=>"04800000"}]
76
+ def tax_categories
77
+ response = client.request(:get_sales_tax_categories) do |soap|
78
+ soap.body = {
79
+ 'UserID' => configuration.user_id,
80
+ 'Password' => configuration.password
81
+ }
82
+ end
83
+
84
+ # Return an empty array if the response was not successful
85
+ return [] if response[:get_sales_tax_categories_response][:get_sales_tax_categories_result][:service_status][:status_nbr] != '200'
86
+
87
+ response[:get_sales_tax_categories_response][:get_sales_tax_categories_result][:service_result][:sales_tax_category]
88
+ end
89
+
90
+ # The number of API hits remanining for the configured account
91
+ #
92
+ # === Example
93
+ # Strikeiron.remaining_hits
94
+ # # => 100
95
+ def remaining_hits
96
+ response = client.request(:get_remaining_hits) do |soap|
97
+ soap.body = {
98
+ 'UserID' => configuration.user_id,
99
+ 'Password' => configuration.password
100
+ }
101
+ end
102
+ response[:si_subscription_info][:remaining_hits].to_i
103
+ end
104
+
105
+
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,47 @@
1
+ module Strikeiron #:nodoc:
2
+
3
+ # An Address stores information of a physical location for both the seller and customer.
4
+ #
5
+ # === Attributes
6
+ # * <tt>street_address</tt> - The full street address for the seller/customer. Example: 123 Sixth Avenue.
7
+ # * <tt>city</tt> - The city of the address.
8
+ # * <tt>state</tt> - The two letter abbreviation for the state. Example: NY.
9
+ # * <tt>zip_code</tt> - The postal/ZIP code of the address.
10
+ class Address
11
+
12
+ attr_accessor :street_address, :city, :state, :zip_code
13
+
14
+ # Creates an Address with the supplied attributes.
15
+ def initialize(default_values = {})
16
+ safe_keys = %w(street_address city state zip_code)
17
+ # Only permit the keys defined in safe_keys
18
+ default_values.reject! { |key, value| !safe_keys.include?(key.to_s) }
19
+
20
+ default_values.each { |key, value| self.send "#{key}=", value }
21
+ end
22
+
23
+ # Convert the object to a Hash for SOAP
24
+ def to_soap
25
+ {
26
+ 'StreetAddress' => street_address,
27
+ 'City' => city,
28
+ 'State' => state,
29
+ 'ZIPCode' => zip_code
30
+ }
31
+ end
32
+
33
+ class << self
34
+ # Convert the object from a SOAP response to an Address object
35
+ def from_soap(hash = {})
36
+ default_values = {
37
+ :street_address => hash['StreetAddress'],
38
+ :city => hash['City'],
39
+ :state => hash['State'],
40
+ :zip_code => hash['ZIPCode']
41
+ }
42
+ new(default_values)
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,10 @@
1
+ module Strikeiron #:nodoc:
2
+ # Configuration values for Strikeiron such as your UserID and Password.
3
+ #
4
+ # === Attributes
5
+ # * <tt>user_id</tt> - The UserID supplied by Strikeiron for API use.
6
+ # * <tt>password</tt> - The password/key that Strikeiron supplied for API use.
7
+ class Configuration
8
+ attr_accessor :user_id, :password
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ module Strikeiron #:nodoc:
2
+ # Jurisdiction represents a breakdown of the tax calculations for the taxable region(s).
3
+ #
4
+ # === Attributes
5
+ # * <tt>fips</tt> - The code assigned by the Federal Government or state agencies to uniquely identify a location.
6
+ # * <tt>name</tt> - Name associated to the corresponding FIPS code.
7
+ # * <tt>tax_amount</tt> - Sales tax rate for the corresponding FIPS code.
8
+ class Jurisdiction
9
+ attr_accessor :fips, :name, :tax_amount
10
+
11
+ # Creates a Jurisdiction with the supplied attributes.
12
+ def initialize(default_values = {})
13
+ safe_keys = %w(fips name tax_amount)
14
+ # Only permit the keys defined in safe_keys
15
+ default_values.reject! { |key, value| !safe_keys.include?(key.to_s) }
16
+
17
+ default_values.each { |key, value| self.send "#{key}=", value }
18
+ end
19
+
20
+ class << self
21
+ # Convert the SOAP response object to a Jurisdiction
22
+ def from_soap(hash = {})
23
+ default_values = {
24
+ :fips => hash['FIPS'],
25
+ :name => hash['Name'],
26
+ :tax_amount => hash['SalesTaxAmount']
27
+ }
28
+
29
+ new(default_values)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,39 @@
1
+ module Strikeiron #:nodoc:
2
+ # Returned after performing a tax calculation
3
+ #
4
+ # === Attributes
5
+ # * <tt>from</tt> - The from Address.
6
+ # * <tt>to</tt> - The to Address.
7
+ # * <tt>tax_values</tt> - The TaxValue objects returned from Strikeiron.
8
+ # * <tt>total_tax</tt> - The total tax amount to be applied.
9
+ class TaxResult
10
+ attr_accessor :from, :to, :tax_values, :total_tax
11
+
12
+ # Initialize the TaxResult object with the given options
13
+ def initialize(default_values = {})
14
+ default_values.each { |key, value| self.send "#{key}=", value }
15
+ end
16
+
17
+ class << self
18
+ # Convert the object from the Strikeiron response
19
+ def from_soap(response)
20
+ tax_values = []
21
+ response[:results][:tax_value_record].each do |record|
22
+ tax_values << TaxValue.new(
23
+ :category => record[:category],
24
+ :category_id => record[:category_id],
25
+ :tax_amount => record[:sales_tax_amount].to_f,
26
+ :jurisdictions => record[:jurisdictions][:sales_tax_value_jurisdiction].map { |j| Jurisdiction.new(:fips => j[:fips], :name => j[:name], :tax_amount => j[:sales_tax_amount].to_f) }
27
+ )
28
+ end
29
+
30
+ new(
31
+ :from => Address.new(response[:resolved_from_address]),
32
+ :to => Address.new(response[:resolved_to_address]),
33
+ :tax_values => tax_values,
34
+ :total_tax => tax_values.inject(0) { |sum, tax_value| sum + tax_value.tax_amount }
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ module Strikeiron #:nodoc:
2
+ # Total sales tax rate based on the dollar amount and category from TaxValueRequest.
3
+ # TaxValueRecord should only be rendered from a SOAP response.
4
+ #
5
+ # === Attributes
6
+ # * <tt>category</tt> - The corresponding category name. A category name and/or ID must be supplied.
7
+ # * <tt>category_id</tt> - The corresponding category ID. A category name and/or ID must be supplied.
8
+ # * <tt>amount</tt> - The amount to calculate the tax for.
9
+ # * <tt>tax_amount</tt> - The combined sales tax rate based on the address and tax category provided
10
+ #
11
+ # === Notes
12
+ # See Strikeiron.tax_categories for information on obtaining category information.
13
+ class TaxValue
14
+ attr_accessor :category, :category_id, :amount, :tax_amount, :jurisdictions
15
+
16
+ # Creates a TaxValueRecord with the supplied attributes.
17
+ def initialize(default_values = {})
18
+ safe_keys = %w(category category_id amount tax_amount jurisdictions)
19
+ # Only permit the keys defined in safe_keys
20
+ default_values.reject! { |key, value| !safe_keys.include?(key.to_s) }
21
+
22
+ default_values.each { |key, value| self.send "#{key}=", value }
23
+ end
24
+
25
+ # Convert the object to be valid for the SOAP request
26
+ def to_soap
27
+ {
28
+ 'SalesTaxCategoryOrCategoryID' => category || category_id,
29
+ 'Amount' => amount
30
+ }
31
+ end
32
+
33
+ class << self
34
+ # Convert the SOAP response object to a TaxValueRecord
35
+ def from_soap(hash = {})
36
+ default_values = {
37
+ :category => hash['Category'],
38
+ :category_id => hash['CategoryID'],
39
+ :tax_amount => hash['SalesTaxAmount']
40
+ }
41
+
42
+ new(default_values)
43
+ end
44
+ end
45
+ end
46
+ end