ups_time_in_transit_plus 0.1.3

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGMxNTA2ZGEwNWFlYWJjZGU4YWQxYmE5NmZmYzU3ZGRhZWNmYzI3OA==
5
+ data.tar.gz: !binary |-
6
+ ZjY5YTczZWUzYmMyOTdhZGJmYzhmNzc4NDk3NTYyNTFlNTAwNTlkZg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZjljNWFhNzJjODdhZTU1Y2U1OGNhZDQwYjUwNGNhZTBkM2MxYzUwMWRjMzJj
10
+ NmQxZGM3MWI3N2JjNjY2ZTgyY2I5N2VjZDhlN2M5M2UwNmJhNjNlOWFhMjE3
11
+ MjYyY2JjZWE5NjQwMmQwZTBmZmQ2MDdkYjgyZWZkNjc2NDNhZWQ=
12
+ data.tar.gz: !binary |-
13
+ MjNjYzE1ZjY3ZmNmNTU1Y2UyYWU5N2MzYTc3OTJkNGM2ZWVjMjcyYmQ3YTFk
14
+ NzA3MGNmZGY3N2UyYWIzNzFiY2RhODc0MWM0YWQ2OTE5YTZjN2U1MmVkOTQx
15
+ ZTI4OTMzZjZjYjNlMDYzNjYyMjJjZDVkMTQ3MGVhMzBhMDhhMDc=
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
+
data/Manifest ADDED
@@ -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,58 @@
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.1
54
+ some erroneous documentation
55
+
56
+ v.0.1.0
57
+ initial release
58
+
data/Rakefile ADDED
@@ -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,257 @@
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 = 18
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
+ # Rails.logger.debug "Time now is: #{now}\nOrder cutoff time is: #{@order_cutoff_time}\n"
150
+
151
+ # If we're in a weekend (6 is Sat, 0 is Sun,) or we're in Friday after
152
+ # the cutoff time, then our ship date will move
153
+ if(in_weekend or in_friday_after_cutoff)
154
+ pickup_date = now.next_week
155
+
156
+ # if we're in another weekday but after the cutoff time, our ship date
157
+ # moves to tomorrow
158
+ elsif(now.hour > @order_cutoff_time)
159
+ # Rails.logger.debug "\nSetting the time in transit to tomorrow #{now.tomorrow}, since now.hour > @order_cutoff_time = #{now.hour > @order_cutoff_time}\n"
160
+ pickup_date = now.tomorrow
161
+ else
162
+ pickup_date = now
163
+ end
164
+ # Rails.logger.debug "UPS pickup_date is: #{pickup_date} and the time now is #{now}"
165
+ return pickup_date
166
+ end
167
+
168
+ # Builds a hash of transit request attributes based on the given values
169
+ def build_transit_attributes(options)
170
+ # set defaults if none given
171
+ options[:total_packages] = 1 unless options[:total_packages]
172
+
173
+ # convert all options to string values
174
+ options.each_value {|option| option = options.to_s}
175
+
176
+ transit_attributes = {
177
+ :TimeInTransitRequest => {
178
+ :Request => {
179
+ :RequestAction => 'TimeInTransit',
180
+ :TransactionReference => {
181
+ :XpciVersion => XPCI_VERSION
182
+ }
183
+ },
184
+ :TotalPackagesInShipment => options[:total_packages],
185
+ :ShipmentWeight => {
186
+ :UnitOfMeasurement => {
187
+ :Code => options[:unit_of_measurement] || DEFAULT_UNIT_OF_MEASUREMENT
188
+ },
189
+ :Weight => options[:weight],
190
+ },
191
+ :PickupDate => options[:pickup_date],
192
+ :TransitFrom => @transit_from_attributes,
193
+ :TransitTo => {
194
+ :AddressArtifactFormat => {
195
+ :PoliticalDivision2 => options[:city],
196
+ :PoliticalDivision1 => options[:state],
197
+ :CountryCode => options[:country_code] || DEFAULT_COUNTRY_CODE,
198
+ :PostcodePrimaryLow => options[:zip],
199
+ }
200
+ }
201
+ }
202
+ }
203
+ end
204
+
205
+ # generates an xml document for the given attributes
206
+ def generate_xml(attributes)
207
+ xml = REXML::Document.new
208
+ xml << REXML::XMLDecl.new
209
+ emit(attributes, xml)
210
+ xml.root.add_attribute("xml:lang", "en-US")
211
+ xml.to_s
212
+ end
213
+
214
+ # recursively emits xml nodes under the given node for values in the given hash
215
+ def emit(attributes, node)
216
+ attributes.each do |k,v|
217
+ child_node = REXML::Element.new(k.to_s, node)
218
+ (v.respond_to? 'each_key') ? emit(v, child_node) : child_node.add_text(v.to_s)
219
+ end
220
+ end
221
+
222
+ # Posts the given data to the given url, returning the raw response
223
+ def send_request(url, data)
224
+ # Rails.logger.debug "Sending the following data in a request to UPS time in transit API:\n#{data}\n"
225
+ uri = URI.parse(url)
226
+ http = Net::HTTP.new(uri.host, uri.port)
227
+ if uri.port == 443
228
+ http.use_ssl = true
229
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
230
+ end
231
+ response = http.post(uri.path, data)
232
+ response.code == '200' ? response.body : response.error!
233
+ end
234
+
235
+ # converts the given raw xml response to a map of local service codes
236
+ # to estimated delivery dates
237
+ def response_to_map(response)
238
+ # Rails.logger.debug "Received the following data in a response from UPS time in transit API:\n#{response}\n"
239
+ response_doc = REXML::Document.new(response)
240
+ response_code = response_doc.elements['//ResponseStatusCode'].text.to_i
241
+ raise "Invalid response from ups:\n#{response_doc.to_s}" if(!response_code || response_code != 1)
242
+
243
+ service_codes_to_delivery_dates = {}
244
+ response_code = response_doc.elements.each('//ServiceSummary') do |service_element|
245
+ service_code = service_element.elements['Service/Code'].text
246
+ if(service_code)
247
+ date_string = service_element.elements['EstimatedArrival/Date'].text
248
+ time_string = service_element.elements['EstimatedArrival/Time'].text
249
+ days_in_transit = service_element.elements['EstimatedArrival/BusinessTransitDays'].text.to_i
250
+ delivery_date = Time.parse("#{date_string} #{time_string}")
251
+ service_codes_to_delivery_dates[service_code] = {:delivery_date => delivery_date, :days_in_transit => days_in_transit}
252
+ end
253
+ end
254
+ service_codes_to_delivery_dates
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ups_time_in_transit_plus}
5
+ s.version = "0.1.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Joe Stelmach, Benjamin Crudo"]
9
+ # s.cert_chain = ["/Users/joe/workspace/personal/gem-public_cert.pem"]
10
+ s.date = %q{2013-10-07}
11
+ s.description = %q{Provides an easy to use interface to the UPS time in transit API}
12
+ s.email = %q{gems @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_plus.gemspec"]
15
+ s.homepage = %q{http://github.com/BENGMN/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_plus}
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,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ups_time_in_transit_plus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Joe Stelmach, Benjamin Crudo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Provides an easy to use interface to the UPS time in transit API
28
+ email: gems @nospam@ gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README
34
+ - lib/ups_time_in_transit.rb
35
+ files:
36
+ - LICENSE
37
+ - Manifest
38
+ - README
39
+ - Rakefile
40
+ - lib/ups_time_in_transit.rb
41
+ - ups_time_in_transit_plus.gemspec
42
+ homepage: http://github.com/BENGMN/ups_time_in_transit
43
+ licenses: []
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --line-numbers
48
+ - --inline-source
49
+ - --title
50
+ - Ups_time_in_transit
51
+ - --main
52
+ - README
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '1.2'
65
+ requirements: []
66
+ rubyforge_project: ups_time_in_transit_plus
67
+ rubygems_version: 2.1.10
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Provides an easy to use interface to the UPS time in transit API
71
+ test_files: []