ShippingInfo 2.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.
- data/lib/fedex.rb +58 -0
- data/lib/fedex/address.rb +30 -0
- data/lib/fedex/credentials.rb +26 -0
- data/lib/fedex/helpers.rb +20 -0
- data/lib/fedex/label.rb +56 -0
- data/lib/fedex/rate.rb +36 -0
- data/lib/fedex/request/address.rb +92 -0
- data/lib/fedex/request/base.rb +309 -0
- data/lib/fedex/request/label.rb +29 -0
- data/lib/fedex/request/rate.rb +66 -0
- data/lib/fedex/request/shipment.rb +108 -0
- data/lib/fedex/request/tracking_information.rb +89 -0
- data/lib/fedex/shipment.rb +61 -0
- data/lib/fedex/tracking_information.rb +49 -0
- data/lib/fedex/tracking_information/event.rb +19 -0
- data/lib/fedex/version.rb +3 -0
- data/lib/shipping.rb +46 -0
- data/lib/ups/UpsInfo.rb +357 -0
- metadata +119 -0
data/lib/ups/UpsInfo.rb
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'net/http'
|
5
|
+
require 'net/https'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'active_support'
|
8
|
+
|
9
|
+
module Fedex
|
10
|
+
# Provides a simple api to to ups's time in transit service.
|
11
|
+
class UpsInfo
|
12
|
+
|
13
|
+
TEST_URL = "https://wwwcie.ups.com/ups.app/xml"
|
14
|
+
|
15
|
+
# UPS Production URL
|
16
|
+
PRODUCTION_URL = "https://onlinetools.ups.com/ups.app/xml"
|
17
|
+
|
18
|
+
XPCI_VERSION = '1.0002'
|
19
|
+
DEFAULT_CUTOFF_TIME = 14
|
20
|
+
DEFAULT_TIMEOUT = 30
|
21
|
+
DEFAULT_RETRY_COUNT = 3
|
22
|
+
DEFAULT_COUNTRY_CODE = 'US'
|
23
|
+
DEFAULT_UNIT_OF_MEASUREMENT = 'LBS'
|
24
|
+
|
25
|
+
def initialize(access_options)
|
26
|
+
@order_cutoff_time = access_options[:order_cutoff_time] || DEFAULT_CUTOFF_TIME
|
27
|
+
@timeout = access_options[:timeout] || DEFAULT_TIMEOUT
|
28
|
+
@retry_count = access_options[:retry_count] || DEFAULT_CUTOFF_TIME
|
29
|
+
|
30
|
+
@access_xml = generate_xml({
|
31
|
+
:AccessRequest => {
|
32
|
+
:AccessLicenseNumber => access_options[:access_license_number],
|
33
|
+
:UserId => access_options[:user_id],
|
34
|
+
:Password => access_options[:password]
|
35
|
+
}
|
36
|
+
})
|
37
|
+
|
38
|
+
@transit_from_attributes = {
|
39
|
+
:AddressArtifactFormat => {
|
40
|
+
:PoliticalDivision2 => access_options[:sender_city],
|
41
|
+
:PoliticalDivision1 => access_options[:sender_state],
|
42
|
+
:CountryCode => access_options[:sender_country_code] || DEFAULT_COUNTRY_CODE,
|
43
|
+
:PostcodePrimaryLow => access_options[:sender_zip]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
@rate_from_attributes = {
|
48
|
+
:Address => {
|
49
|
+
:City => access_options[:sender_city],
|
50
|
+
:StateProvinceCode => access_options[:sender_state],
|
51
|
+
:CountryCode => access_options[:sender_country_code] || DEFAULT_COUNTRY_CODE,
|
52
|
+
:PostalCode => access_options[:sender_zip]
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def getPrice (options)
|
59
|
+
|
60
|
+
@url = options[:mode] == "production" ? PRODUCTION_URL : TEST_URL + '/Rate'
|
61
|
+
|
62
|
+
#@url = options[:url] + '/Rate'
|
63
|
+
# build our request xml
|
64
|
+
|
65
|
+
xml = @access_xml + generate_xml(build_price_attributes(options))
|
66
|
+
#puts xml
|
67
|
+
# attempt the request in a timeout
|
68
|
+
delivery_price = 0
|
69
|
+
|
70
|
+
begin
|
71
|
+
Timeout.timeout(@timeout) do
|
72
|
+
response = send_request(@url, xml)
|
73
|
+
delivery_price = response_to_price(response)
|
74
|
+
end
|
75
|
+
delivery_price
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def getTransitTime(options)
|
80
|
+
|
81
|
+
@url = options[:mode] == "production" ? PRODUCTION_URL : TEST_URL + '/TimeInTransit'
|
82
|
+
#@url = options[:url] + '/TimeInTransit'
|
83
|
+
# build our request xml
|
84
|
+
pickup_date = calculate_pickup_date
|
85
|
+
options[:pickup_date] = pickup_date.strftime('%Y%m%d')
|
86
|
+
xml = @access_xml + generate_xml(build_transit_attributes(options))
|
87
|
+
|
88
|
+
# attempt the request in a timeout
|
89
|
+
delivery_dates = {}
|
90
|
+
attempts = 0
|
91
|
+
begin
|
92
|
+
Timeout.timeout(@timeout) do
|
93
|
+
response = send_request(@url, xml)
|
94
|
+
delivery_dates = response_to_map(response)
|
95
|
+
end
|
96
|
+
|
97
|
+
# We can only attempt to recover from Timeout errors, all other errors
|
98
|
+
# should be raised back to the user
|
99
|
+
rescue Timeout::Error => error
|
100
|
+
if(attempts < @retry_count)
|
101
|
+
attempts += 1
|
102
|
+
retry
|
103
|
+
|
104
|
+
else
|
105
|
+
raise error
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
delivery_dates
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# calculates the next available pickup date based on the current time and the
|
115
|
+
# configured order cutoff time
|
116
|
+
def calculate_pickup_date
|
117
|
+
now = Time.now
|
118
|
+
day_of_week = now.strftime('%w').to_i
|
119
|
+
in_weekend = [6,0].include?(day_of_week)
|
120
|
+
in_friday_after_cutoff = day_of_week == 5 and now.hour > @order_cutoff_time
|
121
|
+
|
122
|
+
# If we're in a weekend (6 is Sat, 0 is Sun,) or we're in Friday after
|
123
|
+
# the cutoff time, then our ship date will move
|
124
|
+
if(in_weekend or in_friday_after_cutoff)
|
125
|
+
pickup_date = now
|
126
|
+
|
127
|
+
# if we're in another weekday but after the cutoff time, our ship date
|
128
|
+
# moves to tomorrow
|
129
|
+
elsif(now.hour > @order_cutoff_time)
|
130
|
+
pickup_date = now
|
131
|
+
else
|
132
|
+
pickup_date = now
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Builds a hash of transit request attributes based on the given values
|
137
|
+
def build_transit_attributes(options)
|
138
|
+
# set defaults if none given
|
139
|
+
options[:total_packages] = 1 unless options[:total_packages]
|
140
|
+
|
141
|
+
# convert all options to string values
|
142
|
+
options.each_value {|option| option = options.to_s}
|
143
|
+
|
144
|
+
transit_attributes = {
|
145
|
+
:TimeInTransitRequest => {
|
146
|
+
:Request => {
|
147
|
+
:RequestAction => 'TimeInTransit',
|
148
|
+
:TransactionReference => {
|
149
|
+
:XpciVersion => XPCI_VERSION
|
150
|
+
}
|
151
|
+
},
|
152
|
+
:TotalPackagesInShipment => options[:total_packages],
|
153
|
+
:ShipmentWeight => {
|
154
|
+
:UnitOfMeasurement => {
|
155
|
+
:Code => options[:unit_of_measurement] || DEFAULT_UNIT_OF_MEASUREMENT
|
156
|
+
},
|
157
|
+
:Weight => options[:weight],
|
158
|
+
},
|
159
|
+
:PickupDate => options[:pickup_date],
|
160
|
+
:TransitFrom => @transit_from_attributes,
|
161
|
+
:TransitTo => {
|
162
|
+
:AddressArtifactFormat => {
|
163
|
+
:PoliticalDivision2 => options[:city],
|
164
|
+
:PoliticalDivision1 => options[:state],
|
165
|
+
:CountryCode => options[:country_code] || DEFAULT_COUNTRY_CODE,
|
166
|
+
:PostcodePrimaryLow => options[:zip],
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
# Builds a hash of price attributes based on the given values
|
174
|
+
def build_price_attributes(options)
|
175
|
+
# convert all options to string values
|
176
|
+
options.each_value {|option| option = options.to_s}
|
177
|
+
|
178
|
+
rate_attributes = {
|
179
|
+
:RatingServiceSelectionRequest => {
|
180
|
+
:Request => {
|
181
|
+
:RequestAction => 'Rate',
|
182
|
+
:RequestOption => 'Rate',
|
183
|
+
:TransactionReference => {
|
184
|
+
:XpciVersion => '1.0'}
|
185
|
+
},
|
186
|
+
:PickupType => {
|
187
|
+
:Code => '01'
|
188
|
+
},
|
189
|
+
:CustomerClassification => {:Code => '01'},
|
190
|
+
:Shipment => {
|
191
|
+
:Shipper => @rate_from_attributes,
|
192
|
+
:ShipTo => {:Address => {
|
193
|
+
:City => options[:city],
|
194
|
+
:StateProvinceCode => options[:state],
|
195
|
+
:PostalCode => options[:zip],
|
196
|
+
:CountryCode => options[:country_code]}
|
197
|
+
},
|
198
|
+
:Service => {:Code => '03'},
|
199
|
+
:Package => {:PackagingType => {:Code => '02'},
|
200
|
+
:PackageWeight => {:Weight => options[:weight], :UnitOfMeasurement => 'LBS'}
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
# generates an xml document for the given attributes
|
208
|
+
def generate_xml(attributes)
|
209
|
+
xml = REXML::Document.new
|
210
|
+
xml << REXML::XMLDecl.new
|
211
|
+
emit(attributes, xml)
|
212
|
+
xml.root.add_attribute("xml:lang", "en-US")
|
213
|
+
xml.to_s
|
214
|
+
end
|
215
|
+
|
216
|
+
# recursively emits xml nodes under the given node for values in the given hash
|
217
|
+
def emit(attributes, node)
|
218
|
+
attributes.each do |k,v|
|
219
|
+
child_node = REXML::Element.new(k.to_s, node)
|
220
|
+
(v.respond_to? 'each_key') ? emit(v, child_node) : child_node.add_text(v.to_s)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Posts the given data to the given url, returning the raw response
|
225
|
+
def send_request(url, data)
|
226
|
+
uri = URI.parse(url)
|
227
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
228
|
+
if uri.port == 443
|
229
|
+
http.use_ssl = true
|
230
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
231
|
+
end
|
232
|
+
response = http.post(uri.path, data)
|
233
|
+
response.code == '200' ? response.body : response.error!
|
234
|
+
end
|
235
|
+
|
236
|
+
# converts the given raw xml response to a map of local service codes
|
237
|
+
# to estimated delivery dates
|
238
|
+
def response_to_map(response)
|
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
|
+
delivery_date = 0
|
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 == "GND")
|
247
|
+
date_string = service_element.elements['EstimatedArrival/Date'].text
|
248
|
+
pickup_date = service_element.elements['EstimatedArrival/PickupDate'].text
|
249
|
+
#time_string = service_element.elements['EstimatedArrival/Time'].text
|
250
|
+
#delivery_date = Time.parse("#{date_string}")
|
251
|
+
delivery_date = (Time.parse(date_string) - Time.parse(pickup_date)) / 86400
|
252
|
+
#service_codes_to_delivery_dates[ "UPS 12"+ service_code + " Transit Days"] = delivery_date
|
253
|
+
end
|
254
|
+
end
|
255
|
+
delivery_date
|
256
|
+
#response
|
257
|
+
end
|
258
|
+
|
259
|
+
def response_to_price(response)
|
260
|
+
#puts response
|
261
|
+
response_doc = REXML::Document.new(response)
|
262
|
+
response_code = response_doc.elements['//ResponseStatusCode'].text.to_i
|
263
|
+
raise "Invalid response from ups:\n#{response_doc.to_s}" if(!response_code || response_code != 1)
|
264
|
+
delivery_rate = 0
|
265
|
+
|
266
|
+
delivery_rate = response_doc.elements['//RatedShipment/TotalCharges/MonetaryValue'].text.to_f
|
267
|
+
|
268
|
+
delivery_rate
|
269
|
+
#response
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.state_from_zip(zip)
|
273
|
+
zip = zip.to_i
|
274
|
+
{
|
275
|
+
(99500...99929) => "AK",
|
276
|
+
(35000...36999) => "AL",
|
277
|
+
(71600...72999) => "AR",
|
278
|
+
(75502...75505) => "AR",
|
279
|
+
(85000...86599) => "AZ",
|
280
|
+
(90000...96199) => "CA",
|
281
|
+
(80000...81699) => "CO",
|
282
|
+
(6000...6999) => "CT",
|
283
|
+
(20000...20099) => "DC",
|
284
|
+
(20200...20599) => "DC",
|
285
|
+
(19700...19999) => "DE",
|
286
|
+
(32000...33999) => "FL",
|
287
|
+
(34100...34999) => "FL",
|
288
|
+
(30000...31999) => "GA",
|
289
|
+
(96700...96798) => "HI",
|
290
|
+
(96800...96899) => "HI",
|
291
|
+
(50000...52999) => "IA",
|
292
|
+
(83200...83899) => "ID",
|
293
|
+
(60000...62999) => "IL",
|
294
|
+
(46000...47999) => "IN",
|
295
|
+
(66000...67999) => "KS",
|
296
|
+
(40000...42799) => "KY",
|
297
|
+
(45275...45275) => "KY",
|
298
|
+
(70000...71499) => "LA",
|
299
|
+
(71749...71749) => "LA",
|
300
|
+
(1000...2799) => "MA",
|
301
|
+
(20331...20331) => "MD",
|
302
|
+
(20600...21999) => "MD",
|
303
|
+
(3801...3801) => "ME",
|
304
|
+
(3804...3804) => "ME",
|
305
|
+
(3900...4999) => "ME",
|
306
|
+
(48000...49999) => "MI",
|
307
|
+
(55000...56799) => "MN",
|
308
|
+
(63000...65899) => "MO",
|
309
|
+
(38600...39799) => "MS",
|
310
|
+
(59000...59999) => "MT",
|
311
|
+
(27000...28999) => "NC",
|
312
|
+
(58000...58899) => "ND",
|
313
|
+
(68000...69399) => "NE",
|
314
|
+
(3000...3803) => "NH",
|
315
|
+
(3809...3899) => "NH",
|
316
|
+
(7000...8999) => "NJ",
|
317
|
+
(87000...88499) => "NM",
|
318
|
+
(89000...89899) => "NV",
|
319
|
+
(400...599) => "NY",
|
320
|
+
(6390...6390) => "NY",
|
321
|
+
(9000...14999) => "NY",
|
322
|
+
(43000...45999) => "OH",
|
323
|
+
(73000...73199) => "OK",
|
324
|
+
(73400...74999) => "OK",
|
325
|
+
(97000...97999) => "OR",
|
326
|
+
(15000...19699) => "PA",
|
327
|
+
(2800...2999) => "RI",
|
328
|
+
(6379...6379) => "RI",
|
329
|
+
(29000...29999) => "SC",
|
330
|
+
(57000...57799) => "SD",
|
331
|
+
(37000...38599) => "TN",
|
332
|
+
(72395...72395) => "TN",
|
333
|
+
(73300...73399) => "TX",
|
334
|
+
(73949...73949) => "TX",
|
335
|
+
(75000...79999) => "TX",
|
336
|
+
(88501...88599) => "TX",
|
337
|
+
(84000...84799) => "UT",
|
338
|
+
(20105...20199) => "VA",
|
339
|
+
(20301...20301) => "VA",
|
340
|
+
(20370...20370) => "VA",
|
341
|
+
(22000...24699) => "VA",
|
342
|
+
(5000...5999) => "VT",
|
343
|
+
(98000...99499) => "WA",
|
344
|
+
(49936...49936) => "WI",
|
345
|
+
(53000...54999) => "WI",
|
346
|
+
(24700...26899) => "WV",
|
347
|
+
(82000...83199) => "WY"
|
348
|
+
}.each do |range, state|
|
349
|
+
return state if range.include? zip
|
350
|
+
end
|
351
|
+
|
352
|
+
raise ShippingError, "Invalid zip code"
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ShippingInfo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '2.0'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Piyush Patel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: &3108444 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *3108444
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: nokogiri
|
27
|
+
requirement: &3107772 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.5.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *3107772
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &3107136 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.9.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *3107136
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: vcr
|
49
|
+
requirement: &3106620 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.0.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *3106620
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: fakeweb
|
60
|
+
requirement: &3105912 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *3105912
|
69
|
+
description: Provides an interface to get shipping rates and transit time for Fedex,
|
70
|
+
UPS, and USPS
|
71
|
+
email:
|
72
|
+
- er.piyushpatel@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- lib/fedex/address.rb
|
78
|
+
- lib/fedex/credentials.rb
|
79
|
+
- lib/fedex/helpers.rb
|
80
|
+
- lib/fedex/label.rb
|
81
|
+
- lib/fedex/rate.rb
|
82
|
+
- lib/fedex/request/address.rb
|
83
|
+
- lib/fedex/request/base.rb
|
84
|
+
- lib/fedex/request/label.rb
|
85
|
+
- lib/fedex/request/rate.rb
|
86
|
+
- lib/fedex/request/shipment.rb
|
87
|
+
- lib/fedex/request/tracking_information.rb
|
88
|
+
- lib/fedex/shipment.rb
|
89
|
+
- lib/fedex/tracking_information/event.rb
|
90
|
+
- lib/fedex/tracking_information.rb
|
91
|
+
- lib/fedex/version.rb
|
92
|
+
- lib/fedex.rb
|
93
|
+
- lib/shipping.rb
|
94
|
+
- lib/ups/UpsInfo.rb
|
95
|
+
homepage: http://rubygems.org/gems/shippinginfo
|
96
|
+
licenses: []
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project: ShippingInfo
|
115
|
+
rubygems_version: 1.8.16
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Shipping Info Services
|
119
|
+
test_files: []
|