usps-fork 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 (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