shipping 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,6 +1,6 @@
1
1
  == Welcome to Shipping
2
2
 
3
- Shipping is a module that connects APIs for various shippers like UPS and FedEx.
3
+ Shipping is a module that connects APIs for various shippers like UPS and FedEx. Shipping currently supports pricing, shipping, labeling and voiding for UPS and FedEx through their XML APIs.
4
4
 
5
5
  == Download
6
6
 
@@ -15,16 +15,19 @@ There is going to be some data that will persist for all connections. For exampl
15
15
  fedex_account: 1234556
16
16
  fedex_meter: 387878
17
17
 
18
- ups_account: 7B4F74E3075AEEFF
18
+ ups_url: https://wwwcie.ups.com/ups.app/xml
19
+ ups_license_number: 7B4F74E3075AEEFF
19
20
  ups_user: username
20
21
  ups_password: password
22
+ ups_shipper_number: 855AA0
23
+
21
24
 
22
25
  You can set as many default values as you would like in this file.
23
26
 
24
27
  require 'shipping'
25
28
 
26
- ups = Shipping::UPS.new :zip => 97202, :sender_zip => 10001, :weight => 2
27
- ups.price => 8.77
29
+ ups = Shipping::UPS.new :zip => 97202, :state => "OR", :sender_zip => 10001, :weight => 2
30
+ ups.price => 5.8
28
31
  ups.valid_address? => false
29
32
 
30
33
  ups.city = "Portland"
@@ -33,14 +36,15 @@ You can set as many default values as you would like in this file.
33
36
  Alternately, you can instantiate the base class and then see both UPS and FedEx information.
34
37
 
35
38
  ship = Shipping::Base.new :zip => 97202, :state => "OR", :sender_zip => 10001, :weight => 2
36
- ship.ups.price => 8.77
37
- ship.fedex.price => 5.17
39
+ ship.ups.price => 5.8
40
+ ship.fedex.price => 5.8
38
41
 
39
42
  ship.city = "Portland"
40
43
  ship.ups.valid_address? => true
41
44
 
42
45
  == Authors
43
46
  * Lucas Carlson (mailto:lucas@rufy.com)
47
+ * Noah Zoschke (mailto:noah@bitscribe.net)
44
48
 
45
49
  This library is released under the terms of the GNU LGPL.
46
50
 
data/lib/shipping.rb CHANGED
@@ -38,6 +38,8 @@ require 'rexml/document'
38
38
  require 'net/http'
39
39
  require 'net/https'
40
40
  require 'base64'
41
+ require 'fileutils'
42
+ require 'tempfile'
41
43
 
42
44
  require 'extensions'
43
45
  require 'shipping/base'
data/lib/shipping/base.rb CHANGED
@@ -3,159 +3,162 @@
3
3
  # License:: LGPL
4
4
 
5
5
  module Shipping
6
- VERSION = "1.3.0"
7
-
8
- class ShippingError < StandardError; end
9
-
10
- class Base
11
- attr_reader :data, :response, :plain_response, :required
12
-
13
- attr_writer :ups_account, :ups_user, :ups_password
14
- attr_writer :fedex_account, :fedex_meter, :fedex_url
15
-
16
- attr_accessor :name, :phone, :company, :email, :address, :address2, :city, :state, :zip, :country
17
- attr_accessor :sender_name, :sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip, :sender_country
18
-
19
- attr_accessor :weight, :weight_units, :insured_value, :declared_value, :transaction_type, :description
20
- attr_accessor :package_total, :packaging_type, :service_type
21
-
22
- attr_accessor :ship_date, :dropoff_type, :pay_type, :currency_code, :image_type, :label_type
23
-
24
- def initialize(options = {})
25
- prefs = File.expand_path(options[:prefs] || "~/.shipping.yml")
26
- YAML.load(File.open(prefs)).each {|pref, value| eval("@#{pref} = #{value.inspect}")} if File.exists?(prefs)
27
-
28
- @required = Array.new
29
-
30
- # include all provided data
31
- options.each do |method, value|
32
- instance_variable_set("@#{method}", value)
33
- end
34
- end
35
-
36
- # Initializes an instance of Shipping::FedEx with the same instance variables as the base object
37
- def fedex
38
- Shipping::FedEx.new prepare_vars
39
- end
40
-
41
- # Initializes an instance of Shipping::UPS with the same instance variables as the base object
42
- def ups
43
- Shipping::UPS.new prepare_vars
44
- end
45
-
46
- def self.state_from_zip(zip)
47
- zip = zip.to_i
48
- {
49
- (99500...99929) => "AK",
50
- (35000...36999) => "AL",
51
- (71600...72999) => "AR",
52
- (75502...75505) => "AR",
53
- (85000...86599) => "AZ",
54
- (90000...96199) => "CA",
55
- (80000...81699) => "CO",
56
- (6000...6999) => "CT",
57
- (20000...20099) => "DC",
58
- (20200...20599) => "DC",
59
- (19700...19999) => "DE",
60
- (32000...33999) => "FL",
61
- (34100...34999) => "FL",
62
- (30000...31999) => "GA",
63
- (96700...96798) => "HI",
64
- (96800...96899) => "HI",
65
- (50000...52999) => "IA",
66
- (83200...83899) => "ID",
67
- (60000...62999) => "IL",
68
- (46000...47999) => "IN",
69
- (66000...67999) => "KS",
70
- (40000...42799) => "KY",
71
- (45275...45275) => "KY",
72
- (70000...71499) => "LA",
73
- (71749...71749) => "LA",
74
- (1000...2799) => "MA",
75
- (20331...20331) => "MD",
76
- (20600...21999) => "MD",
77
- (3801...3801) => "ME",
78
- (3804...3804) => "ME",
79
- (3900...4999) => "ME",
80
- (48000...49999) => "MI",
81
- (55000...56799) => "MN",
82
- (63000...65899) => "MO",
83
- (38600...39799) => "MS",
84
- (59000...59999) => "MT",
85
- (27000...28999) => "NC",
86
- (58000...58899) => "ND",
87
- (68000...69399) => "NE",
88
- (3000...3803) => "NH",
89
- (3809...3899) => "NH",
90
- (7000...8999) => "NJ",
91
- (87000...88499) => "NM",
92
- (89000...89899) => "NV",
93
- (400...599) => "NY",
94
- (6390...6390) => "NY",
95
- (9000...14999) => "NY",
96
- (43000...45999) => "OH",
97
- (73000...73199) => "OK",
98
- (73400...74999) => "OK",
99
- (97000...97999) => "OR",
100
- (15000...19699) => "PA",
101
- (2800...2999) => "RI",
102
- (6379...6379) => "RI",
103
- (29000...29999) => "SC",
104
- (57000...57799) => "SD",
105
- (37000...38599) => "TN",
106
- (72395...72395) => "TN",
107
- (73300...73399) => "TX",
108
- (73949...73949) => "TX",
109
- (75000...79999) => "TX",
110
- (88501...88599) => "TX",
111
- (84000...84799) => "UT",
112
- (20105...20199) => "VA",
113
- (20301...20301) => "VA",
114
- (20370...20370) => "VA",
115
- (22000...24699) => "VA",
116
- (5000...5999) => "VT",
117
- (98000...99499) => "WA",
118
- (49936...49936) => "WI",
119
- (53000...54999) => "WI",
120
- (24700...26899) => "WV",
121
- (82000...83199) => "WY"
122
- }.each do |range, state|
123
- return state if range.include? zip
124
- end
125
-
126
- raise ShippingError, "Invalid zip code"
127
- end
128
-
129
- private
130
-
131
- def prepare_vars #:nodoc:
132
- h = eval(%q{instance_variables.map {|var| "#{var.gsub("@",":")} => #{eval(var+'.inspect')}"}.join(", ").chomp(", ")})
133
- return eval("{#{h}}")
134
- end
135
-
136
- # Goes out, posts the data, and sets the @response variable with the information
137
- def get_response(url)
138
- check_required
139
- uri = URI.parse url
140
- http = Net::HTTP.new uri.host, uri.port
141
- if uri.port == 443
142
- http.use_ssl = true
143
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
144
- end
145
- @response_plain = http.post(uri.path, @data).body
146
- @response = @response_plain.include?('<?xml') ? REXML::Document.new(@response_plain) : @response_plain
147
-
148
- @response.instance_variable_set "@response_plain", @response_plain
149
- def @response.plain; @response_plain; end
150
- end
151
-
152
- # Make sure that the required fields are not empty
153
- def check_required
154
- for var in @required
155
- raise ShippingError, "The #{var} variable needs to be set" if eval("@#{var}").nil?
156
- end
157
- end
158
-
159
- 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"}
160
- end
161
- end
6
+ VERSION = "1.5.0"
7
+
8
+ class ShippingError < StandardError; end
9
+ class ShippingRequiredFieldError < StandardError; end
10
+
11
+ class Base
12
+ attr_reader :data, :response, :plain_response, :required
13
+
14
+ attr_writer :ups_license_number, :ups_shipper_number, :ups_user, :ups_password, :ups_url, :ups_tool
15
+ attr_writer :fedex_account, :fedex_meter, :fedex_url
16
+
17
+ attr_accessor :name, :phone, :company, :email, :address, :address2, :city, :state, :zip, :country
18
+ attr_accessor :sender_name, :sender_phone, :sender_company, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip, :sender_country
19
+
20
+ attr_accessor :weight, :weight_units, :insured_value, :declared_value, :transaction_type, :description
21
+ attr_accessor :measure_units, :measure_length, :measure_width, :measure_height
22
+ attr_accessor :package_total, :packaging_type, :service_type
23
+
24
+ attr_accessor :ship_date, :dropoff_type, :pay_type, :currency_code, :image_type, :label_type
25
+
26
+ def initialize(options = {})
27
+ prefs = File.expand_path(options[:prefs] || "~/.shipping.yml")
28
+ YAML.load(File.open(prefs)).each {|pref, value| eval("@#{pref} = #{value.inspect}")} if File.exists?(prefs)
29
+
30
+ @required = Array.new
31
+
32
+ # include all provided data
33
+ options.each do |method, value|
34
+ instance_variable_set("@#{method}", value)
35
+ end
36
+
37
+ end
38
+
39
+ # Initializes an instance of Shipping::FedEx with the same instance variables as the base object
40
+ def fedex
41
+ Shipping::FedEx.new prepare_vars
42
+ end
43
+
44
+ # Initializes an instance of Shipping::UPS with the same instance variables as the base object
45
+ def ups
46
+ Shipping::UPS.new prepare_vars
47
+ end
48
+
49
+ def self.state_from_zip(zip)
50
+ zip = zip.to_i
51
+ {
52
+ (99500...99929) => "AK",
53
+ (35000...36999) => "AL",
54
+ (71600...72999) => "AR",
55
+ (75502...75505) => "AR",
56
+ (85000...86599) => "AZ",
57
+ (90000...96199) => "CA",
58
+ (80000...81699) => "CO",
59
+ (6000...6999) => "CT",
60
+ (20000...20099) => "DC",
61
+ (20200...20599) => "DC",
62
+ (19700...19999) => "DE",
63
+ (32000...33999) => "FL",
64
+ (34100...34999) => "FL",
65
+ (30000...31999) => "GA",
66
+ (96700...96798) => "HI",
67
+ (96800...96899) => "HI",
68
+ (50000...52999) => "IA",
69
+ (83200...83899) => "ID",
70
+ (60000...62999) => "IL",
71
+ (46000...47999) => "IN",
72
+ (66000...67999) => "KS",
73
+ (40000...42799) => "KY",
74
+ (45275...45275) => "KY",
75
+ (70000...71499) => "LA",
76
+ (71749...71749) => "LA",
77
+ (1000...2799) => "MA",
78
+ (20331...20331) => "MD",
79
+ (20600...21999) => "MD",
80
+ (3801...3801) => "ME",
81
+ (3804...3804) => "ME",
82
+ (3900...4999) => "ME",
83
+ (48000...49999) => "MI",
84
+ (55000...56799) => "MN",
85
+ (63000...65899) => "MO",
86
+ (38600...39799) => "MS",
87
+ (59000...59999) => "MT",
88
+ (27000...28999) => "NC",
89
+ (58000...58899) => "ND",
90
+ (68000...69399) => "NE",
91
+ (3000...3803) => "NH",
92
+ (3809...3899) => "NH",
93
+ (7000...8999) => "NJ",
94
+ (87000...88499) => "NM",
95
+ (89000...89899) => "NV",
96
+ (400...599) => "NY",
97
+ (6390...6390) => "NY",
98
+ (9000...14999) => "NY",
99
+ (43000...45999) => "OH",
100
+ (73000...73199) => "OK",
101
+ (73400...74999) => "OK",
102
+ (97000...97999) => "OR",
103
+ (15000...19699) => "PA",
104
+ (2800...2999) => "RI",
105
+ (6379...6379) => "RI",
106
+ (29000...29999) => "SC",
107
+ (57000...57799) => "SD",
108
+ (37000...38599) => "TN",
109
+ (72395...72395) => "TN",
110
+ (73300...73399) => "TX",
111
+ (73949...73949) => "TX",
112
+ (75000...79999) => "TX",
113
+ (88501...88599) => "TX",
114
+ (84000...84799) => "UT",
115
+ (20105...20199) => "VA",
116
+ (20301...20301) => "VA",
117
+ (20370...20370) => "VA",
118
+ (22000...24699) => "VA",
119
+ (5000...5999) => "VT",
120
+ (98000...99499) => "WA",
121
+ (49936...49936) => "WI",
122
+ (53000...54999) => "WI",
123
+ (24700...26899) => "WV",
124
+ (82000...83199) => "WY"
125
+ }.each do |range, state|
126
+ return state if range.include? zip
127
+ end
128
+
129
+ raise ShippingError, "Invalid zip code"
130
+ end
131
+
132
+ private
133
+
134
+ def prepare_vars #:nodoc:
135
+ h = eval(%q{instance_variables.map {|var| "#{var.gsub("@",":")} => #{eval(var+'.inspect')}"}.join(", ").chomp(", ")})
136
+ return eval("{#{h}}")
137
+ end
138
+
139
+ # Goes out, posts the data, and sets the @response variable with the information
140
+ def get_response(url)
141
+ check_required
142
+ uri = URI.parse url
143
+ http = Net::HTTP.new uri.host, uri.port
144
+ if uri.port == 443
145
+ http.use_ssl = true
146
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
147
+ end
148
+ @response_plain = http.post(uri.path, @data).body
149
+ @response = @response_plain.include?('<?xml') ? REXML::Document.new(@response_plain) : @response_plain
150
+
151
+ @response.instance_variable_set "@response_plain", @response_plain
152
+ def @response.plain; @response_plain; end
153
+ end
154
+
155
+ # Make sure that the required fields are not empty
156
+ def check_required
157
+ for var in @required
158
+ raise ShippingRequiredFieldError, "The #{var} variable needs to be set" if eval("@#{var}").nil?
159
+ end
160
+ end
161
+
162
+ 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"}
163
+ end
164
+ end
@@ -6,29 +6,28 @@
6
6
  # See http://www.fedex.com/us/solutions/wis/pdf/xml_transguide.pdf?link=4 for the full XML-based API
7
7
 
8
8
  module Shipping
9
- class FedEx < Base
10
- # Gets the list price the regular consumer would have to pay. Discount price is what the
11
- # person with this particular account number will pay
12
- def price
13
- get_price
14
- return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/ListCharges/NetCharge").text.to_f
15
- rescue
16
- raise ShippingError, get_error
17
- end
18
-
19
- # Gets the discount price of the shipping (with discounts taken into consideration).
20
- # "base price" doesn't include surcharges like fuel cost, so I don't think it is the correct price to get
21
- def discount_price
22
- get_price
23
- return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/DiscountedCharges/NetCharge").text.to_f
24
- rescue
25
- raise ShippingError, get_error
26
- end
27
-
9
+ class FedEx < Base
10
+ # Gets the list price the regular consumer would have to pay. Discount price is what the
11
+ # person with this particular account number will pay
12
+ def price
13
+ get_price
14
+ return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/ListCharges/NetCharge").text.to_f
15
+ rescue ShippingError
16
+ raise ShippingError, get_error
17
+ end
18
+
19
+ # Gets the discount price of the shipping (with discounts taken into consideration).
20
+ # "base price" doesn't include surcharges like fuel cost, so I don't think it is the correct price to get
21
+ def discount_price
22
+ get_price
23
+ return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/DiscountedCharges/NetCharge").text.to_f
24
+ rescue ShippingError
25
+ raise ShippingError, get_error
26
+ end
27
+
28
28
  # still not sure what the best way to handle this transaction's return data would be. Possibly a hash of the form {service => delivery_estimate}?
29
29
  def express_service_availability
30
-
31
- @data = String.new
30
+ @data = String.new
32
31
  b = Builder::XmlMarkup.new :target => @data
33
32
  b.instruct!
34
33
  b.FDXServiceAvailabilityRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXServiceAvailabilityRequest.xsd') { |b|
@@ -47,452 +46,478 @@ module Shipping
47
46
  b.ShipDate @ship_date unless @ship_date.blank?
48
47
  b.PackageCount @package_total || '1'
49
48
  }
50
-
49
+
51
50
  get_response @fedex_url
52
51
  end
53
52
 
54
53
  def register
55
- @required = [:name, :company, :phone, :email, :address, :city, :state, :zip]
56
- @required += [:fedex_account, :fedex_url]
57
-
58
- state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase rescue nil
54
+ @required = [:name, :company, :phone, :email, :address, :city, :state, :zip]
55
+ @required += [:fedex_account, :fedex_url]
59
56
 
60
- @data = String.new
61
- b = Builder::XmlMarkup.new :target => @data
57
+ state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase rescue nil
58
+
59
+ @data = String.new
60
+ b = Builder::XmlMarkup.new :target => @data
62
61
  b.instruct!
63
62
  b.FDXSubscriptionRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXSubscriptionRequest.xsd') { |b|
64
63
  b.RequestHeader { |b|
65
- b.CustomerTransactionIdentifier @transaction_identifier if @transaction_identifier # optional
64
+ b.CustomerTransactionIdentifier @transaction_identifier if @transaction_identifier # optional
66
65
  b.AccountNumber @fedex_account
67
66
  }
68
67
  b.Contact { |b|
69
- b.PersonName @name
70
- b.CompanyName @company
71
- b.Department @department if @department
72
- b.PhoneNumber @phone.gsub(/[^\d]/,"")
73
- b.tag! :"E-MailAddress", @email
68
+ b.PersonName @name
69
+ b.CompanyName @company
70
+ b.Department @department if @department
71
+ b.PhoneNumber @phone.gsub(/[^\d]/,"")
72
+ b.tag! :"E-MailAddress", @email
74
73
  }
75
74
  b.Address { |b|
76
- b.Line1 @address
77
- b.Line2 @address2 if @address2
78
- b.City @city
79
- b.StateOrProvinceCode state
80
- b.PostalCode @zip
81
- b.CountryCode @country || 'US'
75
+ b.Line1 @address
76
+ b.Line2 @address2 if @address2
77
+ b.City @city
78
+ b.StateOrProvinceCode state
79
+ b.PostalCode @zip
80
+ b.CountryCode @country || 'US'
82
81
  }
83
- }
84
-
85
- get_response @fedex_url
86
-
87
- return REXML::XPath.first(@response, "//FDXSubscriptionReply/MeterNumber").text
82
+ }
83
+
84
+ get_response @fedex_url
85
+
86
+ return REXML::XPath.first(@response, "//FDXSubscriptionReply/MeterNumber").text
88
87
  end
89
-
88
+
90
89
  # require 'fileutils'
91
90
  # fedex = Shipping::FedEx.new :name => 'John Doe', ... , :sender_zip => 97202
92
91
  # label = fedex.label
93
92
  # puts label.tracking_number
94
- # FileUtils.cp label.image.path, '/path/to/my/images/directory/'
93
+ # FileUtils.cp label.image.path, '/path/to/my/images/directory/'
95
94
  #
96
95
  # There are several types of labels that can be returned by changing @image_type.
97
96
  # PNG is selected by default.
98
97
  #
99
98
  def label
100
- @required = [:phone, :email, :address, :city, :state, :zip ]
101
- @required += [:sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip ]
102
- @required += [:fedex_account, :fedex_url]
103
-
104
- @transaction_type ||= 'rate_ground'
105
- @weight = (@weight.to_f*10).round/10.0
106
- @declared_value = (@declared_value.to_f*100).round/100.0 unless @declared_value.blank?
107
- state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase
108
- sender_state = STATES.has_value?(@sender_state.downcase) ? STATES.index(@sender_state.downcase).upcase : @sender_state.upcase
109
-
110
- @data = String.new
111
-
112
- b = Builder::XmlMarkup.new :target => @data
113
- b.instruct!
114
- b.FDXShipRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXShipRequest.xsd') { |b|
115
- b.RequestHeader { |b|
116
- b.AccountNumber @fedex_account
117
- b.MeterNumber @fedex_meter
118
- b.CarrierCode TransactionTypes[@transaction_type][1]
119
- }
120
- b.ShipDate((Time.now).strftime("%Y-%m-%d"))
121
- b.ShipTime((Time.now).strftime("%H:%M:%S"))
122
- b.DropoffType @dropoff_type || 'REGULARPICKUP'
123
- b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
124
- b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
125
- b.WeightUnits @weight_units || 'LBS' # or KGS
126
- b.Weight @weight
127
- b.CurrencyCode @currency_code || 'USD'
128
- b.Origin { |b|
129
- b.Contact { |b|
130
- if @sender_name.to_s.size > 2
131
- b.PersonName @sender_name
132
- b.CompanyName @sender_company unless @sender_company.blank?
133
- elsif @sender_company.to_s.size > 2
134
- b.PersonName @sender_name unless @sender_name.blank?
135
- b.CompanyName @sender_company
136
- else
137
- raise ShippingError, "Either the sender_name or the sender_company value must be bigger than 2 characters."
138
- end
139
- b.Department @sender_department unless @sender_department.blank?
140
- b.PhoneNumber @sender_phone.gsub(/[^\d]/,"")
141
- b.PagerNumber @sender_pager.gsub(/[^\d]/,"") if @sender_pager.class == String
142
- b.FaxNumber @sender_fax.gsub(/[^\d]/,"") if @sender_fax.class == String
143
- b.tag! :"E-MailAddress", @sender_email
144
- }
145
- b.Address { |b|
146
- b.Line1 @sender_address
147
- b.Line2 @sender_address2 unless @sender_address2.blank?
148
- b.City @sender_city
149
- b.StateOrProvinceCode sender_state
150
- b.PostalCode @sender_zip
151
- b.CountryCode @sender_country || 'US'
152
- }
153
- }
154
- b.Destination { |b|
155
- b.Contact { |b|
156
- if @name.to_s.size > 2
157
- b.PersonName @name
158
- b.CompanyName @company unless @company.blank?
159
- elsif @company.to_s.size > 2
160
- b.PersonName @name unless @name.blank?
161
- b.CompanyName @company
162
- else
163
- raise ShippingError, "Either the name or the company value must be bigger than 2 characters."
164
- end
165
- b.Department @department unless @department.blank?
166
- b.PhoneNumber @phone.gsub(/[^\d]/,"")
167
- b.PagerNumber @pager.gsub(/[^\d]/,"") if @pager.class == String
168
- b.FaxNumber @fax.gsub(/[^\d]/,"") if @fax.class == String
169
- b.tag! :"E-MailAddress", @email
170
- }
171
- b.Address { |b|
172
- b.Line1 @address
173
- b.Line2 @address2 unless @address2.blank?
174
- b.City @city
175
- b.StateOrProvinceCode state
176
- b.PostalCode @zip
177
- b.CountryCode @country || 'US'
178
- }
179
- }
180
- b.Payment { |b|
181
- b.PayorType PaymentTypes[@pay_type] || 'SENDER'
182
- b.Payor { |b|
183
- b.AccountNumber @payor_account_number
184
- b.CountryCode @payor_country_code unless @payor_country_code.blank?
185
- } unless @payor_account_number.blank?
186
- }
187
- b.RMA { |b|
188
- b.Number @rma_number
189
- } unless @rma_number.blank?
190
- b.SpecialServices { |b|
191
- b.EMailNotification { |b|
192
- b.ShipAlertOptionalMessage @message
193
- b.Shipper { |b|
194
- b.ShipAlert @shipper_ship_alert ? 'true' : 'false'
195
- b.LanguageCode @shipper_language || 'EN' # FR also available
196
- }
197
- b.Recipient { |b|
198
- b.ShipAlert @recipient_ship_alert ? 'true' : 'false'
199
- b.LanguageCode @recipient_language || 'EN' # FR also available
200
- }
201
- b.Other { |b|
202
- b.tag! :"E-MailAddress", @other_email
203
- b.ShipAlert @other_ship_alert ? 'true' : 'false'
204
- b.LanguageCode @other_language || 'EN' # FR also available
205
- } unless @other_email.blank?
206
- }
207
- } unless @message.blank?
208
- b.Label { |b|
209
- b.Type @label_type || '2DCOMMON'
210
- b.ImageType @image_type || 'PNG'
211
- }
212
- }
213
- get_response @fedex_url
214
-
215
- begin
216
- response = Hash.new
217
- response[:tracking_number] = REXML::XPath.first(@response, "//FDXShipReply/Tracking/TrackingNumber").text
218
- response[:encoded_image] = REXML::XPath.first(@response, "//FDXShipReply/Labels/OutboundLabel").text
219
- response[:image] = Tempfile.new("shipping_label")
220
- response[:image].write Base64.decode64( response[:encoded_image] )
221
- response[:image].rewind
222
- rescue
223
- raise ShippingError, get_error
224
- end
225
-
226
- # allows for things like fedex.label.url
227
- def response.method_missing(name, *args)
228
- has_key?(name) ? self[name] : super
229
- end
230
-
231
- # don't allow people to edit the response
232
- response.freeze
99
+ @required = [:phone, :email, :address, :city, :state, :zip ]
100
+ @required += [:sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip ]
101
+ @required += [:fedex_account, :fedex_url, :fedex_meter]
102
+
103
+ @transaction_type ||= 'ship_ground'
104
+ @weight = (@weight.to_f*10).round/10.0
105
+ @declared_value = (@declared_value.to_f*100).round/100.0 unless @declared_value.blank?
106
+ state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase
107
+ sender_state = STATES.has_value?(@sender_state.downcase) ? STATES.index(@sender_state.downcase).upcase : @sender_state.upcase
108
+
109
+ @data = String.new
110
+ b = Builder::XmlMarkup.new :target => @data
111
+ b.instruct!
112
+ b.FDXShipRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXShipRequest.xsd') { |b|
113
+ b.RequestHeader { |b|
114
+ b.AccountNumber @fedex_account
115
+ b.MeterNumber @fedex_meter
116
+ b.CarrierCode TransactionTypes[@transaction_type][1]
117
+ }
118
+ b.ShipDate((Time.now).strftime("%Y-%m-%d"))
119
+ b.ShipTime((Time.now).strftime("%H:%M:%S"))
120
+ b.DropoffType @dropoff_type || 'REGULARPICKUP'
121
+ b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
122
+ b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
123
+ b.WeightUnits @weight_units || 'LBS' # or KGS
124
+ b.Weight @weight
125
+ b.CurrencyCode @currency_code || 'USD'
126
+ b.Origin { |b|
127
+ b.Contact { |b|
128
+ if @sender_name.to_s.size > 2
129
+ b.PersonName @sender_name
130
+ b.CompanyName @sender_company unless @sender_company.blank?
131
+ elsif @sender_company.to_s.size > 2
132
+ b.PersonName @sender_name unless @sender_name.blank?
133
+ b.CompanyName @sender_company
134
+ else
135
+ raise ShippingError, "Either the sender_name or the sender_company value must be bigger than 2 characters."
136
+ end
137
+ b.Department @sender_department unless @sender_department.blank?
138
+ b.PhoneNumber @sender_phone.gsub(/[^\d]/,"")
139
+ b.PagerNumber @sender_pager.gsub(/[^\d]/,"") if @sender_pager.class == String
140
+ b.FaxNumber @sender_fax.gsub(/[^\d]/,"") if @sender_fax.class == String
141
+ b.tag! :"E-MailAddress", @sender_email
142
+ }
143
+ b.Address { |b|
144
+ b.Line1 @sender_address
145
+ b.Line2 @sender_address2 unless @sender_address2.blank?
146
+ b.City @sender_city
147
+ b.StateOrProvinceCode sender_state
148
+ b.PostalCode @sender_zip
149
+ b.CountryCode @sender_country || 'US'
150
+ }
151
+ }
152
+ b.Destination { |b|
153
+ b.Contact { |b|
154
+ if @name.to_s.size > 2
155
+ b.PersonName @name
156
+ b.CompanyName @company unless @company.blank?
157
+ elsif @company.to_s.size > 2
158
+ b.PersonName @name unless @name.blank?
159
+ b.CompanyName @company
160
+ else
161
+ raise ShippingError, "Either the name or the company value must be bigger than 2 characters."
162
+ end
163
+ b.Department @department unless @department.blank?
164
+ b.PhoneNumber @phone.gsub(/[^\d]/,"")
165
+ b.PagerNumber @pager.gsub(/[^\d]/,"") if @pager.class == String
166
+ b.FaxNumber @fax.gsub(/[^\d]/,"") if @fax.class == String
167
+ b.tag! :"E-MailAddress", @email
168
+ }
169
+ b.Address { |b|
170
+ b.Line1 @address
171
+ b.Line2 @address2 unless @address2.blank?
172
+ b.City @city
173
+ b.StateOrProvinceCode state
174
+ b.PostalCode @zip
175
+ b.CountryCode @country || 'US'
176
+ }
177
+ }
178
+ b.Payment { |b|
179
+ b.PayorType PaymentTypes[@pay_type] || 'SENDER'
180
+ b.Payor { |b|
181
+ b.AccountNumber @payor_account_number
182
+ b.CountryCode @payor_country_code unless @payor_country_code.blank?
183
+ } unless @payor_account_number.blank?
184
+ }
185
+ b.RMA { |b|
186
+ b.Number @rma_number
187
+ } unless @rma_number.blank?
188
+ b.SpecialServices { |b|
189
+ b.EMailNotification { |b|
190
+ b.ShipAlertOptionalMessage @message
191
+ b.Shipper { |b|
192
+ b.ShipAlert @shipper_ship_alert ? 'true' : 'false'
193
+ b.LanguageCode @shipper_language || 'EN' # FR also available
194
+ }
195
+ b.Recipient { |b|
196
+ b.ShipAlert @recipient_ship_alert ? 'true' : 'false'
197
+ b.LanguageCode @recipient_language || 'EN' # FR also available
198
+ }
199
+ b.Other { |b|
200
+ b.tag! :"E-MailAddress", @other_email
201
+ b.ShipAlert @other_ship_alert ? 'true' : 'false'
202
+ b.LanguageCode @other_language || 'EN' # FR also available
203
+ } unless @other_email.blank?
204
+ }
205
+ } unless @message.blank?
206
+ b.Label { |b|
207
+ b.Type @label_type || '2DCOMMON'
208
+ b.ImageType @image_type || 'PNG'
209
+ }
210
+ }
211
+ get_response @fedex_url
212
+
213
+ begin
214
+ response = Hash.new
215
+ response[:tracking_number] = REXML::XPath.first(@response, "//FDXShipReply/Tracking/TrackingNumber").text
216
+ response[:encoded_image] = REXML::XPath.first(@response, "//FDXShipReply/Labels/OutboundLabel").text
217
+ response[:image] = Tempfile.new("shipping_label")
218
+ response[:image].write Base64.decode64( response[:encoded_image] )
219
+ response[:image].rewind
220
+ rescue
221
+ raise ShippingError, get_error
222
+ end
223
+
224
+ # allows for things like fedex.label.url
225
+ def response.method_missing(name, *args)
226
+ has_key?(name) ? self[name] : super
227
+ end
228
+
229
+ # don't allow people to edit the response
230
+ response.freeze
233
231
  end
234
232
 
235
- # require 'fileutils'
236
- # fedex = Shipping::FedEx.new :name => 'John Doe', ... , :sender_zip => 97202
237
- # label = fedex.email_label
238
- # puts label.url
239
- # puts label.tracking_number
240
- #
233
+ # require 'fileutils'
234
+ # fedex = Shipping::FedEx.new :name => 'John Doe', ... , :sender_zip => 97202
235
+ # label = fedex.email_label
236
+ # puts label.url
237
+ # puts label.tracking_number
238
+ #
241
239
  def return_label
242
- @required = [:phone, :email, :address, :city, :state, :zip ]
243
- @required += [:sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip ]
244
- @required += [:fedex_account, :fedex_url, :weight ]
245
-
246
- @transaction_type ||= 'rate_ground'
247
- @weight = (@weight.to_f*10).round/10.0
248
- @declared_value = (@declared_value.to_f*100).round/100.0 unless @declared_value.blank?
249
-
250
- state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase rescue nil
251
- sender_state = STATES.has_value?(@sender_state.downcase) ? STATES.index(@sender_state.downcase).upcase : @sender_state.upcase rescue nil
252
-
253
- @data = String.new
254
-
255
- b = Builder::XmlMarkup.new :target => @data
240
+ @required = [:phone, :email, :address, :city, :state, :zip ]
241
+ @required += [:sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip ]
242
+ @required += [:fedex_account, :fedex_url, :fedex_meter, :weight ]
243
+
244
+ @transaction_type ||= 'ship_ground'
245
+ @weight = (@weight.to_f*10).round/10.0
246
+ @declared_value = (@declared_value.to_f*100).round/100.0 unless @declared_value.blank?
247
+
248
+ state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase rescue nil
249
+ sender_state = STATES.has_value?(@sender_state.downcase) ? STATES.index(@sender_state.downcase).upcase : @sender_state.upcase rescue nil
250
+
251
+ @data = String.new
252
+ b = Builder::XmlMarkup.new :target => @data
256
253
  b.instruct!
257
254
  b.FDXEmailLabelRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXEmailLabelRequest.xsd') { |b|
258
- b.RequestHeader { |b|
259
- b.AccountNumber @fedex_account
260
- b.MeterNumber @fedex_meter
261
- b.CarrierCode TransactionTypes[@transaction_type][1]
262
- }
263
- b.URLExpirationDate((Time.now + 3600*24).strftime("%Y-%m-%d"))
264
- b.tag! :"URLNotificationE-MailAddress", @sender_email
265
- b.MerchantPhoneNumber @sender_phone.gsub(/[^\d]/,"")
266
- b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
267
- b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
268
- b.WeightUnits @weight_units || 'LBS' # or KGS
269
- b.CurrencyCode @currency_code || 'USD'
270
- b.Origin { |b|
271
- b.Contact { |b|
272
- if @sender_name.to_s.size > 2
273
- b.PersonName @sender_name
274
- b.CompanyName @sender_company unless @sender_company.blank?
275
- elsif @sender_company.to_s.size > 2
276
- b.PersonName @sender_name unless @sender_name.blank?
277
- b.CompanyName @sender_company
278
- else
279
- raise ShippingError, "Either the sender_name or the sender_company value must be bigger than 2 characters."
280
- end
281
- b.Department @sender_department unless @sender_department.blank?
282
- b.PhoneNumber @sender_phone.gsub(/[^\d]/,"")
283
- b.PagerNumber @sender_pager.gsub(/[^\d]/,"") if @sender_pager.class == String
284
- b.FaxNumber @sender_fax.gsub(/[^\d]/,"") if @sender_fax.class == String
285
- b.tag! :"E-MailAddress", @sender_email
286
- }
287
- b.Address { |b|
288
- b.Line1 @sender_address
289
- b.Line2 @sender_address2 unless @sender_address2.blank?
290
- b.City @sender_city
291
- b.StateOrProvinceCode sender_state
292
- b.PostalCode @sender_zip
293
- b.CountryCode @sender_country || 'US'
294
- }
295
- }
296
- b.Destination { |b|
297
- b.Contact { |b|
298
- if @name.to_s.size > 2
299
- b.PersonName @name
300
- b.CompanyName @company unless @company.blank?
301
- elsif @company.to_s.size > 2
302
- b.PersonName @name unless @name.blank?
303
- b.CompanyName @company
304
- else
305
- raise ShippingError, "Either the name or the company value must be bigger than 2 characters."
306
- end
307
- b.Department @department unless @department.blank?
308
- b.PhoneNumber @phone.gsub(/[^\d]/,"")
309
- b.PagerNumber @pager.gsub(/[^\d]/,"") if @pager.class == String
310
- b.FaxNumber @fax.gsub(/[^\d]/,"") if @fax.class == String
311
- b.tag! :"E-MailAddress", @email
312
- }
313
- b.Address { |b|
314
- b.Line1 @address
315
- b.Line2 @address2 unless @address2.blank?
316
- b.City @city
317
- b.StateOrProvinceCode state
318
- b.PostalCode @zip
319
- b.CountryCode @country || 'US'
320
- }
321
- }
322
- b.Payment { |b|
323
- b.PayorType PaymentTypes[@pay_type] || 'SENDER'
324
- b.Payor { |b|
325
- b.AccountNumber @payor_account_number
326
- b.CountryCode @payor_country_code unless @payor_country_code.blank?
327
- } unless @payor_account_number.blank?
328
- }
329
- b.RMA { |b|
330
- b.Number @rma_number
331
- } unless @rma_number.blank?
332
- b.Package { |b|
333
- b.Weight @weight
334
- b.DeclaredValue @declared_value || '99.00'
335
- b.ReferenceInfo { |b|
336
- b.CustomerReference @customer_reference
337
- } unless @customer_reference.blank?
338
- b.ItemDescription @description || "Shipment"
339
- }
340
- b.SpecialServices { |b|
341
- b.EMailNotification { |b|
342
- b.ShipAlertOptionalMessage @message
343
- b.Shipper { |b|
344
- b.ShipAlert @shipper_ship_alert ? 'true' : 'false'
345
- b.LanguageCode @shipper_language || 'EN' # FR also available
346
- }
347
- b.Recipient { |b|
348
- b.ShipAlert @recipient_ship_alert ? 'true' : 'false'
349
- b.LanguageCode @recipient_language || 'EN' # FR also available
350
- }
351
- b.Other { |b|
352
- b.tag! :"E-MailAddress", @other_email
353
- b.ShipAlert @other_ship_alert ? 'true' : 'false'
354
- b.LanguageCode @other_language || 'EN' # FR also available
355
- } unless @other_email.blank?
356
- }
357
- } unless @message.blank?
358
- }
359
-
360
- get_response @fedex_url
361
-
362
- begin
363
- response = Hash.new
364
- response[:url] = REXML::XPath.first(@response, "//FDXEmailLabelReply/URL").text
365
- response[:userid] = REXML::XPath.first(@response, "//FDXEmailLabelReply/UserID").text
366
- response[:password] = REXML::XPath.first(@response, "//FDXEmailLabelReply/Password").text
367
- response[:tracking_number] = REXML::XPath.first(@response, "//FDXEmailLabelReply/Package/TrackingNumber").text
368
- rescue
369
- raise ShippingError, get_error
370
- end
371
-
372
- # allows for things like fedex.label.url
373
- def response.method_missing(name, *args)
374
- has_key?(name) ? self[name] : super
375
- end
376
-
377
- # don't allow people to edit the response
378
- return response.freeze
379
- end
380
-
381
- private
382
-
383
- def get_price #:nodoc:
384
- @required = [:zip, :sender_zip, :weight]
385
- @required += [:transaction_type, :fedex_account, :fedex_meter, :fedex_url]
386
-
387
- @transaction_type ||= 'rate_ground'
388
- @weight = (@weight.to_f*10).round/10.0
389
-
390
- @data = String.new
391
- b = Builder::XmlMarkup.new(:target => @data)
392
- b.instruct!
393
- b.FDXRateRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXRateRequest.xsd') { |b|
394
- b.RequestHeader { |b|
395
- b.AccountNumber @fedex_account
396
- b.MeterNumber @fedex_meter
397
- b.CarrierCode TransactionTypes[@transaction_type][1]
398
- }
399
- b.ShipDate @ship_date unless @ship_date.blank?
400
- b.DropoffType @dropoff_type || 'REGULARPICKUP'
401
- b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
402
- b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
403
- b.WeightUnits @weight_units || 'LBS'
404
- b.Weight @weight
405
- b.ListRate true #tells fedex to return list rates as well as discounted rates
406
- b.OriginAddress { |b|
407
- b.StateOrProvinceCode self.class.state_from_zip(@sender_zip)
408
- b.PostalCode @sender_zip
409
- b.CountryCode @sender_country_code || "US"
410
- }
411
- b.DestinationAddress { |b|
412
- b.StateOrProvinceCode self.class.state_from_zip(@zip)
413
- b.PostalCode @zip
414
- b.CountryCode @country || "US"
415
- }
416
- b.Payment { |b|
417
- b.PayorType PaymentTypes[@pay_type] || 'SENDER'
418
- }
419
- b.PackageCount @package_total || '1'
420
- }
421
-
422
- get_response @fedex_url
423
- end
424
-
425
- def get_error
426
- return if @response.class != REXML::Document
255
+ b.RequestHeader { |b|
256
+ b.AccountNumber @fedex_account
257
+ b.MeterNumber @fedex_meter
258
+ b.CarrierCode TransactionTypes[@transaction_type][1]
259
+ }
260
+ b.URLExpirationDate((Time.now + 3600*24).strftime("%Y-%m-%d"))
261
+ b.tag! :"URLNotificationE-MailAddress", @sender_email
262
+ b.MerchantPhoneNumber @sender_phone.gsub(/[^\d]/,"")
263
+ b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
264
+ b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
265
+ b.WeightUnits @weight_units || 'LBS' # or KGS
266
+ b.CurrencyCode @currency_code || 'USD'
267
+ b.Origin { |b|
268
+ b.Contact { |b|
269
+ if @sender_name.to_s.size > 2
270
+ b.PersonName @sender_name
271
+ b.CompanyName @sender_company unless @sender_company.blank?
272
+ elsif @sender_company.to_s.size > 2
273
+ b.PersonName @sender_name unless @sender_name.blank?
274
+ b.CompanyName @sender_company
275
+ else
276
+ raise ShippingError, "Either the sender_name or the sender_company value must be bigger than 2 characters."
277
+ end
278
+ b.Department @sender_department unless @sender_department.blank?
279
+ b.PhoneNumber @sender_phone.gsub(/[^\d]/,"")
280
+ b.PagerNumber @sender_pager.gsub(/[^\d]/,"") if @sender_pager.class == String
281
+ b.FaxNumber @sender_fax.gsub(/[^\d]/,"") if @sender_fax.class == String
282
+ b.tag! :"E-MailAddress", @sender_email
283
+ }
284
+ b.Address { |b|
285
+ b.Line1 @sender_address
286
+ b.Line2 @sender_address2 unless @sender_address2.blank?
287
+ b.City @sender_city
288
+ b.StateOrProvinceCode sender_state
289
+ b.PostalCode @sender_zip
290
+ b.CountryCode @sender_country || 'US'
291
+ }
292
+ }
293
+ b.Destination { |b|
294
+ b.Contact { |b|
295
+ if @name.to_s.size > 2
296
+ b.PersonName @name
297
+ b.CompanyName @company unless @company.blank?
298
+ elsif @company.to_s.size > 2
299
+ b.PersonName @name unless @name.blank?
300
+ b.CompanyName @company
301
+ else
302
+ raise ShippingError, "Either the name or the company value must be bigger than 2 characters."
303
+ end
304
+ b.Department @department unless @department.blank?
305
+ b.PhoneNumber @phone.gsub(/[^\d]/,"")
306
+ b.PagerNumber @pager.gsub(/[^\d]/,"") if @pager.class == String
307
+ b.FaxNumber @fax.gsub(/[^\d]/,"") if @fax.class == String
308
+ b.tag! :"E-MailAddress", @email
309
+ }
310
+ b.Address { |b|
311
+ b.Line1 @address
312
+ b.Line2 @address2 unless @address2.blank?
313
+ b.City @city
314
+ b.StateOrProvinceCode state
315
+ b.PostalCode @zip
316
+ b.CountryCode @country || 'US'
317
+ }
318
+ }
319
+
320
+ b.Payment { |b|
321
+ b.PayorType PaymentTypes[@pay_type] || 'SENDER'
322
+ b.Payor { |b|
323
+ b.AccountNumber @payor_account_number
324
+ b.CountryCode @payor_country_code unless @payor_country_code.blank?
325
+ } unless @payor_account_number.blank?
326
+ }
327
+
328
+ b.RMA { |b|
329
+ b.Number @rma_number
330
+ } unless @rma_number.blank?
331
+
332
+ b.Package { |b|
333
+ b.Weight @weight
334
+ b.DeclaredValue @declared_value || '99.00'
335
+ b.ReferenceInfo { |b|
336
+ b.CustomerReference @customer_reference
337
+ } unless @customer_reference.blank?
338
+ b.ItemDescription @description || "Shipment"
339
+ }
340
+
341
+ b.SpecialServices { |b|
342
+ b.EMailNotification { |b|
343
+ b.ShipAlertOptionalMessage @message
344
+ b.Shipper { |b|
345
+ b.ShipAlert @shipper_ship_alert ? 'true' : 'false'
346
+ b.LanguageCode @shipper_language || 'EN' # FR also available
347
+ }
348
+ b.Recipient { |b|
349
+ b.ShipAlert @recipient_ship_alert ? 'true' : 'false'
350
+ b.LanguageCode @recipient_language || 'EN' # FR also available
351
+ }
352
+ b.Other { |b|
353
+ b.tag! :"E-MailAddress", @other_email
354
+ b.ShipAlert @other_ship_alert ? 'true' : 'false'
355
+ b.LanguageCode @other_language || 'EN' # FR also available
356
+ } unless @other_email.blank?
357
+ }
358
+ } unless @message.blank?
359
+ }
360
+
361
+ get_response @fedex_url
362
+
363
+ begin
364
+ response = Hash.new
365
+ response[:url] = REXML::XPath.first(@response, "//FDXEmailLabelReply/URL").text
366
+ response[:userid] = REXML::XPath.first(@response, "//FDXEmailLabelReply/UserID").text
367
+ response[:password] = REXML::XPath.first(@response, "//FDXEmailLabelReply/Password").text
368
+ response[:tracking_number] = REXML::XPath.first(@response, "//FDXEmailLabelReply/Package/TrackingNumber").text
369
+ rescue
370
+ raise ShippingError, get_error
371
+ end
372
+
373
+ # allows for things like fedex.label.url
374
+ def response.method_missing(name, *args)
375
+ has_key?(name) ? self[name] : super
376
+ end
377
+
378
+ # don't allow people to edit the response
379
+ return response.freeze
380
+ end
381
+
382
+ def void(tracking_number)
383
+ @required = [:fedex_account, :fedex_url, :fedex_meter]
384
+
385
+ @transaction_type ||= 'ship_ground'
386
+
387
+ @data = String.new
388
+ b = Builder::XmlMarkup.new :target => @data
389
+ b.instruct!
390
+ b.FDXShipDeleteRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXShipDeleteRequest.xsd') { |b|
391
+ b.RequestHeader { |b|
392
+ b.AccountNumber @fedex_account
393
+ b.MeterNumber @fedex_meter
394
+ b.CarrierCode TransactionTypes[@transaction_type][1]
395
+ }
396
+ b.TrackingNumber tracking_number
397
+ }
427
398
 
428
- code = REXML::XPath.first(@response, "//Error/Code").text
429
- message = REXML::XPath.first(@response, "//Error/Message").text
399
+ get_response @fedex_url
400
+ raise ShippingError, get_error if get_error
401
+ return true
402
+ end
403
+
404
+ private
405
+
406
+ def get_price #:nodoc:
407
+ @required = [:zip, :sender_zip, :weight]
408
+ @required += [:transaction_type, :fedex_account, :fedex_meter, :fedex_url]
409
+
410
+ @transaction_type ||= 'rate_ground'
411
+ @weight = (@weight.to_f*10).round/10.0
412
+
413
+ @data = String.new
414
+ b = Builder::XmlMarkup.new(:target => @data)
415
+ b.instruct!
416
+ b.FDXRateRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXRateRequest.xsd') { |b|
417
+ b.RequestHeader { |b|
418
+ b.AccountNumber @fedex_account
419
+ b.MeterNumber @fedex_meter
420
+ b.CarrierCode TransactionTypes[@transaction_type][1]
421
+ }
422
+ b.ShipDate @ship_date unless @ship_date.blank?
423
+ b.DropoffType @dropoff_type || 'REGULARPICKUP'
424
+ b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
425
+ b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
426
+ b.WeightUnits @weight_units || 'LBS'
427
+ b.Weight @weight
428
+ b.ListRate true #tells fedex to return list rates as well as discounted rates
429
+ b.OriginAddress { |b|
430
+ b.StateOrProvinceCode self.class.state_from_zip(@sender_zip)
431
+ b.PostalCode @sender_zip
432
+ b.CountryCode @sender_country_code || "US"
433
+ }
434
+ b.DestinationAddress { |b|
435
+ b.StateOrProvinceCode self.class.state_from_zip(@zip)
436
+ b.PostalCode @zip
437
+ b.CountryCode @country || "US"
438
+ }
439
+ b.Payment { |b|
440
+ b.PayorType PaymentTypes[@pay_type] || 'SENDER'
441
+ }
442
+ b.PackageCount @package_total || '1'
443
+ }
444
+
445
+ get_response @fedex_url
446
+ end
447
+
448
+ def get_error
449
+ return if @response.class != REXML::Document
450
+ error = REXML::XPath.first(@response, "//Error")
451
+ return if !error
430
452
 
431
- return "Error #{code}: #{message}"
432
- end
433
-
434
- # The following type hashes are to allow cross-api data retrieval
435
-
436
- ServiceTypes = {
437
- "priority" => "PRIORITYOVERNIGHT",
438
- "2day" => "FEDEX2DAY",
439
- "standard_overnight" => "STANDARDOVERNIGHT",
440
- "first_overnight" => "FIRSTOVERNIGHT",
441
- "express_saver" => "FEDEXEXPRESSSAVER",
442
- "1day_freight" => "FEDEX1DAYFREIGHT",
443
- "2day_freight" => "FEDEX2DAYFREIGHT",
444
- "3day_freight" => "FEDEX3DAYFREIGHT",
445
- "international_priority" => "INTERNATIONALPRIORITY",
446
- "international_economy" => "INTERNATIONALECONOMY",
447
- "international_first" => "INTERNATIONALFIRST",
448
- "international_priority_freight" => "INTERNATIONALPRIORITYFREIGHT",
449
- "international_economy_freight" => "INTERNATIONALECONOMYFREIGHT",
450
- "home_delivery" => "GROUNDHOMEDELIVERY",
451
- "ground_service" => "FEDEXGROUND",
452
- "international_ground_service" => "INTERNATIONALGROUND"
453
- }
454
-
455
- PackageTypes = {
456
- "fedex_envelope" => "FEDEXENVELOPE",
457
- "fedex_pak" => "FEDEXPAK",
458
- "fedex_box" => "FEDEXBOX",
459
- "fedex_tube" => "FEDEXTUBE",
460
- "fedex_10_kg_box" => "FEDEX10KGBOX",
461
- "fedex_25_kg_box" => "FEDEX25KGBOX",
462
- "your_packaging" => "YOURPACKAGING"
463
- }
464
-
465
- DropoffTypes = {
466
- 'regular_pickup' => 'REGULARPICKUP',
467
- 'request_courier' => 'REQUESTCOURIER',
468
- 'dropbox' => 'DROPBOX',
469
- 'business_service_center' => 'BUSINESSSERVICECENTER',
470
- 'station' => 'STATION'
471
- }
472
-
473
- PaymentTypes = {
474
- 'sender' => 'SENDER',
475
- 'recipient' => 'RECIPIENT',
476
- 'third_party' => 'THIRDPARTY',
477
- 'collect' => 'COLLECT'
478
- }
479
-
480
-
481
- TransactionTypes = {
482
- 'rate_ground' => ['022','FDXG'],
483
- 'rate_express' => ['022','FDXE'],
484
- 'rate_services' => ['025',''],
485
- 'ship_ground' => ['021','FDXG'],
486
- 'ship_express' => ['021','FDXE'],
487
- 'cancel_express' => ['023','FDXE'],
488
- 'cancel_ground' => ['023','FDXG'],
489
- 'close_ground' => ['007','FDXG'],
490
- 'service_available' => ['019','FDXE'],
491
- 'fedex_locater' => ['410',''],
492
- 'subscribe' => ['211',''],
493
- 'sig_proof_delivery' => ['402',''],
494
- 'track' => ['405',''],
495
- 'ref_track' => ['403','']
496
- }
497
- end
453
+ code = REXML::XPath.first(error, "//Code").text
454
+ message = REXML::XPath.first(error, "//Message").text
455
+
456
+ return "Error #{code}: #{message}"
457
+ end
458
+
459
+ # The following type hashes are to allow cross-api data retrieval
460
+
461
+ ServiceTypes = {
462
+ "priority" => "PRIORITYOVERNIGHT",
463
+ "2day" => "FEDEX2DAY",
464
+ "standard_overnight" => "STANDARDOVERNIGHT",
465
+ "first_overnight" => "FIRSTOVERNIGHT",
466
+ "express_saver" => "FEDEXEXPRESSSAVER",
467
+ "1day_freight" => "FEDEX1DAYFREIGHT",
468
+ "2day_freight" => "FEDEX2DAYFREIGHT",
469
+ "3day_freight" => "FEDEX3DAYFREIGHT",
470
+ "international_priority" => "INTERNATIONALPRIORITY",
471
+ "international_economy" => "INTERNATIONALECONOMY",
472
+ "international_first" => "INTERNATIONALFIRST",
473
+ "international_priority_freight" => "INTERNATIONALPRIORITYFREIGHT",
474
+ "international_economy_freight" => "INTERNATIONALECONOMYFREIGHT",
475
+ "home_delivery" => "GROUNDHOMEDELIVERY",
476
+ "ground_service" => "FEDEXGROUND",
477
+ "international_ground_service" => "INTERNATIONALGROUND"
478
+ }
479
+
480
+ PackageTypes = {
481
+ "fedex_envelope" => "FEDEXENVELOPE",
482
+ "fedex_pak" => "FEDEXPAK",
483
+ "fedex_box" => "FEDEXBOX",
484
+ "fedex_tube" => "FEDEXTUBE",
485
+ "fedex_10_kg_box" => "FEDEX10KGBOX",
486
+ "fedex_25_kg_box" => "FEDEX25KGBOX",
487
+ "your_packaging" => "YOURPACKAGING"
488
+ }
489
+
490
+ DropoffTypes = {
491
+ 'regular_pickup' => 'REGULARPICKUP',
492
+ 'request_courier' => 'REQUESTCOURIER',
493
+ 'dropbox' => 'DROPBOX',
494
+ 'business_service_center' => 'BUSINESSSERVICECENTER',
495
+ 'station' => 'STATION'
496
+ }
497
+
498
+ PaymentTypes = {
499
+ 'sender' => 'SENDER',
500
+ 'recipient' => 'RECIPIENT',
501
+ 'third_party' => 'THIRDPARTY',
502
+ 'collect' => 'COLLECT'
503
+ }
504
+
505
+
506
+ TransactionTypes = {
507
+ 'rate_ground' => ['022','FDXG'],
508
+ 'rate_express' => ['022','FDXE'],
509
+ 'rate_services' => ['025',''],
510
+ 'ship_ground' => ['021','FDXG'],
511
+ 'ship_express' => ['021','FDXE'],
512
+ 'cancel_express' => ['023','FDXE'],
513
+ 'cancel_ground' => ['023','FDXG'],
514
+ 'close_ground' => ['007','FDXG'],
515
+ 'service_available' => ['019','FDXE'],
516
+ 'fedex_locater' => ['410',''],
517
+ 'subscribe' => ['211',''],
518
+ 'sig_proof_delivery' => ['402',''],
519
+ 'track' => ['405',''],
520
+ 'ref_track' => ['403','']
521
+ }
522
+ end
498
523
  end