ups_time_in_transit 0.1.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.
Binary file
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2010 Joseph Stelmach
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
@@ -0,0 +1,6 @@
1
+ LICENSE
2
+ Manifest
3
+ README
4
+ Rakefile
5
+ lib/ups_time_in_transit.rb
6
+ ups_time_in_transit.gemspec
data/README ADDED
@@ -0,0 +1,55 @@
1
+ ups_time_in_transit is an easy to use interface to the UPS Time in Transit API.
2
+
3
+ == installation ==
4
+ gem install ups_time_in_transit
5
+
6
+ == usage ==
7
+
8
+ # define your access options. These are options that won't change
9
+ # between requests. Take care to specify the proper UPS url:
10
+ # 'https://wwwcie.ups.com/ups.app/xml/TimeInTransit' for testing and
11
+ # development, and 'https://onlinetools.ups.com/ups.app/xml/TimeInTransit'
12
+ # for production.
13
+ access_options = {
14
+ :url => 'https://wwwcie.ups.com/ups.app/xml/TimeInTransit',
15
+ :access_license_number => 'foo',
16
+ :user_id => 'bar',
17
+ :password => 'baz',
18
+ :order_cutoff_time => 17 ,
19
+ :sender_city => 'Hoboken',
20
+ :sender_state => 'NJ',
21
+ :sender_country_code => 'US',
22
+ :sender_zip => '07030'}
23
+
24
+ # It's best to store these these options in a yaml file and load them
25
+ # into a map when you need them:
26
+ yaml = YAML.load_file("#{RAILS_ROOT}/config/ups_time_in_transit.yml")
27
+ access_options = yaml[RAILS_ENV].inject({}){|h,(k, v)| h[k.to_sym] = v; h}
28
+
29
+ # create an api instance with your access options
30
+ time_in_transit_api = UPS::TimeInTransit.new(access_options)
31
+
32
+ # for each request, generate a map of request options describing
33
+ # the shipment and where it's going
34
+ request_options = {
35
+ :total_packages => 1,
36
+ :unit_of_measurement => 'LBS',
37
+ :weight => 10,
38
+ :city => 'Newark',
39
+ :state => 'DE',
40
+ :zip => '19711',
41
+ :country_code => 'US'}
42
+
43
+ # request the map of delivery types (overnight, ground, etc.) to the expected
44
+ # delivery date for that type. Be sure to rescue any errors.
45
+ begin
46
+ delivery_dates = time_in_transit_api.request(request_options)
47
+ rescue => error
48
+ puts error.inspect
49
+ end
50
+
51
+ == changelog ==
52
+
53
+ v.0.1.0
54
+ initial release
55
+
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('ups_time_in_transit', '0.1.1') do |p|
6
+ p.description = "Provides an easy to use interface to the UPS time in transit API"
7
+ p.url = "http://github.com/joestelmach/ups_time_in_transit"
8
+ p.author = "Joe Stelmach"
9
+ p.email = "joestelmach @nospam@ gmail.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.runtime_dependencies = ['activesupport']
12
+ end
@@ -0,0 +1,250 @@
1
+ require 'date'
2
+ require 'rexml/document'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'rubygems'
6
+ require 'active_support'
7
+
8
+ module UPS
9
+ # Provides a simple api to to ups's time in transit service.
10
+ class TimeInTransit
11
+ XPCI_VERSION = '1.0002'
12
+ DEFAULT_CUTOFF_TIME = 14
13
+ DEFAULT_TIMEOUT = 30
14
+ DEFAULT_RETRY_COUNT = 3
15
+ DEFAULT_COUNTRY_CODE = 'US'
16
+ DEFAULT_UNIT_OF_MEASUREMENT = 'LBS'
17
+
18
+ # Creates a TimeInTransit instance based on the given hash of access
19
+ # options The following access options are available and are required
20
+ # unless a default value is specified:
21
+ #
22
+ # [<tt>:url</tt>]
23
+ # The ups api url to use
24
+ #
25
+ # [<tt>:access_license_number</tt>]
26
+ # Your ups license number
27
+ #
28
+ # [<tt>:user_id</tt>]
29
+ # Your ups user id
30
+ #
31
+ # [<tt>password</tt>]
32
+ # Your ups password
33
+ #
34
+ # [<tt>:order_cutoff_time</tt>]
35
+ # Your own arbitrary cutoff time that is some time before the actual ups cutoff
36
+ # time. Requests made after this time will use the following day as the send
37
+ # date (or the following monday if the request is made on a weekend or on a
38
+ # friday after this time.)
39
+ #
40
+ # [<tt>:sender_city</tt>]
41
+ # The city you are shipping from
42
+ #
43
+ # [<tt>:sender_state</tt>]
44
+ # The state you are shipping from
45
+ #
46
+ # [<tt>:sender_zip</tt>]
47
+ # The zip code you are shipping from
48
+ #
49
+ # [<tt>:sender_country_code</tt>]
50
+ # The country you are shipping from (defaults to 'US')
51
+ #
52
+ # [<tt>:retry_count</tt>]
53
+ # The number of times you would like to retry when a connection
54
+ # fails (defaults to 3)
55
+ #
56
+ # [<tt>:timeout</tt>]
57
+ # The number of seconds you would like to wait for a response before
58
+ # giving up (defaults to 30)
59
+ #
60
+ def initialize(access_options)
61
+ @order_cutoff_time = access_options[:order_cutoff_time] || DEFAULT_CUTOFF_TIME
62
+ @url = access_options[:url]
63
+ @timeout = access_options[:timeout] || DEFAULT_TIMEOUT
64
+ @retry_count = access_options[:retry_count] || DEFAULT_CUTOFF_TIME
65
+
66
+ @access_xml = generate_xml({
67
+ :AccessRequest => {
68
+ :AccessLicenseNumber => access_options[:access_license_number],
69
+ :UserId => access_options[:user_id],
70
+ :Password => access_options[:password]
71
+ }
72
+ })
73
+
74
+ @transit_from_attributes = {
75
+ :AddressArtifactFormat => {
76
+ :PoliticalDivision2 => access_options[:sender_city],
77
+ :PoliticalDivision1 => access_options[:sender_state],
78
+ :CountryCode => access_options[:sender_country_code] || DEFAULT_COUNTRY_CODE,
79
+ :PostcodePrimaryLow => access_options[:sender_zip]
80
+ }
81
+ }
82
+ end
83
+
84
+ # Requests time in transit information based on the given hash of options:
85
+ #
86
+ # [<tt>:total_packages</tt>]
87
+ # the number of packages in the shipment (defaults to 1)
88
+ #
89
+ # [<tt>:unit_of_measurement</tt>]
90
+ # the unit of measurement to use (defaults to 'LBS')
91
+ #
92
+ # [<tt>:weight</tt>]
93
+ # the weight of the shipment in the given units
94
+ #
95
+ # [<tt>:city</tt>]
96
+ # the city you are shipping to
97
+ #
98
+ # [<tt>:state</tt>]
99
+ # the state you are shipping to
100
+ #
101
+ # [<tt>:zip</tt>]
102
+ # the zip code you are shipping to
103
+ #
104
+ # [<tt>:country_code</tt>]
105
+ # the country you are shipping to (defaults to 'US')
106
+ #
107
+ # An error will be raised if the request is unsuccessful.
108
+ #
109
+ def request(options)
110
+
111
+ # build our request xml
112
+ pickup_date = calculate_pickup_date
113
+ options[:pickup_date] = pickup_date.strftime('%Y%m%d')
114
+ xml = @access_xml + generate_xml(build_transit_attributes(options))
115
+
116
+ # attempt the request in a timeout
117
+ delivery_dates = {}
118
+ attempts = 0
119
+ begin
120
+ Timeout.timeout(@timeout) do
121
+ response = send_request(@url, xml)
122
+ delivery_dates = response_to_map(response)
123
+ end
124
+
125
+ # We can only attempt to recover from Timeout errors, all other errors
126
+ # should be raised back to the user
127
+ rescue Timeout::Error => error
128
+ if(attempts < @retry_count)
129
+ attempts += 1
130
+ retry
131
+
132
+ else
133
+ raise error
134
+ end
135
+ end
136
+
137
+ delivery_dates
138
+ end
139
+
140
+ private
141
+
142
+ # calculates the next available pickup date based on the current time and the
143
+ # configured order cutoff time
144
+ def calculate_pickup_date
145
+ now = Time.now
146
+ day_of_week = now.strftime('%w').to_i
147
+ in_weekend = [6,0].include?(day_of_week)
148
+ in_friday_after_cutoff = day_of_week == 5 and now.hour > @order_cutoff_time
149
+
150
+ # If we're in a weekend (6 is Sat, 0 is Sun,) or we're in Friday after
151
+ # the cutoff time, then our ship date will move
152
+ if(in_weekend or in_friday_after_cutoff)
153
+ pickup_date = now.next_week
154
+
155
+ # if we're in another weekday but after the cutoff time, our ship date
156
+ # moves to tomorrow
157
+ elsif(now.hour > @order_cutoff_time)
158
+ pickup_date = now.tomorrow
159
+ else
160
+ pickup_date = now
161
+ end
162
+ end
163
+
164
+ # Builds a hash of transit request attributes based on the given values
165
+ def build_transit_attributes(options)
166
+ # set defaults if none given
167
+ options[:total_packages] = 1 unless options[:total_packages]
168
+
169
+ # convert all options to string values
170
+ options.each_value {|option| option = options.to_s}
171
+
172
+ transit_attributes = {
173
+ :TimeInTransitRequest => {
174
+ :Request => {
175
+ :RequestAction => 'TimeInTransit',
176
+ :TransactionReference => {
177
+ :XpciVersion => XPCI_VERSION
178
+ }
179
+ },
180
+ :TotalPackagesInShipment => options[:total_packages],
181
+ :ShipmentWeight => {
182
+ :UnitOfMeasurement => {
183
+ :Code => options[:unit_of_measurement] || DEFAULT_UNIT_OF_MEASUREMENT
184
+ },
185
+ :Weight => options[:weight],
186
+ },
187
+ :PickupDate => options[:pickup_date],
188
+ :TransitFrom => @transit_from_attributes,
189
+ :TransitTo => {
190
+ :AddressArtifactFormat => {
191
+ :PoliticalDivision2 => options[:city],
192
+ :PoliticalDivision1 => options[:state],
193
+ :CountryCode => options[:country_code] || DEFAULT_COUNTRY_CODE,
194
+ :PostcodePrimaryLow => options[:zip],
195
+ }
196
+ }
197
+ }
198
+ }
199
+ end
200
+
201
+ # generates an xml document for the given attributes
202
+ def generate_xml(attributes)
203
+ xml = REXML::Document.new
204
+ xml << REXML::XMLDecl.new
205
+ emit(attributes, xml)
206
+ xml.root.add_attribute("xml:lang", "en-US")
207
+ xml.to_s
208
+ end
209
+
210
+ # recursively emits xml nodes under the given node for values in the given hash
211
+ def emit(attributes, node)
212
+ attributes.each do |k,v|
213
+ child_node = REXML::Element.new(k.to_s, node)
214
+ (v.respond_to? 'each_key') ? emit(v, child_node) : child_node.add_text(v.to_s)
215
+ end
216
+ end
217
+
218
+ # Posts the given data to the given url, returning the raw response
219
+ def send_request(url, data)
220
+ uri = URI.parse(url)
221
+ http = Net::HTTP.new(uri.host, uri.port)
222
+ if uri.port == 443
223
+ http.use_ssl = true
224
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
225
+ end
226
+ response = http.post(uri.path, data)
227
+ response.code == '200' ? response.body : response.error!
228
+ end
229
+
230
+ # converts the given raw xml response to a map of local service codes
231
+ # to estimated delivery dates
232
+ def response_to_map(response)
233
+ response_doc = REXML::Document.new(response)
234
+ response_code = response_doc.elements['//ResponseStatusCode'].text.to_i
235
+ raise "Invalid response from ups:\n#{response_doc.to_s}" if(!response_code || response_code != 1)
236
+
237
+ service_codes_to_delivery_dates = {}
238
+ response_code = response_doc.elements.each('//ServiceSummary') do |service_element|
239
+ service_code = service_element.elements['Service/Code'].text
240
+ if(service_code)
241
+ date_string = service_element.elements['EstimatedArrival/Date'].text
242
+ time_string = service_element.elements['EstimatedArrival/Time'].text
243
+ delivery_date = Time.parse("#{date_string} #{time_string}")
244
+ service_codes_to_delivery_dates[service_code] = delivery_date
245
+ end
246
+ end
247
+ service_codes_to_delivery_dates
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ups_time_in_transit}
5
+ s.version = "0.1.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Joe Stelmach"]
9
+ s.cert_chain = ["/Users/joe/workspace/personal/gem-public_cert.pem"]
10
+ s.date = %q{2010-06-01}
11
+ s.description = %q{Provides an easy to use interface to the UPS time in transit API}
12
+ s.email = %q{joestelmach @nospam@ gmail.com}
13
+ s.extra_rdoc_files = ["LICENSE", "README", "lib/ups_time_in_transit.rb"]
14
+ s.files = ["LICENSE", "Manifest", "README", "Rakefile", "lib/ups_time_in_transit.rb", "ups_time_in_transit.gemspec"]
15
+ s.homepage = %q{http://github.com/joestelmach/ups_time_in_transit}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ups_time_in_transit", "--main", "README"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{ups_time_in_transit}
19
+ s.rubygems_version = %q{1.3.7}
20
+ s.signing_key = %q{/Users/joe/workspace/personal/gem-private_key.pem}
21
+ s.summary = %q{Provides an easy to use interface to the UPS time in transit API}
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 3
26
+
27
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
28
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<activesupport>, [">= 0"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<activesupport>, [">= 0"])
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ups_time_in_transit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Joe Stelmach
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain:
17
+ - |
18
+ -----BEGIN CERTIFICATE-----
19
+ MIIDODCCAiCgAwIBAgIBADANBgkqhkiG9w0BAQUFADBCMRQwEgYDVQQDDAtqb2Vz
20
+ dGVsbWFjaDEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYD
21
+ Y29tMB4XDTEwMDYwMjAyMTEzN1oXDTExMDYwMjAyMTEzN1owQjEUMBIGA1UEAwwL
22
+ am9lc3RlbG1hY2gxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixk
23
+ ARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANkIW8alNYIx
24
+ 7dkzBZXc6XE3WomRJCYZLgWAlbnGT/iF9xOunTOcsYOFVaP9fY5tQ3c9DKOd/TtG
25
+ bTFKZYvOQM2ASlfJo4KrW3NqiwpG1kPsTI3wOOrOzoClnVu8EneK0kG5PETDZY2H
26
+ h0B4kd7ejCmX8L4KRIVZRljZLSjLMEPEyA2+9CPIL7UfjmKHEIWjmQli+IrfUVoa
27
+ CpcrNxG7FBa/pwfsIF8nr1xSw35K7S1oJ5ma65s2449wVspqLXg5H3IQxz/Y9coe
28
+ iuG1QhmI78RSIHfzDPNXp5smOsHrVJm21h71eF/oV19BNvGuMkavOS5NSPZ3T685
29
+ v4NVpu7qoosCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
30
+ BBYEFPCOueB1WpFUwO+OTSX4ABA4e/bPMA0GCSqGSIb3DQEBBQUAA4IBAQBnrQSg
31
+ rLL1UBujU/RkXPdC/xj61lywwy+HuiGruu1VbLVSten2VjjDEK4ga/7K9z3SRWiW
32
+ fFXj61rM9ljvYbQN2+74WR2WjbX7057bqY80/vSnyIxqxXoJzjg6OAHvdXq+oAel
33
+ A5jXBeb7FGd3IIUjAwWvLmvK+s3AmfOZ8y7e/A0QKtgvSufrUWN+ZaGnzP64WMPf
34
+ CHFGU7Orn1e+tYjg7zAQMXkYdQ9u/HvbJ6/gLQwD4bKwRW6jZGSBdUUQU7W4/ygC
35
+ irVgr9ouyby8ZibMqCXSRjQ9BP3xIRThygXC5MLpq4bkHn/twgUWC2FYWTeI3F43
36
+ BNAwAE8UFlzJGIy5
37
+ -----END CERTIFICATE-----
38
+
39
+ date: 2010-06-01 00:00:00 -04:00
40
+ default_executable:
41
+ dependencies:
42
+ - !ruby/object:Gem::Dependency
43
+ name: activesupport
44
+ prerelease: false
45
+ requirement: &id001 !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :runtime
55
+ version_requirements: *id001
56
+ description: Provides an easy to use interface to the UPS time in transit API
57
+ email: joestelmach @nospam@ gmail.com
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files:
63
+ - LICENSE
64
+ - README
65
+ - lib/ups_time_in_transit.rb
66
+ files:
67
+ - LICENSE
68
+ - Manifest
69
+ - README
70
+ - Rakefile
71
+ - lib/ups_time_in_transit.rb
72
+ - ups_time_in_transit.gemspec
73
+ has_rdoc: true
74
+ homepage: http://github.com/joestelmach/ups_time_in_transit
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --line-numbers
80
+ - --inline-source
81
+ - --title
82
+ - Ups_time_in_transit
83
+ - --main
84
+ - README
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 11
102
+ segments:
103
+ - 1
104
+ - 2
105
+ version: "1.2"
106
+ requirements: []
107
+
108
+ rubyforge_project: ups_time_in_transit
109
+ rubygems_version: 1.3.7
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Provides an easy to use interface to the UPS time in transit API
113
+ test_files: []
114
+
@@ -0,0 +1,2 @@
1
+ �����\+Ψ���� y�AUI��K���{�����dsn`0�Mm�◓B�|:~�dA?�������*�@���|;$�)�pW���BLF @ �\
2
+ ��J.)Λ����0����.#��?�����ƤBP_�;(X���H��G Dz V��$SGDa�3�\B;"q�