ups_time_in_transit 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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�