ideaoforder-shipping 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +429 -0
- data/README +51 -0
- data/Rakefile +251 -0
- data/lib/shipping/base.rb +314 -0
- data/lib/shipping/fedex.rb +585 -0
- data/lib/shipping/ups.rb +809 -0
- metadata +69 -0
data/Rakefile
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/contrib/rubyforgepublisher'
|
7
|
+
require File.dirname(__FILE__) + '/lib/shipping'
|
8
|
+
|
9
|
+
PKG_VERSION = Shipping::VERSION
|
10
|
+
PKG_NAME = "shipping"
|
11
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
12
|
+
RUBY_FORGE_PROJECT = "shipping"
|
13
|
+
RUBY_FORGE_USER = ENV['RUBY_FORGE_USER'] || "cardmagic"
|
14
|
+
RELEASE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
15
|
+
|
16
|
+
PKG_FILES = FileList[
|
17
|
+
"lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*"
|
18
|
+
]
|
19
|
+
|
20
|
+
desc "Default Task"
|
21
|
+
task :default => [ :test ]
|
22
|
+
|
23
|
+
# Run the unit tests
|
24
|
+
desc "Run all unit tests"
|
25
|
+
Rake::TestTask.new("test") { |t|
|
26
|
+
t.libs << "lib"
|
27
|
+
t.pattern = 'test/*/*_test.rb'
|
28
|
+
t.verbose = true
|
29
|
+
}
|
30
|
+
|
31
|
+
# Make a console, useful when working on tests
|
32
|
+
desc "Generate a test console"
|
33
|
+
task :console do
|
34
|
+
verbose( false ) { sh "irb -I lib/ -r 'shipping'" }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Genereate the RDoc documentation
|
38
|
+
desc "Create documentation"
|
39
|
+
Rake::RDocTask.new("doc") { |rdoc|
|
40
|
+
rdoc.title = "Ruby Shipping - UPS, FedEx, USPS"
|
41
|
+
rdoc.rdoc_dir = 'html'
|
42
|
+
rdoc.rdoc_files.include('README')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
}
|
45
|
+
|
46
|
+
# Genereate the package
|
47
|
+
spec = Gem::Specification.new do |s|
|
48
|
+
|
49
|
+
#### Basic information.
|
50
|
+
|
51
|
+
s.name = 'shipping'
|
52
|
+
s.version = PKG_VERSION
|
53
|
+
s.summary = <<-EOF
|
54
|
+
A general shipping module to find out the shipping prices via UPS or FedEx.
|
55
|
+
EOF
|
56
|
+
s.description = <<-EOF
|
57
|
+
A general shipping module to find out the shipping prices via UPS or FedEx.
|
58
|
+
EOF
|
59
|
+
|
60
|
+
#### Which files are to be included in this gem? Everything! (Except CVS directories.)
|
61
|
+
|
62
|
+
s.files = PKG_FILES
|
63
|
+
|
64
|
+
#### Load-time details: library and application (you will need one or both).
|
65
|
+
|
66
|
+
s.add_dependency('builder', '>= 1.2.0')
|
67
|
+
s.requirements << "An xml-builder library."
|
68
|
+
|
69
|
+
s.require_path = 'lib'
|
70
|
+
s.autorequire = 'shipping'
|
71
|
+
|
72
|
+
#### Documentation and testing.
|
73
|
+
|
74
|
+
s.has_rdoc = true
|
75
|
+
|
76
|
+
#### Author and project details.
|
77
|
+
|
78
|
+
s.author = "Lucas Carlson"
|
79
|
+
s.email = "lucas@rufy.com"
|
80
|
+
s.homepage = "http://shipping.rufy.com/"
|
81
|
+
end
|
82
|
+
|
83
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
84
|
+
pkg.need_zip = true
|
85
|
+
pkg.need_tar = true
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Report code statistics (KLOCs, etc) from the application"
|
89
|
+
task :stats do
|
90
|
+
require 'code_statistics'
|
91
|
+
CodeStatistics.new(
|
92
|
+
["Library", "lib"],
|
93
|
+
["Units", "test"]
|
94
|
+
).to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "Publish new documentation"
|
98
|
+
task :publish do
|
99
|
+
Rake::RubyForgePublisher.new('shipping', 'cardmagic').upload
|
100
|
+
`ssh rufy update-shipping-doc`
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Publish the release files to RubyForge."
|
104
|
+
task :upload => [:package] do
|
105
|
+
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
106
|
+
|
107
|
+
if RUBY_FORGE_PROJECT then
|
108
|
+
require 'net/http'
|
109
|
+
require 'open-uri'
|
110
|
+
|
111
|
+
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
112
|
+
project_data = open(project_uri) { |data| data.read }
|
113
|
+
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
114
|
+
raise "Couldn't get group id" unless group_id
|
115
|
+
|
116
|
+
# This echos password to shell which is a bit sucky
|
117
|
+
if ENV["RUBY_FORGE_PASSWORD"]
|
118
|
+
password = ENV["RUBY_FORGE_PASSWORD"]
|
119
|
+
else
|
120
|
+
password = Proc.new do
|
121
|
+
sync = STDOUT.sync
|
122
|
+
begin
|
123
|
+
echo false
|
124
|
+
STDOUT.sync = true
|
125
|
+
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
126
|
+
STDIN.gets.chomp
|
127
|
+
ensure
|
128
|
+
echo true
|
129
|
+
STDOUT.sync = sync
|
130
|
+
puts
|
131
|
+
end
|
132
|
+
end.call
|
133
|
+
end
|
134
|
+
|
135
|
+
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
136
|
+
data = [
|
137
|
+
"login=1",
|
138
|
+
"form_loginname=#{RUBY_FORGE_USER}",
|
139
|
+
"form_pw=#{password}"
|
140
|
+
].join("&")
|
141
|
+
http.post("/account/login.php", data)
|
142
|
+
end
|
143
|
+
|
144
|
+
cookie = login_response["set-cookie"]
|
145
|
+
raise "Login failed" unless cookie
|
146
|
+
headers = { "Cookie" => cookie }
|
147
|
+
|
148
|
+
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
149
|
+
release_data = open(release_uri, headers) { |data| data.read }
|
150
|
+
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
151
|
+
raise "Couldn't get package id" unless package_id
|
152
|
+
|
153
|
+
first_file = true
|
154
|
+
release_id = ""
|
155
|
+
|
156
|
+
files.each do |filename|
|
157
|
+
basename = File.basename(filename)
|
158
|
+
file_ext = File.extname(filename)
|
159
|
+
file_data = File.open(filename, "rb") { |file| file.read }
|
160
|
+
|
161
|
+
puts "Releasing #{basename}..."
|
162
|
+
|
163
|
+
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
164
|
+
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
165
|
+
type_map = {
|
166
|
+
".zip" => "3000",
|
167
|
+
".tgz" => "3110",
|
168
|
+
".gz" => "3110",
|
169
|
+
".gem" => "1400"
|
170
|
+
}; type_map.default = "9999"
|
171
|
+
type = type_map[file_ext]
|
172
|
+
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
173
|
+
|
174
|
+
query_hash = if first_file then
|
175
|
+
{
|
176
|
+
"group_id" => group_id,
|
177
|
+
"package_id" => package_id,
|
178
|
+
"release_name" => RELEASE_NAME,
|
179
|
+
"release_date" => release_date,
|
180
|
+
"type_id" => type,
|
181
|
+
"processor_id" => "8000", # Any
|
182
|
+
"release_notes" => "",
|
183
|
+
"release_changes" => "",
|
184
|
+
"preformatted" => "1",
|
185
|
+
"submit" => "1"
|
186
|
+
}
|
187
|
+
else
|
188
|
+
{
|
189
|
+
"group_id" => group_id,
|
190
|
+
"release_id" => release_id,
|
191
|
+
"package_id" => package_id,
|
192
|
+
"step2" => "1",
|
193
|
+
"type_id" => type,
|
194
|
+
"processor_id" => "8000", # Any
|
195
|
+
"submit" => "Add This File"
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
query = "?" + query_hash.map do |(name, value)|
|
200
|
+
[name, URI.encode(value)].join("=")
|
201
|
+
end.join("&")
|
202
|
+
|
203
|
+
data = [
|
204
|
+
"--" + boundary,
|
205
|
+
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
206
|
+
"Content-Type: application/octet-stream",
|
207
|
+
"Content-Transfer-Encoding: binary",
|
208
|
+
"", file_data, ""
|
209
|
+
].join("\x0D\x0A")
|
210
|
+
|
211
|
+
release_headers = headers.merge(
|
212
|
+
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
213
|
+
)
|
214
|
+
|
215
|
+
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
216
|
+
http.post(target + query, data, release_headers)
|
217
|
+
end
|
218
|
+
|
219
|
+
if first_file then
|
220
|
+
release_id = release_response.body[/release_id=(\d+)/, 1]
|
221
|
+
raise("Couldn't get release id") unless release_id
|
222
|
+
end
|
223
|
+
|
224
|
+
first_file = false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
begin
|
230
|
+
if !defined?(USE_TERMIOS) || USE_TERMIOS
|
231
|
+
require 'termios'
|
232
|
+
else
|
233
|
+
raise LoadError
|
234
|
+
end
|
235
|
+
|
236
|
+
# Enable or disable stdin echoing to the terminal.
|
237
|
+
def echo(enable)
|
238
|
+
term = Termios::getattr(STDIN)
|
239
|
+
|
240
|
+
if enable
|
241
|
+
term.c_lflag |= (Termios::ECHO | Termios::ICANON)
|
242
|
+
else
|
243
|
+
term.c_lflag &= ~Termios::ECHO
|
244
|
+
end
|
245
|
+
|
246
|
+
Termios::setattr(STDIN, Termios::TCSANOW, term)
|
247
|
+
end
|
248
|
+
rescue LoadError
|
249
|
+
def echo(enable)
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
# Author:: Lucas Carlson (mailto:lucas@rufy.com)
|
2
|
+
# Copyright:: Copyright (c) 2005 Lucas Carlson
|
3
|
+
# License:: LGPL
|
4
|
+
|
5
|
+
# Updated:: 12-22-2008 by Mark Dickson (mailto:mark@sitesteaders.com)
|
6
|
+
|
7
|
+
module Shipping
|
8
|
+
VERSION = "1.6.0"
|
9
|
+
|
10
|
+
class ShippingError < StandardError; end
|
11
|
+
class ShippingRequiredFieldError < StandardError; end
|
12
|
+
|
13
|
+
class Base
|
14
|
+
attr_reader :data, :response, :plain_response, :required, :services
|
15
|
+
|
16
|
+
attr_writer :ups_license_number, :ups_shipper_number, :ups_user, :ups_password, :ups_url, :ups_tool
|
17
|
+
attr_writer :fedex_account, :fedex_meter, :fedex_url, :fedex_package_weight_limit_in_lbs
|
18
|
+
|
19
|
+
attr_accessor :name, :phone, :company, :email, :address, :address2, :city, :state, :zip, :country
|
20
|
+
attr_accessor :sender_name, :sender_phone, :sender_company, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip, :sender_country
|
21
|
+
|
22
|
+
attr_accessor :weight, :weight_units, :insured_value, :declared_value, :transaction_type, :description
|
23
|
+
attr_accessor :measure_units, :measure_length, :measure_width, :measure_height
|
24
|
+
attr_accessor :package_total, :packaging_type, :service_type
|
25
|
+
|
26
|
+
attr_accessor :price, :discount_price, :eta, :time_in_transit
|
27
|
+
|
28
|
+
attr_accessor :ship_date, :dropoff_type, :pay_type, :currency_code, :image_type, :label_type
|
29
|
+
|
30
|
+
attr_accessor :weight_each, :quantity, :max_weight, :max_quantity, :items
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
prefs = File.expand_path(options[:prefs] || "~/.shipping.yml")
|
34
|
+
YAML.load(File.open(prefs)).each {|pref, value| eval("@#{pref} = #{value.inspect}")} if File.exists?(prefs)
|
35
|
+
|
36
|
+
@required = Array.new
|
37
|
+
@services = Array.new
|
38
|
+
|
39
|
+
# include all provided data
|
40
|
+
options.each do |method, value|
|
41
|
+
instance_variable_set("@#{method}", value)
|
42
|
+
end
|
43
|
+
|
44
|
+
case options[:carrier]
|
45
|
+
when "fedex"
|
46
|
+
fedex
|
47
|
+
when "ups"
|
48
|
+
ups
|
49
|
+
when nil
|
50
|
+
else
|
51
|
+
raise ShippingError, "unknown service"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Initializes an instance of Shipping::FedEx with the same instance variables as the base object
|
56
|
+
def fedex
|
57
|
+
Shipping::FedEx.new prepare_vars
|
58
|
+
end
|
59
|
+
|
60
|
+
# Initializes an instance of Shipping::UPS with the same instance variables as the base object
|
61
|
+
def ups
|
62
|
+
Shipping::UPS.new prepare_vars
|
63
|
+
end
|
64
|
+
|
65
|
+
# Attempt to package items in multiple boxes efficiently
|
66
|
+
# This doesn't use the bin-packing algorithm, but instead attempts to mirror how people pack boxes
|
67
|
+
# -- since people will most likely be packing them.
|
68
|
+
# -- It attempts to pack like items whenever possible.
|
69
|
+
# @items: array of weights
|
70
|
+
# @weight_each: can be used instead of array of items
|
71
|
+
# @variation_threshold: how much variety you'll allow (default to 10% variation [e.g. 10 items, 10 each])
|
72
|
+
# @weight_threshold: the minimum weight a box must be to close (default .5, i.e. half full by weight)
|
73
|
+
# @quantity_threshold: the minimum full a box must be to close (default .5, i.e. half full by the number of items that will fit)
|
74
|
+
def boxes
|
75
|
+
# See if we're dealing with an array of items
|
76
|
+
if @items.length > 0
|
77
|
+
@items.each {|item| item[:total_weight] = item[:weight] * item[:quantity]} # get weight totals
|
78
|
+
props = @items.inject({:weights => [], :quantities => [], :total_weights => []}) {|h, item| h[:weights] << item[:weight];h[:quantities] << item[:quantity]; h[:total_weights] << item[:total_weight];h}
|
79
|
+
@quantity = props[:quantities].sum
|
80
|
+
total_weight = props[:total_weights].sum
|
81
|
+
|
82
|
+
# check to see if these are all the same weight
|
83
|
+
if props[:weights].uniq.length == 1
|
84
|
+
itemized = false
|
85
|
+
@weight_each = props[:weights].uniq[0]
|
86
|
+
else
|
87
|
+
itemized = true
|
88
|
+
end
|
89
|
+
else
|
90
|
+
@required = ['quantity', 'weight_each']
|
91
|
+
total_weight = @quantity.to_f * @weight_each
|
92
|
+
itemized = false
|
93
|
+
end
|
94
|
+
|
95
|
+
max_weight = @max_weight || 150 # Fed Ex and UPS commercial max
|
96
|
+
max_quantity = @max_quantity || @quantity
|
97
|
+
variation_threshold = @variation_threshold || 0.1 # default to 10% variation (e.g. 10 items, 10 each)
|
98
|
+
weight_threshold = @weight_threshold || 0.5 # default to half full
|
99
|
+
quantity_threshold = @quantity_threshold || 0.5 #default to half full
|
100
|
+
box = Array.new
|
101
|
+
|
102
|
+
# See if boxes should be divided by weight or number
|
103
|
+
bw = total_weight / max_weight
|
104
|
+
bq = @quantity.to_f / max_quantity
|
105
|
+
min_boxes = [bw.ceil, bq.ceil].max.to_i
|
106
|
+
|
107
|
+
# work with list of items
|
108
|
+
if itemized
|
109
|
+
leftovers = Array.new
|
110
|
+
variation = @items.length / @quantity.to_f # this shows us how much repetition there is
|
111
|
+
|
112
|
+
# First, we attempt to pack like items/weights
|
113
|
+
# we can skip this if variation is really high
|
114
|
+
if variation < variation_threshold
|
115
|
+
@items.each do |item|
|
116
|
+
while item[:quantity] > 0
|
117
|
+
max = (@max_weight / item[:weight]).truncate # how many of this weight can be packed in
|
118
|
+
this_num = [max, @max_quantity, item[:quantity]].min # should we pack by weight, number avail, or quantity
|
119
|
+
this_weight = this_num * item[:weight]
|
120
|
+
item[:quantity] -= this_num
|
121
|
+
|
122
|
+
# if we haven't met the threshold
|
123
|
+
if (this_weight / @max_weight) <= weight_threshold and (this_num / @max_quantity) <= quantity_threshold
|
124
|
+
leftovers << {:weight => this_weight, :quantity => this_num, :item => item[:id]}
|
125
|
+
else #otherwise, pack it
|
126
|
+
box << {:weight => this_weight, :quantity => this_num, :item => item[:id]}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
leftovers = @items
|
132
|
+
end
|
133
|
+
|
134
|
+
# Then, we pack all the leftovers
|
135
|
+
leftover_box = {:weight => @max_weight, :quantity => @max_quantity}
|
136
|
+
this_box = {:weight => 0.0, :quantity => 0}
|
137
|
+
leftovers.each do |item|
|
138
|
+
for i in 1..item[:quantity]
|
139
|
+
leftover_box[:weight] -= item[:weight]
|
140
|
+
leftover_box[:quantity] -= 1
|
141
|
+
if leftover_box[:weight] > 0 and leftover_box[:quantity] > 0
|
142
|
+
this_box[:weight] += item[:weight]
|
143
|
+
this_box[:quantity] += 1
|
144
|
+
elsif leftover_box[:weight] = 0 and leftover_box[:quantity] >= 0
|
145
|
+
this_box[:weight] += item[:weight]
|
146
|
+
this_box[:quantity] += 1
|
147
|
+
box << {:weight => this_box[:weight], :quantity => this_box[:quantity]}
|
148
|
+
leftover_box = {:weight => @max_weight, :quantity => @max_quantity}
|
149
|
+
this_box = {:weight => 0.0, :quantity => 0}
|
150
|
+
else
|
151
|
+
box << {:weight => this_box[:weight], :quantity => this_box[:quantity]}
|
152
|
+
leftover_box = {:weight => @max_weight, :quantity => @max_quantity}
|
153
|
+
this_box = {:weight => 0.0, :quantity => 0}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
if this_box[:weight] > 0.0 and this_box[:quantity] > 0
|
158
|
+
box << {:weight => this_box[:weight], :quantity => this_box[:quantity]}
|
159
|
+
end
|
160
|
+
|
161
|
+
inefficiency = box.length / min_boxes
|
162
|
+
|
163
|
+
else # pack super efficiently
|
164
|
+
if bw > bq
|
165
|
+
box_weight = max_weight
|
166
|
+
box_quantity = max_weight / @weight_each
|
167
|
+
else
|
168
|
+
box_weight = max_quantity * @weight_each
|
169
|
+
box_quantity = max_quantity
|
170
|
+
end
|
171
|
+
|
172
|
+
# fill the rest of the boxes
|
173
|
+
num_boxes = min_boxes - 1
|
174
|
+
(num_boxes).times do
|
175
|
+
box << {:weight => box_weight, :quantity => box_quantity}
|
176
|
+
end
|
177
|
+
|
178
|
+
# if there is an uneven number for packaging
|
179
|
+
if @quantity % min_boxes != 0 or num_boxes == 0
|
180
|
+
excess_q = @quantity - (box_quantity * num_boxes)
|
181
|
+
excess_w = excess_q * @weight_each
|
182
|
+
box << {:weight => excess_w, :quantity => excess_q}
|
183
|
+
end
|
184
|
+
inefficiency = 0
|
185
|
+
end
|
186
|
+
return box
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.state_from_zip(zip)
|
190
|
+
zip = zip.to_i
|
191
|
+
{
|
192
|
+
(99500...99929) => "AK",
|
193
|
+
(35000...36999) => "AL",
|
194
|
+
(71600...72999) => "AR",
|
195
|
+
(75502...75505) => "AR",
|
196
|
+
(85000...86599) => "AZ",
|
197
|
+
(90000...96199) => "CA",
|
198
|
+
(80000...81699) => "CO",
|
199
|
+
(6000...6999) => "CT",
|
200
|
+
(20000...20099) => "DC",
|
201
|
+
(20200...20599) => "DC",
|
202
|
+
(19700...19999) => "DE",
|
203
|
+
(32000...33999) => "FL",
|
204
|
+
(34100...34999) => "FL",
|
205
|
+
(30000...31999) => "GA",
|
206
|
+
(96700...96798) => "HI",
|
207
|
+
(96800...96899) => "HI",
|
208
|
+
(50000...52999) => "IA",
|
209
|
+
(83200...83899) => "ID",
|
210
|
+
(60000...62999) => "IL",
|
211
|
+
(46000...47999) => "IN",
|
212
|
+
(66000...67999) => "KS",
|
213
|
+
(40000...42799) => "KY",
|
214
|
+
(45275...45275) => "KY",
|
215
|
+
(70000...71499) => "LA",
|
216
|
+
(71749...71749) => "LA",
|
217
|
+
(1000...2799) => "MA",
|
218
|
+
(20331...20331) => "MD",
|
219
|
+
(20600...21999) => "MD",
|
220
|
+
(3801...3801) => "ME",
|
221
|
+
(3804...3804) => "ME",
|
222
|
+
(3900...4999) => "ME",
|
223
|
+
(48000...49999) => "MI",
|
224
|
+
(55000...56799) => "MN",
|
225
|
+
(63000...65899) => "MO",
|
226
|
+
(38600...39799) => "MS",
|
227
|
+
(59000...59999) => "MT",
|
228
|
+
(27000...28999) => "NC",
|
229
|
+
(58000...58899) => "ND",
|
230
|
+
(68000...69399) => "NE",
|
231
|
+
(3000...3803) => "NH",
|
232
|
+
(3809...3899) => "NH",
|
233
|
+
(7000...8999) => "NJ",
|
234
|
+
(87000...88499) => "NM",
|
235
|
+
(89000...89899) => "NV",
|
236
|
+
(400...599) => "NY",
|
237
|
+
(6390...6390) => "NY",
|
238
|
+
(9000...14999) => "NY",
|
239
|
+
(43000...45999) => "OH",
|
240
|
+
(73000...73199) => "OK",
|
241
|
+
(73400...74999) => "OK",
|
242
|
+
(97000...97999) => "OR",
|
243
|
+
(15000...19699) => "PA",
|
244
|
+
(2800...2999) => "RI",
|
245
|
+
(6379...6379) => "RI",
|
246
|
+
(29000...29999) => "SC",
|
247
|
+
(57000...57799) => "SD",
|
248
|
+
(37000...38599) => "TN",
|
249
|
+
(72395...72395) => "TN",
|
250
|
+
(73300...73399) => "TX",
|
251
|
+
(73949...73949) => "TX",
|
252
|
+
(75000...79999) => "TX",
|
253
|
+
(88501...88599) => "TX",
|
254
|
+
(84000...84799) => "UT",
|
255
|
+
(20105...20199) => "VA",
|
256
|
+
(20301...20301) => "VA",
|
257
|
+
(20370...20370) => "VA",
|
258
|
+
(22000...24699) => "VA",
|
259
|
+
(5000...5999) => "VT",
|
260
|
+
(98000...99499) => "WA",
|
261
|
+
(49936...49936) => "WI",
|
262
|
+
(53000...54999) => "WI",
|
263
|
+
(24700...26899) => "WV",
|
264
|
+
(82000...83199) => "WY"
|
265
|
+
}.each do |range, state|
|
266
|
+
return state if range.include? zip
|
267
|
+
end
|
268
|
+
|
269
|
+
raise ShippingError, "Invalid zip code"
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
def prepare_vars #:nodoc:
|
275
|
+
h = eval(%q{instance_variables.map {|var| "#{var.gsub("@",":")} => #{eval(var+'.inspect')}"}.join(", ").chomp(", ")})
|
276
|
+
return eval("{#{h}}")
|
277
|
+
end
|
278
|
+
|
279
|
+
# Goes out, posts the data, and sets the @response variable with the information
|
280
|
+
def get_response(url)
|
281
|
+
check_required
|
282
|
+
uri = URI.parse url
|
283
|
+
http = Net::HTTP.new uri.host, uri.port
|
284
|
+
if uri.port == 443
|
285
|
+
http.use_ssl = true
|
286
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
287
|
+
end
|
288
|
+
@response_plain = http.post(uri.path, @data).body
|
289
|
+
@response = @response_plain.include?('<?xml') ? REXML::Document.new(@response_plain) : @response_plain
|
290
|
+
|
291
|
+
@response.instance_variable_set "@response_plain", @response_plain
|
292
|
+
def @response.plain; @response_plain; end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Make sure that the required fields are not empty
|
296
|
+
def check_required
|
297
|
+
for var in @required
|
298
|
+
raise ShippingRequiredFieldError, "The #{var} variable needs to be set" if eval("@#{var}").nil?
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
STATES = {"al" => "alabama", "ne" => "nebraska", "ak" => "alaska", "nv" => "nevada", "az" => "arizona", "nh" => "new hampshire", "ar" => "arkansas", "nj" => "new jersey", "ca" => "california", "nm" => "new mexico", "co" => "colorado", "ny" => "new york", "ct" => "connecticut", "nc" => "north carolina", "de" => "delaware", "nd" => "north dakota", "fl" => "florida", "oh" => "ohio", "ga" => "georgia", "ok" => "oklahoma", "hi" => "hawaii", "or" => "oregon", "id" => "idaho", "pa" => "pennsylvania", "il" => "illinois", "pr" => "puerto rico", "in" => "indiana", "ri" => "rhode island", "ia" => "iowa", "sc" => "south carolina", "ks" => "kansas", "sd" => "south dakota", "ky" => "kentucky", "tn" => "tennessee", "la" => "louisiana", "tx" => "texas", "me" => "maine", "ut" => "utah", "md" => "maryland", "vt" => "vermont", "ma" => "massachusetts", "va" => "virginia", "mi" => "michigan", "wa" => "washington", "mn" => "minnesota", "dc" => "district of columbia", "ms" => "mississippi", "wv" => "west virginia", "mo" => "missouri", "wi" => "wisconsin", "mt" => "montana", "wy" => "wyoming"}
|
303
|
+
|
304
|
+
def self.initialize_for_fedex_service(xml)
|
305
|
+
s = Shipping::Base.new
|
306
|
+
s.fedex
|
307
|
+
s.eta = REXML::XPath.first(xml, "DeliveryDate").text unless REXML::XPath.match(xml, "DeliveryDate").empty?
|
308
|
+
s.service_type = REXML::XPath.first(xml, "Service").text
|
309
|
+
s.discount_price = REXML::XPath.first(xml, "EstimatedCharges/DiscountedCharges/BaseCharge").text
|
310
|
+
s.price = REXML::XPath.first(xml, "EstimatedCharges/DiscountedCharges/NetCharge").text
|
311
|
+
return s
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|