usps 0.1.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 (60) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +56 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/lib/usps.rb +45 -0
  8. data/lib/usps/address.rb +72 -0
  9. data/lib/usps/client.rb +56 -0
  10. data/lib/usps/configuration.rb +16 -0
  11. data/lib/usps/errors.rb +46 -0
  12. data/lib/usps/request.rb +13 -0
  13. data/lib/usps/request/address_standardization.rb +44 -0
  14. data/lib/usps/request/base.rb +42 -0
  15. data/lib/usps/request/city_and_state_lookup.rb +32 -0
  16. data/lib/usps/request/delivery_confirmation.rb +88 -0
  17. data/lib/usps/request/delivery_confirmation_certify.rb +10 -0
  18. data/lib/usps/request/tracking_lookup.rb +28 -0
  19. data/lib/usps/request/zip_code_lookup.rb +45 -0
  20. data/lib/usps/response.rb +7 -0
  21. data/lib/usps/response/address_standardization.rb +38 -0
  22. data/lib/usps/response/base.rb +13 -0
  23. data/lib/usps/response/city_and_state_lookup.rb +28 -0
  24. data/lib/usps/response/delivery_confirmation.rb +25 -0
  25. data/lib/usps/response/tracking_lookup.rb +19 -0
  26. data/lib/usps/test.rb +34 -0
  27. data/lib/usps/test/address_verification.rb +31 -0
  28. data/lib/usps/test/city_and_state_lookup.rb +19 -0
  29. data/lib/usps/test/delivery_confirmation.rb +59 -0
  30. data/lib/usps/test/tracking_lookup.rb +29 -0
  31. data/lib/usps/test/zip_code_lookup.rb +37 -0
  32. data/spec/address_spec.rb +65 -0
  33. data/spec/configuration_spec.rb +19 -0
  34. data/spec/data/address_standardization_1.xml +1 -0
  35. data/spec/data/address_standardization_2.xml +1 -0
  36. data/spec/data/city_and_state_lookup_1.xml +1 -0
  37. data/spec/data/city_and_state_lookup_2.xml +1 -0
  38. data/spec/data/delivery_confirmation_1.xml +1 -0
  39. data/spec/data/delivery_confirmation_2.xml +1 -0
  40. data/spec/data/tracking_lookup_1.xml +1 -0
  41. data/spec/data/tracking_lookup_2.xml +1 -0
  42. data/spec/request/address_standardization_spec.rb +45 -0
  43. data/spec/request/base_spec.rb +16 -0
  44. data/spec/request/city_and_state_lookup_spec.rb +32 -0
  45. data/spec/request/delivery_confirmation_certify_spec.rb +11 -0
  46. data/spec/request/delivery_confirmation_spec.rb +57 -0
  47. data/spec/request/signature_confirmation_spec.rb +0 -0
  48. data/spec/request/tracking_spec.rb +21 -0
  49. data/spec/request/zip_code_lookup_spec.rb +42 -0
  50. data/spec/response/address_standardization_spec.rb +54 -0
  51. data/spec/response/base_spec.rb +0 -0
  52. data/spec/response/city_and_state_lookup_spec.rb +27 -0
  53. data/spec/response/delivery_confirmation_spec.rb +43 -0
  54. data/spec/response/signature_confirmation_spec.rb +0 -0
  55. data/spec/response/tracking_lookup_spec.rb +27 -0
  56. data/spec/spec.opts +1 -0
  57. data/spec/spec_helper.rb +25 -0
  58. data/spec/usps_spec.rb +8 -0
  59. data/usps.gemspec +129 -0
  60. metadata +178 -0
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Chris Gaffney
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ = usps
2
+
3
+ Ruby API for accessing the USPS WebTools API found here: http://www.usps.com/webtools/technical.htm
4
+
5
+ Usage of this library assumes you already have a USPS API account and that all priviledges have been granted.
6
+
7
+ == Exposed API Calls
8
+
9
+ The following USPS API calls are currently exposed through this library:
10
+
11
+ <AddressValidateRequest> -- USPS::Request::AddressStandardization
12
+ <CityStateLookupRequest> -- USPS::Request::CityAndStateLookup
13
+ <ZipCodeLookupRequest> -- USPS::Request::ZipCodeLookup
14
+ <TrackRequest> -- USPS::Request::TrackingLookup
15
+
16
+ <DeliveryConfirmationV3.0Request> -- USPS::Request::DeliveryConfirmation (for production)
17
+ <DeliveryConfirmCertifyV3.0Request> -- USPS::Request::DeliveryConfirmationCertify (for testing)
18
+
19
+ == How to use?
20
+
21
+ Using the library is as simple as building a new USPS::Request::[type] object, calling #send! and using the response.
22
+ For example, to send a tracking request you'd do the following:
23
+
24
+ request = USPS::Request::TrackingLookup.new(tracking_number)
25
+ response = request.send!
26
+
27
+ response.summary
28
+ response.details
29
+
30
+ The library assumes that either ENV['USPS_USER'] is set, or that you set USPS.username to your USPS API username.
31
+
32
+ See the individual USPS::Request classes for details on how to use them.
33
+
34
+ == USPS API Certification
35
+
36
+ Part of the process of setting up an account with the USPS API is to run certain tests against the USPS API.
37
+ This library has all the requisite tests built in, runnable with a simple
38
+
39
+ USPS_USER="[username]" rake certify
40
+
41
+ If any of the tests fail, you don't have access to that API and may need to work with USPS to fix.
42
+
43
+ == Note on Patches/Pull Requests
44
+
45
+ * Fork the project.
46
+ * Make your feature addition or bug fix.
47
+ * Add tests for it. This is important so I don't break it in a
48
+ future version unintentionally.
49
+ * Commit, do not mess with rakefile, version, or history.
50
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
51
+ * Send me a pull request. Bonus points for topic branches.
52
+
53
+
54
+ == Copyright
55
+
56
+ Copyright (c) 2010 Chris Gaffney. See LICENSE for details.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "usps"
9
+ gem.summary = "USPS Webtools API for Ruby"
10
+ gem.description = "USPS Webtools API for Ruby."
11
+ gem.email = "gaffneyc@gmail.com"
12
+ gem.homepage = "http://github.com/gaffneyc/usps"
13
+ gem.authors = ["Chris Gaffney"]
14
+
15
+ gem.add_dependency 'builder', '>= 2.1.2'
16
+ gem.add_dependency 'nokogiri', '>= 1.4.1'
17
+ gem.add_dependency 'typhoeus', '>= 0.1.18'
18
+
19
+ gem.add_development_dependency "mocha", ">= 0.9.8"
20
+ gem.add_development_dependency "rspec", ">= 1.3.0"
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ require 'spec/rake/spectask'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :spec => :check_dependencies
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "usps #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
53
+ desc "Run the certification tests against USPS's API. Requires ENV['USPS_USER'] to be set or passed in."
54
+ task :certify do
55
+ ruby "-rubygems -Ilib -rusps lib/usps/test.rb"
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,45 @@
1
+ require 'builder'
2
+ require 'nokogiri'
3
+
4
+ module USPS
5
+ require 'usps/errors'
6
+ require 'usps/configuration'
7
+
8
+ autoload :Client, 'usps/client'
9
+ autoload :Address, 'usps/address'
10
+ autoload :Request, 'usps/request'
11
+ autoload :Response, 'usps/response'
12
+
13
+ class << self
14
+ attr_writer :config
15
+
16
+ def client
17
+ @client ||= Client.new
18
+ end
19
+
20
+ def testing=(val)
21
+ config.testing = val
22
+ end
23
+
24
+ def config
25
+ @config ||= Configuration.new
26
+ end
27
+
28
+ def configure(&block)
29
+ block.call(self.config)
30
+ end
31
+
32
+ # These two methods are currently here for backwards compatability
33
+ def username=(user)
34
+ config.username = user
35
+ end
36
+
37
+ def username
38
+ config.username
39
+ end
40
+
41
+ def get_city_and_state_for_zip(zip)
42
+ USPS::Request::CityAndStateLookup.new(zip).send!.get(zip)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,72 @@
1
+ # TODO: Documentation
2
+ #
3
+ # The USPS API uses a standard where Address2 is the street adress and Address1 is
4
+ # the apartment, suite, etc... I have switched them to match how I see them on an envelope.
5
+ # Additionally they are refered to address and extra_address though both address1 and address2
6
+ # work. Just remember they are flip flopped based on the USPS documentation.
7
+ class USPS::Address < Struct.new(:name, :company, :address1, :address2, :city, :state, :zip5, :zip4)
8
+
9
+ # Alias address getters and setters for a slightly more expressive api
10
+ alias :address :address1
11
+ alias :address= :address1=
12
+ alias :extra_address :address2
13
+ alias :extra_address= :address2=
14
+
15
+ # The USPS always refers to company as firm
16
+ alias :firm :company
17
+ alias :firm= :company=
18
+
19
+ attr_reader :error
20
+
21
+ def initialize(options = {}, &block)
22
+ options.each_pair do |k, v|
23
+ self.send("#{k}=", v)
24
+ end
25
+
26
+ block.call(self) if block
27
+ end
28
+
29
+ def zip
30
+ zip4 ? "#{zip5}-#{zip4}" : zip5.to_s
31
+ end
32
+
33
+ # Sets zip5 and zip4 if given a zip code in the format "99881" or "99881-1234"
34
+ def zip=(val)
35
+ self.zip5, self.zip4 = val.to_s.split('-')
36
+ end
37
+
38
+ # Check with the USPS if this address can be verified and will in missing
39
+ # fields (such as zip code) if they are available.
40
+ def valid?
41
+ @error = nil
42
+ standardize
43
+ true
44
+ rescue USPS::Error => e
45
+ @error = e
46
+ false
47
+ end
48
+
49
+ def standardize
50
+ response = USPS::Request::AddressStandardization.new(self).send!
51
+ response[self]
52
+ end
53
+
54
+ def standardize!
55
+ replace(self.standardize)
56
+ end
57
+
58
+ # Similar to Hash#replace, overwrite the values of this object with the other.
59
+ # It will not replace a provided key on the original object that does not exist
60
+ # on the replacing object (such as name with verification requests).
61
+ def replace(other)
62
+ raise ArgumentError unless other.is_a?(USPS::Address)
63
+
64
+ other.each_pair do |key, val|
65
+ # Do not overwrite values that may exist on the original but not on
66
+ # the replacement.
67
+ self[key] = val unless val.nil?
68
+ end
69
+
70
+ self
71
+ end
72
+ end
@@ -0,0 +1,56 @@
1
+ require 'typhoeus'
2
+
3
+ module USPS
4
+ class Client
5
+ def request(request, &block)
6
+ server = server(request)
7
+
8
+ # The USPS documentation shows all requests as being GET requests, but
9
+ # the servers also appear to support POST. We're using POST here so we
10
+ # don't need to escape the request and to keep from running into any
11
+ # concerns with data length.
12
+ response = Typhoeus::Request.post(server, {
13
+ :timeout => USPS.config.timeout,
14
+ :params => {
15
+ "API" => request.api,
16
+ "XML" => request.build
17
+ }
18
+ })
19
+
20
+ # Parse the request
21
+ xml = Nokogiri::XML.parse(response.body)
22
+
23
+ # Process and raise errors
24
+ if((error = xml.search('Error')).any?)
25
+ # This is where things get a little tricky. There are a bunch of errors
26
+ # that the USPS can send back.
27
+ why = error.search('Description').text
28
+ code = error.search('Number').text
29
+ source = error.search('Source').text
30
+
31
+ raise Error.for_code(code).new(why, code, source)
32
+ end
33
+
34
+ # Initialize the proper response object and parse the message
35
+ request.response_for(xml)
36
+ end
37
+
38
+ def testing?
39
+ USPS.config.testing
40
+ end
41
+
42
+ private
43
+ def server(request)
44
+ dll = testing? ? "ShippingAPITest.dll" : "ShippingAPI.dll"
45
+
46
+ case
47
+ when request.secure?
48
+ "https://secure.shippingapis.com/#{dll}"
49
+ when testing?
50
+ "http://testing.shippingapis.com/#{dll}"
51
+ else
52
+ "http://production.shippingapis.com/#{dll}"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ module USPS
2
+ # Configuration options:
3
+ # username: Provided by the USPS during registration
4
+ # timeout: Connection timeout in milliseconds, nil to disable
5
+ # testing: Should requests be done against the testing service or not
6
+ # (only specific requests are supported).
7
+ class Configuration < Struct.new(:username, :timeout, :testing)
8
+ def initialize
9
+ self.timeout = 5000
10
+ self.testing = false
11
+ self.username = ENV['USPS_USER']
12
+ end
13
+
14
+ alias :testing? :testing
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ module USPS
2
+ # Error Hierarchy
3
+ #
4
+ # StandardError
5
+ # USPS::Error
6
+ # USPS::AuthorizationError
7
+ # USPS::ValidationError
8
+ # USPS::InvalidCityError
9
+ # USPS::InvalidStateError
10
+ # USPS::AddressNotFoundError
11
+ # USPS::MultipleAddressError
12
+ # USPS::InvalidImageTypeError
13
+ class Error < StandardError
14
+ attr_reader :number, :source
15
+
16
+ def initialize(message, number, source)
17
+ super(message)
18
+
19
+ @number = number
20
+ @source = source
21
+ end
22
+
23
+ class << self
24
+ def for_code(code)
25
+ case code
26
+ when '80040b1a' ; AuthorizationError
27
+ when '-2147219400'; InvalidCityError
28
+ when '-2147219401'; AddressNotFoundError
29
+ when '-2147219402'; InvalidStateError
30
+ when '-2147219403'; MultipleAddressError
31
+ when '-2147218900'; InvalidImageTypeError
32
+ else ; Error
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ class AuthorizationError < Error; end
39
+
40
+ class ValidationError < Error; end
41
+ class InvalidCityError < ValidationError; end
42
+ class InvalidStateError < ValidationError; end
43
+ class AddressNotFoundError < ValidationError; end
44
+ class MultipleAddressError < ValidationError; end
45
+ class InvalidImageTypeError < ValidationError; end
46
+ end
@@ -0,0 +1,13 @@
1
+ module USPS::Request
2
+ autoload :Base, 'usps/request/base'
3
+ autoload :ZipCodeLookup, 'usps/request/zip_code_lookup'
4
+ autoload :CityAndStateLookup, 'usps/request/city_and_state_lookup'
5
+ autoload :AddressStandardization, 'usps/request/address_standardization'
6
+
7
+ # Delivery and Signature confirmation.
8
+ # DeliveryConfirmationCertify and SignatureConfirmationCertify should be used for testing
9
+ autoload :DeliveryConfirmation, 'usps/request/delivery_confirmation'
10
+ autoload :DeliveryConfirmationCertify, 'usps/request/delivery_confirmation_certify'
11
+
12
+ autoload :TrackingLookup, 'usps/request/tracking_lookup'
13
+ end
@@ -0,0 +1,44 @@
1
+ module USPS::Request
2
+ # TODO: #send! could be made smarter to send lookup batches
3
+ class AddressStandardization < Base
4
+ config(
5
+ :api => 'Verify',
6
+ :tag => 'AddressValidateRequest',
7
+ :secure => false,
8
+ :response => USPS::Response::AddressStandardization
9
+ )
10
+
11
+ # At most 5 addresses can be verified
12
+ def initialize(*addresses)
13
+ @addresses = addresses.flatten
14
+
15
+ if @addresses.size > 5
16
+ raise ArgumentError, 'at most 5 addresses can be verified at a time'
17
+ end
18
+ end
19
+
20
+ def response_for(xml)
21
+ self.class.response.new(@addresses, xml)
22
+ end
23
+
24
+ def build
25
+ super do |builder|
26
+ @addresses.each_with_index do |addy, i|
27
+ builder.tag!('Address', :ID => i) do
28
+ builder.tag!('FirmName', addy.firm)
29
+
30
+ # Address fields are swapped in the USPS API
31
+ builder.tag!('Address1', addy.extra_address)
32
+ builder.tag!('Address2', addy.address)
33
+
34
+ builder.tag!('City', addy.city)
35
+ builder.tag!('State', addy.state)
36
+
37
+ builder.tag!('Zip5', addy.zip5)
38
+ builder.tag!('Zip4', addy.zip4)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end