usps-fork 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +89 -0
  4. data/Rakefile +15 -0
  5. data/lib/usps.rb +47 -0
  6. data/lib/usps/address.rb +72 -0
  7. data/lib/usps/client.rb +55 -0
  8. data/lib/usps/configuration.rb +17 -0
  9. data/lib/usps/errors.rb +46 -0
  10. data/lib/usps/request.rb +14 -0
  11. data/lib/usps/request/address_standardization.rb +44 -0
  12. data/lib/usps/request/base.rb +42 -0
  13. data/lib/usps/request/city_and_state_lookup.rb +32 -0
  14. data/lib/usps/request/delivery_confirmation.rb +88 -0
  15. data/lib/usps/request/delivery_confirmation_certify.rb +10 -0
  16. data/lib/usps/request/tracking_field_lookup.rb +28 -0
  17. data/lib/usps/request/tracking_lookup.rb +28 -0
  18. data/lib/usps/request/zip_code_lookup.rb +45 -0
  19. data/lib/usps/response.rb +8 -0
  20. data/lib/usps/response/address_standardization.rb +52 -0
  21. data/lib/usps/response/base.rb +13 -0
  22. data/lib/usps/response/city_and_state_lookup.rb +42 -0
  23. data/lib/usps/response/delivery_confirmation.rb +25 -0
  24. data/lib/usps/response/tracking_field_lookup.rb +34 -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/lib/usps/track_detail.rb +49 -0
  33. data/lib/usps/version.rb +3 -0
  34. data/spec/address_spec.rb +65 -0
  35. data/spec/configuration_spec.rb +19 -0
  36. data/spec/data/address_standardization_1.xml +1 -0
  37. data/spec/data/address_standardization_2.xml +1 -0
  38. data/spec/data/city_and_state_lookup_1.xml +1 -0
  39. data/spec/data/city_and_state_lookup_2.xml +1 -0
  40. data/spec/data/delivery_confirmation_1.xml +1 -0
  41. data/spec/data/delivery_confirmation_2.xml +1 -0
  42. data/spec/data/tracking_field_lookup.xml +41 -0
  43. data/spec/data/tracking_field_lookup_2.xml +17 -0
  44. data/spec/data/tracking_lookup_1.xml +1 -0
  45. data/spec/data/tracking_lookup_2.xml +1 -0
  46. data/spec/request/address_standardization_spec.rb +45 -0
  47. data/spec/request/base_spec.rb +16 -0
  48. data/spec/request/city_and_state_lookup_spec.rb +32 -0
  49. data/spec/request/delivery_confirmation_certify_spec.rb +11 -0
  50. data/spec/request/delivery_confirmation_spec.rb +57 -0
  51. data/spec/request/tracking_field_spec.rb +20 -0
  52. data/spec/request/tracking_spec.rb +21 -0
  53. data/spec/request/zip_code_lookup_spec.rb +42 -0
  54. data/spec/response/address_standardization_spec.rb +54 -0
  55. data/spec/response/base_spec.rb +0 -0
  56. data/spec/response/city_and_state_lookup_spec.rb +27 -0
  57. data/spec/response/delivery_confirmation_spec.rb +43 -0
  58. data/spec/response/tracking_field_lookup_spec.rb +29 -0
  59. data/spec/response/tracking_lookup_spec.rb +27 -0
  60. data/spec/spec.opts +1 -0
  61. data/spec/spec_helper.rb +13 -0
  62. data/spec/track_detail_spec.rb +51 -0
  63. data/spec/usps_spec.rb +8 -0
  64. metadata +147 -0
@@ -0,0 +1,32 @@
1
+ module USPS::Request
2
+ # All the address information APIs are essentially identical
3
+ class CityAndStateLookup < Base
4
+ config(
5
+ :api => 'CityStateLookup',
6
+ :tag => 'CityStateLookupRequest',
7
+ :secure => false,
8
+ :response => USPS::Response::CityAndStateLookup
9
+ )
10
+
11
+ # Given a list of zip codes, looks up what city and state they are associated with.
12
+ #
13
+ # The USPS api is only capable of handling at most 5 zip codes per request.
14
+ def initialize(*zip_codes)
15
+ @zip_codes = zip_codes.flatten
16
+
17
+ if(@zip_codes.size > 5)
18
+ raise ArgumentError, 'at most 5 lookups can be performed per request'
19
+ end
20
+ end
21
+
22
+ def build
23
+ super do |builder|
24
+ @zip_codes.each_with_index do |zip, i|
25
+ builder.tag!('ZipCode', :ID => i) do
26
+ builder.tag!('Zip5', zip)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ module USPS::Request
2
+ class DeliveryConfirmation < Base
3
+ config(
4
+ :api => 'DeliveryConfirmationV3',
5
+ :tag => 'DeliveryConfirmationV3.0Request',
6
+ :secure => true,
7
+ :response => USPS::Response::DeliveryConfirmation
8
+ )
9
+
10
+ attr_reader :to, :from, :weight, :options, :format
11
+
12
+ DEFAULTS = {
13
+ :type => 1,
14
+ :format => 'TIF',
15
+ :service => 'Priority'
16
+ }.freeze
17
+
18
+ # List of valid options and their mapping to their tag
19
+ OPTIONS = {
20
+ :type => 'Option',
21
+ :service => 'ServiceType',
22
+ :po_zip_code => 'PoZipCode',
23
+ :label_date => 'LabelDate',
24
+ :customer_reference => 'CustomerRefNo',
25
+ :separate_receipt => 'SeparateReceiptPage',
26
+ :address_service => 'AddressServiceRequested',
27
+ :sender_name => 'SenderName',
28
+ :sender_email => 'SenderEMail',
29
+ :recipient_name => 'RecipientName',
30
+ :recipient_email => 'RecipientEMail'
31
+ }.freeze
32
+
33
+ FORMATS = %w(TIF PDF).freeze
34
+
35
+ # === Options:
36
+ # * <tt>:
37
+ def initialize(to, from, weight, options = {})
38
+ @to = to
39
+ @from = from
40
+ @weight = weight
41
+ @options = DEFAULTS.merge(options)
42
+
43
+ @type = @options.delete(:type)
44
+ self.format = @options.delete(:format)
45
+ end
46
+
47
+ def format=(format)
48
+ format = format.upcase
49
+
50
+ unless(FORMATS.include?(format))
51
+ raise ArgumentError, "Format must be one of #{FORMATS.join(',')}"
52
+ end
53
+
54
+ @format = format.upcase
55
+ end
56
+
57
+ def build
58
+ super do |builder|
59
+ builder.tag!('Option', @type)
60
+ builder.tag!('ImageParameters')
61
+
62
+ [
63
+ [self.from, 'From'],
64
+ [self.to, 'To']
65
+ ].each do |address, prefix|
66
+ builder.tag!("#{prefix}Name", address.name)
67
+ builder.tag!("#{prefix}Firm", address.company)
68
+ builder.tag!("#{prefix}Address1", address.extra_address)
69
+ builder.tag!("#{prefix}Address2", address.address)
70
+ builder.tag!("#{prefix}City", address.city)
71
+ builder.tag!("#{prefix}State", address.state)
72
+ builder.tag!("#{prefix}Zip5", address.zip5)
73
+ builder.tag!("#{prefix}Zip4", address.zip4)
74
+ end
75
+
76
+ builder.tag!('WeightInOunces', self.weight)
77
+
78
+ @options.each_pair do |k,v|
79
+ OPTIONS[k].tap do |tag|
80
+ builder.tag!(tag, v.to_s) if tag
81
+ end
82
+ end
83
+
84
+ builder.tag!('ImageType', @format)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,10 @@
1
+ module USPS::Request
2
+ class DeliveryConfirmationCertify < USPS::Request::DeliveryConfirmation
3
+ config(
4
+ :api => 'DelivConfirmCertifyV3',
5
+ :tag => 'DelivConfirmCertifyV3.0Request',
6
+ :secure => true,
7
+ :response => USPS::Response::DeliveryConfirmation
8
+ )
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module USPS::Request
2
+ # Given a valid USPS tracking number, use this class to request
3
+ # tracking information from USPS's systems.
4
+ #
5
+ # Returns a USPS::Response::TrackingFieldLookup object with the pertinent
6
+ # information
7
+ class TrackingFieldLookup < Base
8
+ config(
9
+ :api => 'TrackV2',
10
+ :tag => 'TrackFieldRequest',
11
+ :secure => false,
12
+ :response => USPS::Response::TrackingFieldLookup
13
+ )
14
+
15
+ # Build a new TrackingLookup request.
16
+ # Takes the USPS tracking number to request information for
17
+ def initialize(track_id)
18
+ @track_id = track_id
19
+ end
20
+
21
+ def build
22
+ super do |builder|
23
+ builder.tag!('TrackID', :ID => @track_id)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module USPS::Request
2
+ # Given a valid USPS tracking number, use this class to request
3
+ # tracking information from USPS's systems.
4
+ #
5
+ # Returns a USPS::Response::TrackingLookup object with the pertinent
6
+ # information
7
+ class TrackingLookup < Base
8
+ config(
9
+ :api => 'TrackV2',
10
+ :tag => 'TrackRequest',
11
+ :secure => false,
12
+ :response => USPS::Response::TrackingLookup
13
+ )
14
+
15
+ # Build a new TrackingLookup request.
16
+ # Takes the USPS tracking number to request information for
17
+ def initialize(track_id)
18
+ @track_id = track_id
19
+ end
20
+
21
+ def build
22
+ super do |builder|
23
+ builder.tag!('TrackID', :ID => @track_id)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ module USPS::Request
2
+ # This class is essentially identical to AddressStandardization but do to how
3
+ # #build inheritance is being done it's easier just to reimplement it for now.
4
+ #
5
+ # TODO: #send! could be made smarter to send lookup batches
6
+ class ZipCodeLookup < Base
7
+ config(
8
+ :api => 'ZipCodeLookup',
9
+ :tag => 'ZipCodeLookupRequest',
10
+ :secure => false,
11
+ :response => USPS::Response::AddressStandardization
12
+ )
13
+
14
+ # At most 5 zip codes can be retrieved at once
15
+ def initialize(*addresses)
16
+ @addresses = addresses
17
+
18
+ if @addresses.size > 5
19
+ raise ArgumentError, 'at most 5 lookups can be performed per request'
20
+ end
21
+ end
22
+
23
+ def response_for(xml)
24
+ self.class.response.new(@addresses, xml)
25
+ end
26
+
27
+ def build
28
+ super do |builder|
29
+ @addresses.each_with_index do |addy, i|
30
+ builder.tag!('Address', :ID => i) do
31
+ builder.tag!('FirmName', addy.firm)
32
+
33
+ # Address 1 and 2 are backwards compared to how they appear on an
34
+ # envelope.
35
+ builder.tag!('Address1', addy.extra_address)
36
+ builder.tag!('Address2', addy.address)
37
+
38
+ builder.tag!('City', addy.city)
39
+ builder.tag!('State', addy.state)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ module USPS::Response
2
+ autoload :Base, 'usps/response/base'
3
+ autoload :CityAndStateLookup, 'usps/response/city_and_state_lookup'
4
+ autoload :DeliveryConfirmation, 'usps/response/delivery_confirmation'
5
+ autoload :AddressStandardization, 'usps/response/address_standardization'
6
+ autoload :TrackingLookup, 'usps/response/tracking_lookup'
7
+ autoload :TrackingFieldLookup, 'usps/response/tracking_field_lookup'
8
+ end
@@ -0,0 +1,52 @@
1
+ # TODO: AddressStandardization _can_ handle up to 5 addresses at once and each
2
+ # can be valid or error out. Currently the system raises an exception if any
3
+ # are invalid. The error should be by address.
4
+ module USPS::Response
5
+ class AddressStandardization < Base
6
+ def initialize(addresses, xml)
7
+ @addresses = {}
8
+
9
+ [addresses].flatten.each_with_index do |addy, i|
10
+ @addresses[addy] = parse(xml.search("Address[@ID='#{i}']"))
11
+
12
+ # Name is not sent nor received so lets make sure to set it so the
13
+ # standardized version is roughly equivalent
14
+ @addresses[addy].name = addy.name
15
+ end
16
+ end
17
+
18
+ # Returns an address representing the standardized version of the given
19
+ # address from the results of the query.
20
+ def get(address)
21
+ @addresses[address]
22
+ end
23
+ alias :[] :get
24
+
25
+ def addresses
26
+ @addresses
27
+ end
28
+
29
+ def to_h
30
+ hash = {}
31
+ @addresses.each_pair do |key, value|
32
+ hash[key.to_h] = value.to_h
33
+ end
34
+
35
+ hash
36
+ end
37
+
38
+ private
39
+ def parse(node)
40
+ USPS::Address.new(
41
+ :company => node.search('FirmName').text,
42
+ :address1 => node.search('Address2').text,
43
+ :address2 => node.search('Address1').text,
44
+ :city => node.search('City').text,
45
+ :state => node.search('State').text,
46
+ :zip5 => node.search('Zip5').text,
47
+ :zip4 => node.search('Zip4').text,
48
+ :return_text => node.search('ReturnText').text,
49
+ )
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ module USPS::Response
2
+ class Base
3
+ attr_accessor :raw
4
+
5
+ class << self
6
+ def parse(xml)
7
+ response = self.new(xml)
8
+ response.raw = xml
9
+ response
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ module USPS::Response
2
+ class CityAndStateLookup < Base
3
+ # Result record for a city and state lookup.
4
+ class Result < Struct.new(:zip, :city, :state); end
5
+
6
+ def initialize(xml)
7
+ @data = {}
8
+
9
+ xml.search('ZipCode').each do |node|
10
+ zip = node.search('Zip5').text.to_i
11
+
12
+ @data[zip] = Result.new(
13
+ zip,
14
+ node.search('City').text,
15
+ node.search('State').text
16
+ )
17
+ end
18
+ end
19
+
20
+ # Returns a single city/state pair given a zip5
21
+ def get(zip)
22
+ @data[zip.to_i]
23
+ end
24
+ alias :[] :get
25
+
26
+ # Returns all city/state data from the query results
27
+ def data
28
+ @data
29
+ end
30
+
31
+ # Returns all city/state data as a pure Ruby hash (e.g. no Structs as values)
32
+ def to_h
33
+ hash = {}
34
+ @data.each_pair do |key, value|
35
+ hash[key] = value.to_h
36
+ end
37
+
38
+ hash
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,25 @@
1
+ module USPS::Response
2
+ class DeliveryConfirmation < Base
3
+ attr_reader :label, :confirmation, :address, :postnet
4
+
5
+ alias :confirmation_number :confirmation
6
+
7
+ def initialize(xml)
8
+ # Label is Base64 encoded
9
+ @label = xml.search('DeliveryConfirmationLabel').text.unpack("m*")[0]
10
+ @confirmation = xml.search('DeliveryConfirmationNumber').text
11
+ @postnet = xml.search('Postnet').text
12
+
13
+ @address = USPS::Address.new(
14
+ :name => xml.search('ToName').text,
15
+ :company => xml.search('ToFirm').text,
16
+ :address => xml.search('ToAddress2').text,
17
+ :address2 => xml.search('ToAddress1').text,
18
+ :city => xml.search('ToCity').text,
19
+ :state => xml.search('ToState').text,
20
+ :zip5 => xml.search('ToZip5').text,
21
+ :zip4 => xml.search('ToZip4').text
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module USPS::Response
2
+ # Response object from a USPS::Request::TrackingLookup request.
3
+ # Includes a summary of the current status of the shipment, along with
4
+ # an array of details of the shipment's progress
5
+ class TrackingFieldLookup < Base
6
+
7
+ attr_accessor :summary, :details
8
+
9
+
10
+ def initialize(xml)
11
+ @summary = parse(xml.search("TrackSummary"))
12
+ @details = []
13
+ xml.search("TrackDetail").each do |detail|
14
+ @details << parse(detail)
15
+ end
16
+ end
17
+
18
+ private
19
+ def parse(node)
20
+ USPS::TrackDetail.new(
21
+ :event_time => node.search('EventTime').text,
22
+ :event_date => node.search('EventDate').text,
23
+ :event => node.search('Event').text,
24
+ :event_city => node.search('EventCity').text,
25
+ :event_state => node.search('EventState').text,
26
+ :event_zip_code => node.search('EventZIPCode').text,
27
+ :event_country => node.search('EventCountry').text,
28
+ :firm_name => node.search('FirmName').text,
29
+ :name => node.search('Name').text,
30
+ :authorized_agent => node.search('AuthorizedAgent').text
31
+ )
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module USPS::Response
2
+ # Response object from a USPS::Request::TrackingLookup request.
3
+ # Includes a summary of the current status of the shipment, along with
4
+ # an array of details of the shipment's progress
5
+ class TrackingLookup < Base
6
+
7
+ attr_accessor :summary, :details
8
+
9
+ def initialize(xml)
10
+ @summary = xml.search("TrackSummary").text
11
+
12
+ @details = []
13
+ xml.search("TrackDetail").each do |detail|
14
+ @details << detail.text
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ require 'usps'
2
+ require 'test/unit'
3
+
4
+ module USPS
5
+ # This class is a test runner for the various test requests that are outlined
6
+ # in the USPS API documentation. These tests are often used to determine if a
7
+ # developer is allowed to gain access to the production systems.
8
+ #
9
+ # Running this test suite should fullfil all requirements for access to the production
10
+ # system for the APIs supported by the library.
11
+ class Test < Test::Unit::TestCase
12
+ require 'usps/test/zip_code_lookup'
13
+ require 'usps/test/address_verification'
14
+ require 'usps/test/city_and_state_lookup'
15
+ require 'usps/test/tracking_lookup'
16
+
17
+ if(ENV['USPS_USER'].nil?)
18
+ raise 'USPS_USER must be set in the environment to run these tests'
19
+ end
20
+
21
+ USPS.configure do |config|
22
+ # Being explicit even though it's set in the configuration by default
23
+ config.username = ENV['USPS_USER']
24
+
25
+ # Set USPS_LIVE to anything to run against production
26
+ config.testing = true
27
+ end
28
+
29
+ include ZipCodeLookup
30
+ include CityAndStateLookup
31
+ include AddressVerification
32
+ include TrackingLookup
33
+ end
34
+ end