active_shipping 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5868e8cfd35acb6845aa850d2a8e12999cc0a71b
4
- data.tar.gz: 998bba416cc03877b6da20f141fff0272b5f9543
3
+ metadata.gz: 167a59476f3013546de9f351d4d62b045ce18695
4
+ data.tar.gz: 5a8d406aad880b511e8102fe462a3e344a1660d7
5
5
  SHA512:
6
- metadata.gz: 8ecbdea4269a2eeb54d54e45219dbbddd4518fcc65a88cee70575d0c8c40814ec1dbd99986bc715e6bb10e66ab16d3da3ee534e209b4a94ea0557053f5eeb372
7
- data.tar.gz: 4b27e759684cea1e91286dfc062cc5ab167ae9696053b0b252e8c4b0804d7aee04c5ba2f1bf7cbf6bd3a8bc7d63ddc9e37c4317c58c70e9cfb1f84ffaa0f53ee
6
+ metadata.gz: 0a21b4a2467b1ead50736c57cd9b3da5494090e6f4866e78d9f91a8cbeff1e89ac1dd7ff82187677ee6f897f718cc4fabd48fb7f8e49b998b5e6e7e51f045fab
7
+ data.tar.gz: fce0b6be9e67015c62b6ed7651243ad7e09791ca20502509e29c72138a47c400dfca855f4f6203a5f27796916d09467b2931690f5d6440652913d5b13068c019
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # ActiveShipping CHANGELOG
2
2
 
3
+ ### v1.2.0
4
+
5
+ - Added support for buying labels with FedEx
6
+ - Added support for batched tracking requests with USPS
7
+
8
+ ### v1.1.3
9
+
10
+ - Handle ZIP+4 numbers in USPS tracking
11
+ - Add a field for rate estimate references
12
+ - Add support for UPS mail innovations tracking option
13
+
14
+ ### v1.1.2
15
+
16
+ - Fix finding of error descriptions for USPS tracking
17
+
3
18
  ### v1.1.1
4
19
 
5
20
  - Fix bug with USPS tracking not handling optional fields being absent.
data/README.md CHANGED
@@ -5,6 +5,7 @@ This library interfaces with the web services of various shipping carriers. The
5
5
  - Finding shipping rates
6
6
  - Registering shipments
7
7
  - Tracking shipments
8
+ - Purchasing shipping labels
8
9
 
9
10
  Active Shipping is currently being used and improved in a production environment for [Shopify][]. Development is being done by the Shopify integrations team (<integrations-team@shopify.com>). Discussion is welcome in the [Active Merchant Google Group][discuss].
10
11
 
@@ -36,6 +36,7 @@ require 'active_shipping/tracking_response'
36
36
  require 'active_shipping/delivery_date_estimates_response'
37
37
  require 'active_shipping/shipping_response'
38
38
  require 'active_shipping/label_response'
39
+ require 'active_shipping/label'
39
40
  require 'active_shipping/package'
40
41
  require 'active_shipping/location'
41
42
  require 'active_shipping/rate_estimate'
@@ -68,6 +68,14 @@ module ActiveShipping
68
68
  'station' => 'STATION'
69
69
  }
70
70
 
71
+ SIGNATURE_OPTION_CODES = {
72
+ adult: 'ADULT', # 21 years plus
73
+ direct: 'DIRECT', # A person at the delivery address
74
+ indirect: 'INDIRECT', # A person at the delivery address, or a neighbor, or a signed note for fedex on the door
75
+ none_required: 'NO_SIGNATURE_REQUIRED',
76
+ default_for_service: 'SERVICE_DEFAULT'
77
+ }
78
+
71
79
  PAYMENT_TYPES = {
72
80
  'sender' => 'SENDER',
73
81
  'recipient' => 'RECIPIENT',
@@ -153,10 +161,127 @@ module ActiveShipping
153
161
  parse_tracking_response(xml, options)
154
162
  end
155
163
 
164
+
165
+ # Get Shipping labels
166
+ def create_shipment(origin, destination, packages, options = {})
167
+ options = @options.update(options)
168
+ packages = Array(packages)
169
+ raise Error, "Multiple packages are not supported yet." if packages.length > 1
170
+
171
+ request = build_shipment_request(origin, destination, packages, options)
172
+ logger.debug(request) if logger
173
+
174
+ logger.debug(confirm_response) if logger
175
+
176
+ response = commit(save_request(request), (options[:test] || false))
177
+ parse_ship_response(response)
178
+ end
179
+
156
180
  protected
157
181
 
182
+ def build_shipment_request(origin, destination, packages, options = {})
183
+ imperial = location_uses_imperial(origin)
184
+
185
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
186
+ xml.ProcessShipmentRequest(xmlns: 'http://fedex.com/ws/ship/v13') do
187
+ build_request_header(xml)
188
+ build_version_node(xml, 'ship', 13, 0 ,0)
189
+
190
+ xml.RequestedShipment do
191
+ xml.ShipTimestamp(ship_timestamp(options[:turn_around_time]).iso8601(0))
192
+ xml.DropoffType('REGULAR_PICKUP')
193
+ xml.ServiceType(options[:service_type] || 'FEDEX_GROUND')
194
+ xml.PackagingType('YOUR_PACKAGING')
195
+
196
+ xml.Shipper do
197
+ build_contact_address_nodes(xml, options[:shipper] || origin)
198
+ end
199
+
200
+ xml.Recipient do
201
+ build_contact_address_nodes(xml, destination)
202
+ end
203
+
204
+ xml.Origin do
205
+ build_contact_address_nodes(xml, origin)
206
+ end
207
+
208
+ xml.ShippingChargesPayment do
209
+ xml.PaymentType('SENDER')
210
+ xml.Payor do
211
+ build_shipment_responsible_party_node(xml, options[:shipper] || origin)
212
+ end
213
+ end
214
+
215
+ xml.LabelSpecification do
216
+ xml.LabelFormatType('COMMON2D')
217
+ xml.ImageType('PNG')
218
+ xml.LabelStockType('PAPER_7X4.75')
219
+ end
220
+
221
+ xml.RateRequestTypes('ACCOUNT')
222
+
223
+ xml.PackageCount(packages.size)
224
+ packages.each do |package|
225
+ xml.RequestedPackageLineItems do
226
+ xml.GroupPackageCount(1)
227
+ build_package_weight_node(xml, package, imperial)
228
+ build_package_dimensions_node(xml, package, imperial)
229
+
230
+ # Reference Numbers
231
+ reference_numbers = Array(package.options[:reference_numbers])
232
+ if reference_numbers.size > 0
233
+ xml.CustomerReferences do
234
+ reference_numbers.each do |reference_number_info|
235
+ xml.CustomerReferenceType(reference_number_info[:type] || "CUSTOMER_REFERENCE")
236
+ xml.Value(reference_number_info[:value])
237
+ end
238
+ end
239
+ end
240
+
241
+ xml.SpecialServicesRequested do
242
+ xml.SpecialServiceTypes("SIGNATURE_OPTION")
243
+ xml.SignatureOptionDetail do
244
+ xml.OptionType(SIGNATURE_OPTION_CODES[package.options[:signature_option] || :default_for_service])
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ xml_builder.to_xml
253
+ end
254
+
255
+ def build_contact_address_nodes(xml, location)
256
+ xml.Contact do
257
+ xml.PersonName(location.name)
258
+ xml.CompanyName(location.company)
259
+ xml.PhoneNumber(location.phone)
260
+ end
261
+ xml.Address do
262
+ xml.StreetLines(location.address1) if location.address1
263
+ xml.StreetLines(location.address2) if location.address2
264
+ xml.City(location.city) if location.city
265
+ xml.StateOrProvinceCode(location.state)
266
+ xml.PostalCode(location.postal_code)
267
+ xml.CountryCode(location.country_code(:alpha2))
268
+ xml.Residential('true') if location.residential?
269
+ end
270
+ end
271
+
272
+ def build_shipment_responsible_party_node(xml, origin)
273
+ xml.ResponsibleParty do
274
+ xml.AccountNumber(@options[:account])
275
+ xml.Contact do
276
+ xml.PersonName(origin.name)
277
+ xml.CompanyName(origin.company)
278
+ xml.PhoneNumber(origin.phone)
279
+ end
280
+ end
281
+ end
282
+
158
283
  def build_rate_request(origin, destination, packages, options = {})
159
- imperial = %w(US LR MM).include?(origin.country_code(:alpha2))
284
+ imperial = location_uses_imperial(origin)
160
285
 
161
286
  xml_builder = Nokogiri::XML::Builder.new do |xml|
162
287
  xml.RateRequest(xmlns: 'http://fedex.com/ws/rate/v13') do
@@ -341,31 +466,35 @@ module ActiveShipping
341
466
  success = response_success?(xml)
342
467
  message = response_message(xml)
343
468
 
344
- rate_estimates = xml.root.css('> RateReplyDetails').map do |rated_shipment|
345
- service_code = rated_shipment.at('ServiceType').text
346
- is_saturday_delivery = rated_shipment.at('AppliedOptions').try(:text) == 'SATURDAY_DELIVERY'
347
- service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code
348
-
349
- transit_time = rated_shipment.at('TransitTime').text if service_code == "FEDEX_GROUND"
350
- max_transit_time = rated_shipment.at('MaximumTransitTime').try(:text) if service_code == "FEDEX_GROUND"
351
-
352
- delivery_timestamp = rated_shipment.at('DeliveryTimestamp').try(:text)
353
-
354
- delivery_range = delivery_range_from(transit_time, max_transit_time, delivery_timestamp, options)
355
-
356
- currency = rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').text
357
- RateEstimate.new(origin, destination, @@name,
358
- self.class.service_name_for_code(service_type),
359
- :service_code => service_code,
360
- :total_price => rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').text.to_f,
361
- :currency => currency,
362
- :packages => packages,
363
- :delivery_range => delivery_range)
364
- end
469
+ if success
470
+ rate_estimates = xml.root.css('> RateReplyDetails').map do |rated_shipment|
471
+ service_code = rated_shipment.at('ServiceType').text
472
+ is_saturday_delivery = rated_shipment.at('AppliedOptions').try(:text) == 'SATURDAY_DELIVERY'
473
+ service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code
474
+
475
+ transit_time = rated_shipment.at('TransitTime').text if service_code == "FEDEX_GROUND"
476
+ max_transit_time = rated_shipment.at('MaximumTransitTime').try(:text) if service_code == "FEDEX_GROUND"
477
+
478
+ delivery_timestamp = rated_shipment.at('DeliveryTimestamp').try(:text)
479
+
480
+ delivery_range = delivery_range_from(transit_time, max_transit_time, delivery_timestamp, options)
481
+
482
+ currency = rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').text
483
+ RateEstimate.new(origin, destination, @@name,
484
+ self.class.service_name_for_code(service_type),
485
+ :service_code => service_code,
486
+ :total_price => rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').text.to_f,
487
+ :currency => currency,
488
+ :packages => packages,
489
+ :delivery_range => delivery_range)
490
+ end
365
491
 
366
- if rate_estimates.empty?
367
- success = false
368
- message = "No shipping rates could be found for the destination address" if message.blank?
492
+ if rate_estimates.empty?
493
+ success = false
494
+ message = "No shipping rates could be found for the destination address" if message.blank?
495
+ end
496
+ else
497
+ rate_estimates = []
369
498
  end
370
499
 
371
500
  RateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :request => last_request, :log_xml => options[:log_xml])
@@ -383,6 +512,19 @@ module ActiveShipping
383
512
  delivery_range
384
513
  end
385
514
 
515
+ def parse_ship_response(response)
516
+ xml = build_document(response, 'ProcessShipmentReply')
517
+ success = response_success?(xml)
518
+ message = response_message(xml)
519
+
520
+ response_info = Hash.from_xml(response)
521
+ tracking_number = xml.css("CompletedPackageDetails TrackingIds TrackingNumber").text
522
+ base_64_image = xml.css("Label Image").text
523
+
524
+ labels = [Label.new(tracking_number, Base64.decode64(base_64_image))]
525
+ LabelResponse.new(success, message, response_info, {labels: labels})
526
+ end
527
+
386
528
  def business_days_from(date, days)
387
529
  future_date = date
388
530
  count = 0
@@ -577,5 +719,9 @@ module ActiveShipping
577
719
  rescue Nokogiri::XML::SyntaxError => e
578
720
  raise ActiveShipping::ResponseContentError.new(e, xml)
579
721
  end
722
+
723
+ def location_uses_imperial(location)
724
+ %w(US LR MM).include?(location.country_code(:alpha2))
725
+ end
580
726
  end
581
727
  end
@@ -156,39 +156,32 @@ module ActiveShipping
156
156
  packages = Array(packages)
157
157
  access_request = build_access_request
158
158
 
159
- begin
159
+ # STEP 1: Confirm. Validation step, important for verifying price.
160
+ confirm_request = build_shipment_request(origin, destination, packages, options)
161
+ logger.debug(confirm_request) if logger
160
162
 
161
- # STEP 1: Confirm. Validation step, important for verifying price.
162
- confirm_request = build_shipment_request(origin, destination, packages, options)
163
- logger.debug(confirm_request) if logger
163
+ confirm_response = commit(:ship_confirm, save_request(access_request + confirm_request), (options[:test] || false))
164
+ logger.debug(confirm_response) if logger
164
165
 
165
- confirm_response = commit(:ship_confirm, save_request(access_request + confirm_request), (options[:test] || false))
166
- logger.debug(confirm_response) if logger
167
-
168
- # ... now, get the digest, it's needed to get the label. In theory,
169
- # one could make decisions based on the price or some such to avoid
170
- # surprises. This also has *no* error handling yet.
171
- xml = parse_ship_confirm(confirm_response)
172
- success = response_success?(xml)
173
- message = response_message(xml)
174
- raise message unless success
175
- digest = response_digest(xml)
176
-
177
- # STEP 2: Accept. Use shipment digest in first response to get the actual label.
178
- accept_request = build_accept_request(digest, options)
179
- logger.debug(accept_request) if logger
180
-
181
- accept_response = commit(:ship_accept, save_request(access_request + accept_request), (options[:test] || false))
182
- logger.debug(accept_response) if logger
166
+ # ... now, get the digest, it's needed to get the label. In theory,
167
+ # one could make decisions based on the price or some such to avoid
168
+ # surprises. This also has *no* error handling yet.
169
+ xml = parse_ship_confirm(confirm_response)
170
+ success = response_success?(xml)
171
+ message = response_message(xml)
172
+ raise message unless success
173
+ digest = response_digest(xml)
183
174
 
184
- # ...finally, build a map from the response that contains
185
- # the label data and tracking information.
186
- parse_ship_accept(accept_response)
175
+ # STEP 2: Accept. Use shipment digest in first response to get the actual label.
176
+ accept_request = build_accept_request(digest, options)
177
+ logger.debug(accept_request) if logger
187
178
 
188
- rescue RuntimeError => e
189
- raise "Could not obtain shipping label. #{e.message}."
179
+ accept_response = commit(:ship_accept, save_request(access_request + accept_request), (options[:test] || false))
180
+ logger.debug(accept_response) if logger
190
181
 
191
- end
182
+ # ...finally, build a map from the response that contains
183
+ # the label data and tracking information.
184
+ parse_ship_accept(accept_response)
192
185
  end
193
186
 
194
187
  def get_delivery_date_estimates(origin, destination, packages, pickup_date=Date.current, options = {})
@@ -855,7 +848,14 @@ module ActiveShipping
855
848
  success = response_success?(xml)
856
849
  message = response_message(xml)
857
850
 
858
- LabelResponse.new(success, message, Hash.from_xml(response).values.first)
851
+ response_info = Hash.from_xml(response).values.first
852
+ packages = response_info["ShipmentResults"]["PackageResults"]
853
+ packages = [packages] if Hash === packages
854
+ labels = packages.map do |package|
855
+ Label.new(package["TrackingNumber"], Base64.decode64(package["LabelImage"]["GraphicImage"]))
856
+ end
857
+
858
+ LabelResponse.new(success, message, response_info, {labels: labels})
859
859
  end
860
860
 
861
861
  def commit(action, request, test = false)
@@ -169,7 +169,14 @@ module ActiveShipping
169
169
  options = @options.update(options)
170
170
  tracking_request = build_tracking_request(tracking_number, options)
171
171
  response = commit(:track, tracking_request, options[:test] || false)
172
- parse_tracking_response(response, options)
172
+ parse_tracking_response(response).first
173
+ end
174
+
175
+ def batch_find_tracking_info(tracking_infos, options = {})
176
+ options = @options.update(options)
177
+ tracking_request = build_tracking_batch_request(tracking_infos, options)
178
+ response = commit(:track, tracking_request, options[:test] || false)
179
+ parse_tracking_response(response, fault_tolerant: true)
173
180
  end
174
181
 
175
182
  def self.size_code_for(package)
@@ -230,7 +237,7 @@ module ActiveShipping
230
237
  if prefix = ONLY_PREFIX_EVENTS.find { |p| description.starts_with?(p) }
231
238
  description = prefix
232
239
  end
233
-
240
+
234
241
  timestamp = "#{node.at('EventDate').text}, #{node.at('EventTime').text}"
235
242
  event_code = node.at('EventCode').text
236
243
  city = node.at('EventCity').try(:text)
@@ -250,16 +257,26 @@ module ActiveShipping
250
257
  protected
251
258
 
252
259
  def build_tracking_request(tracking_number, options = {})
260
+ build_tracking_batch_request([{
261
+ number: tracking_number,
262
+ destination_zip: options[:destination_zip],
263
+ mailing_date: options[:mailing_date]
264
+ }], options)
265
+ end
266
+
267
+ def build_tracking_batch_request(tracking_infos, options)
253
268
  xml_builder = Nokogiri::XML::Builder.new do |xml|
254
- xml.TrackFieldRequest('USERID' => @options[:login]) do
269
+ xml.TrackFieldRequest('USERID' => options[:login]) do
255
270
  xml.Revision { xml.text('1') }
256
- xml.ClientIp { xml.text(@options[:client_ip] || '127.0.0.1') }
257
- xml.SourceId { xml.text(@options[:source_id] || 'active_shipping') }
258
- xml.TrackID('ID' => tracking_number) do
259
- xml.DestinationZipCode { xml.text(strip_zip(@options[:destination_zip]))} if @options[:destination_zip]
260
- if @options[:mailing_date]
261
- formatted_date = @options[:mailing_date].strftime('%Y-%m-%d')
262
- xml.MailingDate { xml.text(formatted_date)}
271
+ xml.ClientIp { xml.text(options[:client_ip] || '127.0.0.1') }
272
+ xml.SourceId { xml.text(options[:source_id] || 'active_shipping') }
273
+ tracking_infos.each do |info|
274
+ xml.TrackID('ID' => info[:number]) do
275
+ xml.DestinationZipCode { xml.text(strip_zip(info[:destination_zip]))} if info[:destination_zip]
276
+ if info[:mailing_date]
277
+ formatted_date = info[:mailing_date].strftime('%Y-%m-%d')
278
+ xml.MailingDate { xml.text(formatted_date)}
279
+ end
263
280
  end
264
281
  end
265
282
  end
@@ -536,21 +553,44 @@ module ActiveShipping
536
553
  package.inches(:length) + package.inches(:width) + package.inches(:height)))
537
554
  end
538
555
 
539
- def parse_tracking_response(response, options)
540
- actual_delivery_date, status = nil
556
+ def parse_tracking_response(response, options = {})
541
557
  xml = Nokogiri.XML(response)
542
558
 
543
- success = response_success?(xml)
544
- message = response_message(xml)
559
+ if has_error?(xml)
560
+ message = error_description_node(xml).text
561
+ # actually raises instead of returning by nature of TrackingResponse#initialize
562
+ return TrackingResponse.new(false, message, Hash.from_xml(response),
563
+ carrier: @@name, xml: response, request: last_request)
564
+ end
565
+
566
+ # Responses are always returned in the order originally given.
567
+ if options[:fault_tolerant]
568
+ xml.root.xpath('TrackInfo').map do |info|
569
+ # Don't let one failure wreck the whole batch
570
+ begin
571
+ parse_tracking_info(response, info)
572
+ rescue ResponseError => e
573
+ e.response
574
+ end
575
+ end
576
+ else
577
+ xml.root.xpath('TrackInfo').map { |info| parse_tracking_info(response, info) }
578
+ end
579
+ end
580
+
581
+
582
+ def parse_tracking_info(response, node)
583
+ success = !has_error?(node)
584
+ message = response_message(node)
545
585
 
546
586
  if success
547
587
  destination = nil
548
588
  shipment_events = []
549
- tracking_details = xml.root.xpath('TrackInfo/TrackDetail')
550
- tracking_details << xml.root.at('TrackInfo/TrackSummary')
589
+ tracking_details = node.xpath('TrackDetail')
590
+ tracking_details << node.at('TrackSummary')
551
591
 
552
- tracking_number = xml.root.at('TrackInfo').attributes['ID'].value
553
- prediction_node = xml.root.at('PredictedDeliveryDate') || xml.root.at('ExpectedDeliveryDate')
592
+ tracking_number = node.attributes['ID'].value
593
+ prediction_node = node.at('PredictedDeliveryDate') || node.at('ExpectedDeliveryDate')
554
594
  scheduled_delivery = prediction_node ? Time.parse(prediction_node.text) : nil
555
595
 
556
596
  tracking_details.each do |event|
@@ -582,30 +622,16 @@ module ActiveShipping
582
622
  )
583
623
  end
584
624
 
585
- def track_summary_node(document)
586
- document.root.xpath('TrackInfo/StatusSummary')
587
- end
588
-
589
- def error_description_node(document)
590
- document.xpath('//Error/Description')
591
- end
592
-
593
- def response_status_node(document)
594
- summary = track_summary_node(document)
595
- return summary unless summary.empty?
596
- error_description_node(document)
597
- end
598
-
599
- def has_error?(document)
600
- !document.at('Error').nil?
625
+ def error_description_node(node)
626
+ node.xpath('//Error/Description')
601
627
  end
602
628
 
603
- def tracking_info_error?(document)
604
- !document.root.at('TrackInfo/Error').nil?
629
+ def response_status_node(node)
630
+ node.at('StatusSummary') || error_description_node(node)
605
631
  end
606
632
 
607
- def response_success?(document)
608
- !(has_error?(document) || tracking_info_error?(document))
633
+ def has_error?(node)
634
+ node.xpath('Error').length > 0
609
635
  end
610
636
 
611
637
  def response_message(document)
@@ -0,0 +1,10 @@
1
+ module ActiveShipping
2
+ class Label
3
+ attr_reader :tracking_number, :img_data
4
+
5
+ def initialize(tracking_number, img_data)
6
+ @tracking_number = tracking_number
7
+ @img_data = img_data
8
+ end
9
+ end
10
+ end
@@ -1,23 +1,10 @@
1
1
  module ActiveShipping
2
-
3
- # This is UPS specific for now; the hash is not at all generic
4
- # or common between carriers.
5
2
  class LabelResponse < Response
6
- attr :params # maybe?
3
+ attr_reader :labels
7
4
 
8
5
  def initialize(success, message, params = {}, options = {})
9
- @params = params
6
+ @labels = options[:labels]
10
7
  super
11
8
  end
12
-
13
- def labels
14
- return @labels if @labels
15
- packages = params["ShipmentResults"]["PackageResults"]
16
- packages = [packages] if Hash === packages
17
- @labels = packages.map do |package|
18
- { :tracking_number => package["TrackingNumber"],
19
- :image => package["LabelImage"] }
20
- end
21
- end
22
9
  end
23
10
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveShipping
2
- VERSION = "1.1.3"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <v13:ProcessShipmentReply xmlns:v13="http://fedex.com/ws/ship/v13"><v13:HighestSeverity>SUCCESS</v13:HighestSeverity><v13:Notifications><v13:Severity>SUCCESS</v13:Severity><v13:Source>ship</v13:Source><v13:Code>0000</v13:Code><v13:Message>Success</v13:Message><v13:LocalizedMessage>Success</v13:LocalizedMessage></v13:Notifications><v13:TransactionDetail> <v13:CustomerTransactionId>ActiveShipping</v13:CustomerTransactionId> </v13:TransactionDetail><v13:Version> <v13:ServiceId>ship</v13:ServiceId> <v13:Major>13</v13:Major> <v13:Intermediate>0</v13:Intermediate> <v13:Minor>0</v13:Minor> </v13:Version><v13:JobId>2cbe617200571fq127edj036951</v13:JobId><v13:CompletedShipmentDetail><v13:UsDomestic>true</v13:UsDomestic><v13:CarrierCode>FDXG</v13:CarrierCode><v13:PackagingDescription>YOUR_PACKAGING</v13:PackagingDescription><v13:OperationalDetail><v13:OriginLocationNumber>915</v13:OriginLocationNumber><v13:DestinationLocationNumber>100</v13:DestinationLocationNumber><v13:TransitTime>FOUR_DAYS</v13:TransitTime><v13:IneligibleForMoneyBackGuarantee>false</v13:IneligibleForMoneyBackGuarantee><v13:DeliveryEligibilities>SATURDAY_DELIVERY</v13:DeliveryEligibilities><v13:ServiceCode>92</v13:ServiceCode></v13:OperationalDetail><v13:ShipmentRating><v13:ActualRateType>PAYOR_ACCOUNT_PACKAGE</v13:ActualRateType><v13:ShipmentRateDetails><v13:RateType>PAYOR_ACCOUNT_PACKAGE</v13:RateType><v13:RateZone>8</v13:RateZone><v13:RatedWeightMethod>ACTUAL</v13:RatedWeightMethod><v13:DimDivisor>0</v13:DimDivisor><v13:FuelSurchargePercent>4.5</v13:FuelSurchargePercent><v13:TotalBillingWeight><v13:Units>LB</v13:Units><v13:Value>8.0</v13:Value></v13:TotalBillingWeight><v13:TotalBaseCharge><v13:Currency>USD</v13:Currency><v13:Amount>12.79</v13:Amount></v13:TotalBaseCharge><v13:TotalFreightDiscounts><v13:Currency>USD</v13:Currency><v13:Amount>0.0</v13:Amount></v13:TotalFreightDiscounts><v13:TotalNetFreight><v13:Currency>USD</v13:Currency><v13:Amount>12.79</v13:Amount></v13:TotalNetFreight><v13:TotalSurcharges><v13:Currency>USD</v13:Currency><v13:Amount>0.58</v13:Amount></v13:TotalSurcharges><v13:TotalNetFedExCharge><v13:Currency>USD</v13:Currency><v13:Amount>13.37</v13:Amount></v13:TotalNetFedExCharge><v13:TotalTaxes><v13:Currency>USD</v13:Currency><v13:Amount>0.0</v13:Amount></v13:TotalTaxes><v13:TotalNetCharge><v13:Currency>USD</v13:Currency><v13:Amount>13.37</v13:Amount></v13:TotalNetCharge><v13:TotalRebates><v13:Currency>USD</v13:Currency><v13:Amount>0.0</v13:Amount></v13:TotalRebates><v13:Surcharges><v13:SurchargeType>FUEL</v13:SurchargeType><v13:Level>PACKAGE</v13:Level><v13:Description>FedEx Ground Fuel</v13:Description><v13:Amount><v13:Currency>USD</v13:Currency><v13:Amount>0.58</v13:Amount></v13:Amount></v13:Surcharges></v13:ShipmentRateDetails></v13:ShipmentRating><v13:CompletedPackageDetails><v13:SequenceNumber>1</v13:SequenceNumber><v13:TrackingIds><v13:TrackingIdType>FEDEX</v13:TrackingIdType><v13:TrackingNumber>794637052920</v13:TrackingNumber></v13:TrackingIds><v13:GroupNumber>0</v13:GroupNumber><v13:PackageRating><v13:ActualRateType>PAYOR_ACCOUNT_PACKAGE</v13:ActualRateType><v13:PackageRateDetails><v13:RateType>PAYOR_ACCOUNT_PACKAGE</v13:RateType><v13:RatedWeightMethod>ACTUAL</v13:RatedWeightMethod><v13:BillingWeight><v13:Units>LB</v13:Units><v13:Value>8.0</v13:Value></v13:BillingWeight><v13:BaseCharge><v13:Currency>USD</v13:Currency><v13:Amount>12.79</v13:Amount></v13:BaseCharge><v13:TotalFreightDiscounts><v13:Currency>USD</v13:Currency><v13:Amount>0.0</v13:Amount></v13:TotalFreightDiscounts><v13:NetFreight><v13:Currency>USD</v13:Currency><v13:Amount>12.79</v13:Amount></v13:NetFreight><v13:TotalSurcharges><v13:Currency>USD</v13:Currency><v13:Amount>0.58</v13:Amount></v13:TotalSurcharges><v13:NetFedExCharge><v13:Currency>USD</v13:Currency><v13:Amount>13.37</v13:Amount></v13:NetFedExCharge><v13:TotalTaxes><v13:Currency>USD</v13:Currency><v13:Amount>0.0</v13:Amount></v13:TotalTaxes><v13:NetCharge><v13:Currency>USD</v13:Currency><v13:Amount>13.37</v13:Amount></v13:NetCharge><v13:TotalRebates><v13:Currency>USD</v13:Currency><v13:Amount>0.0</v13:Amount></v13:TotalRebates><v13:Surcharges><v13:SurchargeType>FUEL</v13:SurchargeType><v13:Level>PACKAGE</v13:Level><v13:Description>FedEx Ground Fuel</v13:Description><v13:Amount><v13:Currency>USD</v13:Currency><v13:Amount>0.58</v13:Amount></v13:Amount></v13:Surcharges></v13:PackageRateDetails></v13:PackageRating><v13:OperationalDetail><v13:OperationalInstructions><v13:Number>2</v13:Number><v13:Content>TRK#</v13:Content></v13:OperationalInstructions><v13:OperationalInstructions><v13:Number>7</v13:Number><v13:Content>9622001900008000296000794637052920</v13:Content></v13:OperationalInstructions><v13:OperationalInstructions><v13:Number>8</v13:Number><v13:Content>537J1/25E2/EE4B</v13:Content></v13:OperationalInstructions><v13:OperationalInstructions><v13:Number>10</v13:Number><v13:Content>7946 3705 2920</v13:Content></v13:OperationalInstructions><v13:OperationalInstructions><v13:Number>15</v13:Number><v13:Content>10017</v13:Content></v13:OperationalInstructions><v13:OperationalInstructions><v13:Number>18</v13:Number><v13:Content>9622 0019 0 (000 800 0296) 0 00 7946 3705 2920</v13:Content></v13:OperationalInstructions><v13:Barcodes><v13:BinaryBarcodes><v13:Type>COMMON_2D</v13:Type><v13:Value>Wyk+HjAxHTAyMTAwMTcdODQwHTAxOR03OTQ2MzcwNTI5MjAdRkRFRx0wMDA4MDAwMjk2HTEyMB0dMS8xHTcuNTBMQh1OHTc4MCAzcmQgQXZlbnVlHU5ldyBZb3JrHU5ZHUJvYiBCb2JzZW4eMDYdMTBaR0QwMDcdMTJaMTIzMTIzMTIzNB0xNFpTdWl0ZSAgMjYwMR0yMFocHTMxWjk2MjIwMDE5MDAwMDgwMDAyOTYwMDA3OTQ2MzcwNTI5MjAdMzRaMDEdHgQ=</v13:Value></v13:BinaryBarcodes><v13:StringBarcodes><v13:Type>FEDEX_1D</v13:Type><v13:Value>9622001900008000296000794637052920</v13:Value></v13:StringBarcodes></v13:Barcodes><v13:GroundServiceCode>019</v13:GroundServiceCode></v13:OperationalDetail><v13:Label><v13:Type>OUTBOUND_LABEL</v13:Type><v13:ShippingDocumentDisposition>RETURNED</v13:ShippingDocumentDisposition><v13:ImageType>PNG</v13:ImageType><v13:Resolution>200</v13:Resolution><v13:CopiesToPrint>1</v13:CopiesToPrint><v13:Parts><v13:DocumentPartSequenceNumber>1</v13:DocumentPartSequenceNumber><v13:Image>iVBORw0KGgoAAAANSUhEUgAABXgAAAO2AQAAAAB6QsJkAAAgJUlEQVR42u3dz28jV54Y8EfUxOUAhJ4He1FjuarZdACdgrCHFwpT4cvCi/XN8ydYjgzrlB31CJNQWFpFRYY1h0ZzB3OIBTTE3HIMgj1kgwV2Si5DzACMHvYULLB2F4cdEcjYrUdXJv24/VTfvFdF/XK32pJapKp2v2WTYlOk9GHpW9/3/dZPAjc4MZj4RNCL3ol5w1HsgZ+whWKtZt1W0smw118wXgceA4R1p9WsWtn2thb0PW9BG2BvoT3fvjOUoyzHQ3vbeLnx8gqfb2+PxM17m+PFJBLAaCCkwwOl2Gt4B8YLqfdJNDlv6NXsDR13fOOaUcfao5kPPD7YtCmLU+9X37Cb9rbHD/t7tdryp3sL0NqWw9Z3vzUkTnJPTkQskG8sMj4ovGEzubq/2t6OlHAm5Q2460YuXwDqiohfyxvW95YcfrC2swwy0t5ZS13mk1/P+0tYMN5K/GP3CVzey06HCSYXDpbbfDB8FEGs52+z6qjL/KQrer9ZeVIeAPxF/CM3Uns/rHH3K2hdywt3Eu/GI0svb9q7NpiEVywu/mBZL2/S095wxeGuip3rebcT7z9NvN1Ce9SfgLe5erD00DNecKNReL9lvOzy3jPx2068e2uPYOyNJuLl8zvGG+v5OzzwuPaeBPWrvAxe7t1f0/HLK91RezRU0c3HQzT2AnOjPmeJl1/ee/qnYHzhYInxh/d3lvR4/Eh7R2rYnqh38Fpef+FgkflzhU9sndruKOO9+XrnnJdfyVsA/5xXjA4+8Hx2p0OZUAvaq645sl/Ky7W3resU7b3ceOESr0kYIXQcwuZ+MPF6vR0dLCfePVZb/tXevTbfVpcbj6vE0aMbmR/UvKl6w+Wd5XG9Qy5f74SkTEpmNK78pgFT9YpB4jX1JNF9zCXrSV05uEXjXRBymt7rTtrbeNPW1ag7GuWh39TedUJ9RlwvyoWXNQlx9PhWY4M8eXXBM85+bKiXBkerBRWMC/qydzXTZbNmm9IiUMA24uVoyt52+8R7pL1HY6/9Uq879uqg5xvxurcRL03b+6vj+fsERjs/Znrpe6wLnqj/sne1k0wZ1zwV6cYLXGhP0QvCxC87iV/t3U+8beN9KSP9W6jES93E+/60vD5InR+gpvODc+J91zPewYXe5G8xciFe1o0XbAOLP7iaV/lM1pavnuq1t/GmRZL8G9PUq1befbf+5z/Wj7yLvN0/D/r8uduK34m5C7+Au1f3OmLmWt6QjMc3dfQfE+9+vfzuuytvaW/IeDR4ube54XNwbaCgvQEU4g9Xr+blrT4fXM+b1g9P4qP/lnrvb+//yeodAK/vXJDP2t2HC7pdcpd0C6O9A5i9upcPBtfyClIz3vn99Y/+V+pd3f78YHkbYk/H6cvzWbu7s6YXORYde1W8XJmSVxKP/ICY+rdGx97RkyeDsfeCfKa9QeIdx8O1vPEHo93hk8rgat6YgL+ovU7TSfNDs3LWG13k7RlvvGy828b7y6t6hfY2w8U7V+1lAxieW993Ge+DnTX1mt6WnPtw1d1fuvOavTdrV7YTr4LBhd5HO2b1hBvp8YKb8cKNl4Or5rN45sNVyQfbr+29t/2//290R3v77KL4LaXeZeM14/GdeLl/1fEC9j5c9XuDR9dZV3BmBTpjn1bvD8Xsj5USzC9fUO/srO3yc/XOVb16rh58ePdjcT3vU3P3eOx91Lg/lI+0V3p++YJ6cmetz3XndVxPBletJx9xGBwsz+s4upaXe6fe8Z98svX6sXd/eec6DcJj+JZ34EzW+3MO0f7yfPW63v4ffDXnnPGGbLJeHf263mmRcHA979Ovv/7i7PwV3mS9OvpVbbn1YDjYuc5v+rt/+7uvDx/fSr8ZX8f7WHt/mzPvLc1feZ2fcPinX/3Pr8de6bWaHkRKsRaRitENIUe7g5FrtptOwHutbb3iJ3P/4uk4P4Ss1WTaKx2biPqcbbzN1aiqH4XezXtfd2Kd9rzu1/ty+HDp6GDh0VJbfLM2tzZYeLi8fZBFL2/N6349ENF+NDqsdKP2Nz9dYwsD/cjlLIPeFp/X/fovn0RdGPGoG7GDn64yl2fW24b5dhf+4qvhg1b0P6Lu0l3jVS2WWW+8qr16eSvZkb+6bxe0dyt2Mu3liXfp2V5l357d/2nFN2Vmxr2jR9GAV/aX1f58RXiMlZay7dWL2sAsZYrPz8c1p1azMurtFl7wgstr3u9lNJ9pb+ustz0/r/OZCxmNX/5opMeLxMuTrGu8C4MF9Syb3k5ppMfjSA0fLvODtUdLd9r/8j/vrA1+pDI6HocLqumafDZn89DUO+1WS9c75RmagXonJKdT2rozsaaajaSepFzou4C1Wr3BqEydidSTr+2dcr2OXvSiF73oRe8kvCfrsdA7Ka8gVp68sY5de8peKVwajSB09NwCUHEhB96h9urfGRovuYLX5IjClL0iUqC9g06yhV7FDS/7XqG9g7F3/Qpe3yRfZ7reJ4MRfLN26vXY5b3N2/A+WXMSb9AuBL/+PPZ+6F3amwxu9nS93bBKowUY8I39P9rYbcZe+dLe+Da8j/qzHe0N6faTwTafjb3KpeNBJV5ryl5oD6JtCD33ycDl7lW8ZnQT0/a6qXfQJsNceAnT3l/DoHV1r28OySHk2EumMJ16OUu961fxsul71WB7EPX0+DaO3/XVK3g9M2ZMNx5Uv8qjoc6/n5r88GncyLrX1GfD9mCwsb+8wZsxueQhVdrbLCQlcG26XrnuRCMdw0F7PuCfx4UreK1b8L5Gvd60c+VN41bacW68Tg69+ek3X/A2Pdga8Rx5Gdh58rYdYKNWZr1yfBzkqbcNLGpP2NuD2CPL+fH6N+mdoU17wt4Hnm6JBzfjrb0xee/OzXmbteXJx0N8c94NFbEBm7BXffAb7d3qPXl9707DYhOPBxH62mv54bW8UvcoJ/XZjjsF73DwwCODSnfw+t62C1OIh8GOR5JzpLy+tzSF+I2Xdzx2Q96dJbbv5clr58vL6eS9OzfnnUq9/mC5q/PZjeSHqXj9QZp/8+LtDdLx7Tpe5xa8r1OvO8kq61q++nn0onfstc97Talu2vlH6L0Jb7p+8lteM32aM+/CJL1NL11W+MGH+lGgPLnubI2kdxlv4Ta87Ky3oDzhUmsk2DW828VRwJ0tSbwJetMzoGjv4MCD2B2xyOX3htFlvMn2ljPr1z/93mhjn1oT9rbPehtrbLB9sNYbtC/lZWafglPv3up2d7ky2Xg451XrqffXl/Q6ZivhaTwcrLrdg7V4daLendIo+PU81/FbBwUj7R2M/vZSXrP9OPy2Ny5MOB7c4mhjt9L23/hwhSmI2sY7uqb37uS9zcb+6jZPvMvH3uhy3nT/hzPewbz2epONh+aC/itqr46HZTDe/p1B/zW8E4/f2TdWCV9g2ruSeMWdQedy3vEW+fiMV+eHe2owUe+nb9wde+ueihep3B7w63pbJv9O2LsN8yz1ekytL1LYHrQv6fXP72+kvWZ8m7RXRx13PRO/LG4M+OC/D9glvck+y950txe2S535T0+81QHvu9wbXWo8vhVv06WtjdS7sgoFPhCNmcLl6p1kBzRryttjm+uOjjrX1Gf1CDb5QMZzhcvVk7eyP206qeT++dXqdf2229q/Om/e9ERQz67qTabb8F79vPn/AI5naHpbMPHtF9f3vnB8S7NWyJW3XarDQZ68jxRMfPtFm9v58u4Xbsy7Ha+P95GZoLe7ljOvujHvndibfDzcnLe5EHuPYOLem8u/a9OIh33nxuJhNA0vt27MqxRMPh74bs688sa8cX2C6yZPvOqGfpj2Fqbg7d6cF7Ym7209uDHvVOp1PreWK68/V8iVV+gmJk/em5vQi94ceT8KPxoehfeHejo6Xv9g1kasZtV7ePT06PCpmY69eg4fZta7djh6vHb4+D09rY3HY1378ngpq97H2vv4sfee997a4XteejwDcDXIqPdnf/M7fXu8rv/72djLs+z9aP8oPtrXs3jt8UeHfwrp/ql3N8NCVr3w0dERvGfm77F33y5k2vtce038nniXZzMbD8+099mJ92eJ15wodaIHlL2WV4fBUZLOTr2Wy7MaD88++vLo8dGX6XjxNPteedQ78j/qDZPxePhnifcBZDceXtZvPory5Z1busMneoDWzXqbNbsQ5Mnr0bv9yXpvcmKTr9lz573pfgi92B+jF70v9Yaj2IOulRuvv2C8hdx4W2ZjQFfmxtveNl41WW8o5V4hJjUHYBSFagFGYt2Btt2LL1p+mu6S8sD2i41Fh5y5bNrYe63LAl3e61flHolJVf/eqO/XSzAMXQpzln+xt6S9ccEvuouUlK0z3tHMB96kvZ9ob9Xri8B4H957AMPObAdK9x80LvK2itqr7nWUu9Rxhr1TbyDfWGST9rZm5Z7r9Yd9gH6gvV5Pe+NXeh8Yb72jSsY7PJPP6ntLzpS8ff17+5sxe+DtgtNRzlBdxts/55ULB8vtSXu35NjbgcHdmM15vwCnNXKGUr7ayzqq+C0v3JmCd/fU21lStTn2X2OHPnd64u8v9CbLGx17z8QvbE/B69eJWd5MPHT6an2ODWNGwemLb16Zz2LLL/7xUv98fki8arJeUSV7BMZe6e2woWIcnEB988p8BhtjL33BO9nxItbLW+HYK+AhG0qvA86W+nevzGcgOuoDEw+n519ifOFgiU16PDb5gR17Q+MV3p7j2OrD78wPi8bbOVvvHOj8W5iit5d4QXupWqTf5V0x3tOrkzIxOtDjmzUVr/m9nX4w1N6e7MCc8+Ay3s45L1z5IsrX8O6lXjMed/qdoV7e+pLDHHsQvzJ+43JH1b81vk3Fa+od7ZW2Hi/6zeqcN/RLFEqFV9U7i8WCqXfqut65N22vqCfe2OnoIG66rvbOOdr7qnpSe2EzrSfvySl7z0wmFF8j20+93zQHCbxGtkfvd3hHcM2LcOL6EvSiF73o/Yfv9XU/EDv6oQ3w3UeZZsBLxl4rL14v9RbQOwlvIFnipWFOvGrsFeidkHcjnvW0N7bl+2CBLXYz7iXxW472Kkv+6waJC6KZ7eVtnawTCrQnC5K4pFEQ1zwfzbTyWWO29gbV+UwsytlqtboiVjLtLSi3RrXXFkK61UZdinq2569cn3O29Hjck/X1WU/IlWumielcb8Yn9dRbSLxO1r2BrCZeGhpvyRLkut5p5bOxVyReki+v01wBkSevKOfLq8pHGfdKN8m/NEzzb7We7fh9YXyriox7Tf3gaK+pHxr6v6x7IanPiGXqs3WiChn3FmDT1L/Ejqmufzd1/ZtpL67fQS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvei9WW/TEl4IYM6xEkBMc+D1Hd8Dc84S7bVz4G3SJgNztZgAVD68xIlz44Ut255x4pKTFy+lWzOOqiVe+XYevJ84ND/e2DFeiEt/KIxXxI4tdmWDbWbbq9yC/zbIPxKNuYJoai/JqFcxHb4UQrfQtED0ZMMtCFJv1LLr9eeMt2Ftpl5zAql6w616WfWGtcTrbG2mXinq2tvIqFd66fx1GQ2gJ1R1Ra7IeqPaYFn1NmeS5S0vXkiWt7hkvCHRXpJ4SUa9Asb513h9sv6WyJsXhPF6kGWvquXIm9Q7iTeQ3vfFUeJdz6i3B2k9WWI6/24pVpB1Hb9112VZ9ab1usv0+JZ4q3r+Vt2Sl12v6YcazE+8m4oI2SA1kllv2m/qOuJtsJWzaU4g1fheZuudHqT9vGK6/qWxk5xAar2Y2foX1++gF73oRS960Yveq3qHsUusJlF2SBzFTPFDYmJtme+GFgjHNCCS6m9CE6gQogBOPwNeURh7WUjWj70EfFt/AhpaYbIK3niJlwlvSFJvqL21E69n1gF61E8+DHESL7td7y9++M8v9jJS0HOcNskZ78Lv/TPvFr2EWAK2YFd4W5B4e2rOgcTbk469a7yboX5C1qjxqrfIBatSpurVnXzi/czpxSdeNUdDvchR23wY6SXe+Ja9oXIu9OpHwnipPOOdo7uQAS/Ai16YMV567IUseD+vt5L4PfbSs94ik55ozegFUXtZ6i298/Pb9D681xHQLIQs9W7RnqoR+6x3K/UqW8e69s72S7eZH54ePU689Nhr8tlLvYXEq+YOv7jl+sF4pXXqjc97O6kXiGe8F68XnqpXz73ES7fMeHzOG4y9TZYZr9Gcemvnl7f+2CvS8c3Lhnc8vhH7eHxrHnt7Y29M0/osG950vNCl5HE+O/EOx15IvJANb/BSbzJeiHPjW5a96Xgss+jdHXud4Fz9YOodOa53WJa8Ybq8KdZ7oZ5UaT2pi7XMeE1BbkikyHpJvd4snNbrStfryTedV3uncz2fsVe81Jv0Q7Huh/Q340x5ZTK+kTdZmPSbY2/Sb8a63zQBkvabGYmH2E69nkj6+dSb9vOx7ueN14eMeHH9DnrRi1703oLXXN/UTLY0X6nZobK3qRzox1TfO8TOmHd37N1KrupkvLRnPkOgbEnj7Ho3xal3V8/rQFrCzqDXH3uDpDIwXrvX0/N6UxRCS2XXuxueenXxDpbo9QLpAM2ad7yjZK937LV6ugpTiTd0zKWGM+kNd894F0Hq+6DnZ9Drss3eULfBQWzLt6kNcaEn3wf5tvF+5iiWMe9nLiOJ11xfz5qhEH+/p5sNGRjvXga9pRoRYQ+kLQva60D8Vk9Z8Dzxfulc4Vp70/K61ZXEKxalXWRjLyTeOHveoOQ2jFfp5kyyNz2IZ3vmMK3MeovVRjnx9mTde1Pnh9n3z3hF5uqdB9VG9UnflDnaS/T8Lb1v0lpmvR9XSfWP3z7r/cMz3mHmvJ+f8+rlbe6st5c5r1/15In3e8ZbyLQ31N4/Ofaa/Otk3buuZuwXvabeyaJXuK6beHX+9cx47Gyc8QYZ9JYaqXdRenTGOfHqej2LXlkjqVfXD6l3N/Gm/VAmvbE5wMXR9ZlHPznxpv1mBr1sM/XG9JzX9PMZ9H57auds/U7evCxf3jhnXvBy5oWMewPFmvny1gjLldclDnon6K3WaL68Hnon681XPquyfHldO2feQs7GY+Lly9vM13jMRL7GNxbnK/8ywPENvcfeXVllefKGojGXKy9p5KpeFyTOlVcWIFf9ZmyDn6d65+Ym9KIXvehFL3rRi170ohe96EUvetGL3sl7yVt2q1XRD2NCd5MzNJhjrSXzKYSwC5tgZ81rtZyxl4BcL4C0/IJwtLfJSExi63W9NzkxGA57p15PNLS54BdCqr3EIQ3SKGTNO3xYu5t4ZwgzXqG9vt3U3jnikjrJVjxo74P1ZfM4niuxsL4CIoQg2NJxPDNTqlarK5nz/lwmf3Plusyv172egK3E6xTdaiN73l7z2Ftz/LpMvBpsvLXZRvkKR59MxSusZhoPslFzmmZumtOlBx9rZrHmqOx56Rnvz1e2zDmHtLfpZNQ7bH1STeJBqJrz5op+WIidxBtrr8yet/Pw1Du7on+8ZbzEXJ9Ae1cy5+0/dN9JvaW56omXUlClkiMy6G2VuHnci0mpurLiQUGxrcCxQdaIE4rMeYfHXiClupmdPeM1Z4NkGfU+SLx9ILOJVygv8QqPOLvZ88qxN4DPZslbxitTbwifOZtZ855OAcTnvbtgLnSVXa+qVY23l3jpFmyqWo1m2etWdQQn3mCL2trr1qjMWL1zOm2pEpNJPSlA12cUbFVynQx7Y+LJuqnXBaT1+ozuL6ok096G6d8E6H7IMQ1Hnbz2no+T88K437TNCW59Zho6SWpWdr2bup+3dT8v9NAmPO3dlITZWfXi+h30ohe96EUvetGL3n80XkGKXVVhFNwAKFfAKl1g+kuRsQowzkW5wjm4XYuAaxEelH2IIsWKwg+gol/oE96N0Ite9KIXvejNopdUilaRVKygTAPh03LR/E+4CwEXFVEhqtglAvQLSJdUAlHkTJl/Ev050Yte9KIXvejNshfKFuWccRL5RVoE3+XFiDCfuxrFRbHMfUaDrsUF7YpumSiNhgrVr0AvetH78onCLihzzd300kM58BJP2LnxxjQmLLQgzIlXUUUcvwA+eifilVSW6IYPTYDdc17L9Rkpu74fKT/SXWZXD7hAVRQEfrkiBHD9Ldf0mBDoMVrwIlWmAS1GYqL9ZkhljW6GMcmJ10+9Sjs38+BtmlPmbglJIL3iX9a9/4EKz7ZF/QeQHrqRde8nY+9iTrw7qXcthPQcV1n3/nXq/bO8eOPU64Wm6EHv9LwV3U2WSZcFwKwy5UXXKpeZKywakKIv9D+KRcGP+02q/X7X9RUUfWDT8SZXI0MvenPqTa72ht4b9yb1GeTIa+pf9E7Om/RvRnzOW/QpZ4QzzasIwkVXUxmLOOlSagmrW1SCiErFEsT3LVdEReK6JKBF3+KT9ab9MXonvX4nL950/VmOvMn6ybx4L1z/i1705tlb8ZXFoijiKmLCF9AFHkUV4rOAMSp0x1k07SULRORz6pf1S1zo6vFYBYBe9KIXvehFb2a9yVAMvOsDL4quJVRRuZDuIONC8kSXBKf9JlWqazpSwivoRS960Yte9GbZa/EigUoXKCl3faqCwGcucYtdX5GyVS5qNbfKxFddIXQDqp/g3Gek4pfRi170ohe96M2sl/uCVfQt6ComKImSw/NIhRS570dRoCqKdZlVpL7girNIlVnRKgpmsS5FL3rRi170oje7XhJBhTDKSVGVTXsJ+hka+K7ftfTHKNKub5WBqGIxKOsnqOXrJ8wOoIyhF73oRS960ZtZL+G+poqIB0xjfasrGA+oOTrdd1XZp1HXZ5wXKSVB1zVdZqCEZQX6bS560Yte9KIXvZn1FqFLdRupun6ZRopRF7hl6bbTCnzFysWupU52kCkXiShyooAGvAg8QC960Yte9KI3u17FmDlez+ztEpFioEx7+cITjBEVmK2FXFlFwhklnHP0ohe96EUvejPrpRVCK0xQ7tOgYinl6q8RtSLfrxAeUS5cyyVBORDU7PdZMbuFlongLvcZetGLXvSiN3texQTYkpnbMHbMRVnrbNecdMCcqMbctuI5DzZBeJvQpFvQEaAc4tiC6rd4u+lJz6boldSvWcKJ9W2obGiCEk5TPyANO2RK3wrSZlAA/UB7C9DxmaRkzhLUvMW88iJv2WeMc8V5pEQEXZe5miWKVjc5Ok8xt8h4wALBOQtUmVWEG/EI3ODV5xMTtu8WQqr0rSctIJ4KKanrB3XLd6TlzxHtjQnoB9CcIbDlM2GT0vgt5pXT9Q47D0sr/S11v9/alX0oebLfKtX7cana7ziy35mpDgMnrkJnrgqgb/ZDZxjE7v0+qBX9ymp/yt7+oDUrOx3j3RhG2jtKvbXU23rQGPbbY29caoDdcvp9ZbxSdvZr0/f2Hf2LO+ZWGA7jGht29mr39IN7w1bLXIy6sQQt1fD0Ay92G+u2o9+i6jLxmldO3wsSEu+9l3jhrFelXujLuvwNA/2W+Ba8bx1775/xQun+sfffOCfeRqPxzltgXi1957a8xBOwZbzDxNtPvWTsLTYWaWfslcZLvNgW2ktB6LCfurcXENYDmnhF79S7MUy8Ok8s2ideVa13CIvpUL+8pd+6Je9P2yusGUePUolXFl6Yv2qOvB+ceGNyvzPjxLb2Qgd2Q1tM3SstxwniJEcM1RnvOH4VI0v9Y6/5FB3HgaCfeANhi+Hw4u2FUVDmhPquUGXKo27Zr4DVZZHv+qLCCCtSzSU8oD6woMyKRf0RgVcEVa+sH+46zpZiiTf+/on3OD9I7+Oz3o+XjLeferfkO72pe6HvOFR6iReGL3h1dXPWqxYLp14KneA2vHO6CrvIG77E2zdencu0d3Pq3n6nXXK0So/Hw6GpH/rnxuNdVY/6nfF4rMe1sNDWXj1861zmdFq27E/Z2+s8dHVCS7zijHdc7wSqOuycevXXh2wYJOWGYkGLTt0r9A8koW2Kw6HUZa8Xna8nZ3T9y8f1ZD9O6l+hE7KpJ0nokKo1fW+d+HZSr0tTrw/TKrypknq9qb20Na7XU68nace8XJJwjrj2tPMZE1J7T/uhoTzbD+kWKHZa436oD+N+qGNeLolcJzU67fzwQgH/4lOt6+1fMh3v6MWn2tfqj8sWZ5bLgoD7Zd/s5xkJYAQqWikqvGspXw/IJCBuUNTLRNm3uvrNFVom0ZW88sWnnCx71YtPMfTenDf2vvuZLHlvbH0UetGL3ultL6zo3q9StgLiK8HM9kPfB6p7Sx4Rv+Kb7YlFDswiImDUErrhVJYeri1eZuhFL3rRi170ZtF7YxN60YveW/CG96U19K246jSrasYO1+1+k1kb0tnwFe04TTtcYZbQz1etpl1KXmCJkxf4+p/JC6rOhoSHpESl9dBGL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS960Yte9KIXvehFL3rRi170ohe96EUvetGLXvSiF73oRS96L+ud3IRe9KIXvehFL3rRi170ovfmvOF9f/35/3OLtvOfys5fOpn3Hj6F9ee/++IoPvwvz+CvDrPvfbweR9HXR0fPtPdH2fc+ho/i6Le/1d6fPItzMH//Jn4e//bw//wryIl3f/Q8fn74t39nHy6W/yAHyxusP9PeX30Nj3/y7CgH8xe8fx8/f+x9DX99+Ox5PrxPn38JX8P64TP5+znID+/95OnzL55/HceHuci/Xz49fPr8r55/cXSUD29veDh8/pdF93v/JMzFePyPvT77/xZvcW63rB9fAAAAAElFTkSuQmCC</v13:Image></v13:Parts></v13:Label><v13:SignatureOption>SERVICE_DEFAULT</v13:SignatureOption></v13:CompletedPackageDetails></v13:CompletedShipmentDetail></v13:ProcessShipmentReply>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0"?>
2
+ <TrackFieldRequest USERID="login">
3
+ <Revision>1</Revision>
4
+ <ClientIp>127.0.0.1</ClientIp>
5
+ <SourceId>active_shipping</SourceId>
6
+ <TrackID ID="9102901000462189604217">
7
+ <DestinationZipCode>12345</DestinationZipCode>
8
+ <MailingDate>2010-01-30</MailingDate>
9
+ </TrackID>
10
+ <TrackID ID="5555555555555555555555"/>
11
+ <TrackID ID="9405510ee200828613653750"/>
12
+ </TrackFieldRequest>
@@ -0,0 +1,223 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <TrackResponse>
3
+ <TrackInfo ID="9102901000462189604217">
4
+ <Class>First-Class Package Service</Class>
5
+ <ClassOfMailCode>FC</ClassOfMailCode>
6
+ <DestinationCity>HANNA CITY</DestinationCity>
7
+ <DestinationState>IL</DestinationState>
8
+ <DestinationZip>61536</DestinationZip>
9
+ <EmailEnabled>true</EmailEnabled>
10
+ <ExpectedDeliveryDate>April 28, 2015</ExpectedDeliveryDate>
11
+ <KahalaIndicator>false</KahalaIndicator>
12
+ <MailTypeCode>DM</MailTypeCode>
13
+ <MPDATE>2015-04-24 02:08:04.000000</MPDATE>
14
+ <MPSUFFIX>495865558</MPSUFFIX>
15
+ <OriginCity>ARGYLE</OriginCity>
16
+ <OriginState>TX</OriginState>
17
+ <OriginZip>76226</OriginZip>
18
+ <PodEnabled>false</PodEnabled>
19
+ <PredictedDeliveryDate>April 28, 2015</PredictedDeliveryDate>
20
+ <RestoreEnabled>false</RestoreEnabled>
21
+ <RramEnabled>false</RramEnabled>
22
+ <RreEnabled>false</RreEnabled>
23
+ <Service>USPS Tracking&lt;SUP&gt;&amp;#153;&lt;/SUP&gt;</Service>
24
+ <ServiceTypeCode>001</ServiceTypeCode>
25
+ <Status>Delivered, In/At Mailbox</Status>
26
+ <StatusCategory>Delivered</StatusCategory>
27
+ <StatusSummary>Your item was delivered in or at the mailbox at 9:01 am on April 28, 2015 in HANNA CITY, IL 61536.</StatusSummary>
28
+ <TABLECODE>T</TABLECODE>
29
+ <TrackSummary>
30
+ <EventTime>9:01 am</EventTime>
31
+ <EventDate>April 28, 2015</EventDate>
32
+ <Event>Delivered, In/At Mailbox</Event>
33
+ <EventCity>HANNA CITY</EventCity>
34
+ <EventState>IL</EventState>
35
+ <EventZIPCode>61536</EventZIPCode>
36
+ <EventCountry/>
37
+ <FirmName/>
38
+ <Name/>
39
+ <AuthorizedAgent>false</AuthorizedAgent>
40
+ <EventCode>01</EventCode>
41
+ <DeliveryAttributeCode>01</DeliveryAttributeCode>
42
+ </TrackSummary>
43
+ <TrackDetail>
44
+ <EventTime>8:29 am</EventTime>
45
+ <EventDate>April 28, 2015</EventDate>
46
+ <Event>Out for Delivery</Event>
47
+ <EventCity>HANNA CITY</EventCity>
48
+ <EventState>IL</EventState>
49
+ <EventZIPCode>61536</EventZIPCode>
50
+ <EventCountry/>
51
+ <FirmName/>
52
+ <Name/>
53
+ <AuthorizedAgent>false</AuthorizedAgent>
54
+ <EventCode>OF</EventCode>
55
+ </TrackDetail>
56
+ <TrackDetail>
57
+ <EventTime>8:19 am</EventTime>
58
+ <EventDate>April 28, 2015</EventDate>
59
+ <Event>Sorting Complete</Event>
60
+ <EventCity>HANNA CITY</EventCity>
61
+ <EventState>IL</EventState>
62
+ <EventZIPCode>61536</EventZIPCode>
63
+ <EventCountry/>
64
+ <FirmName/>
65
+ <Name/>
66
+ <AuthorizedAgent>false</AuthorizedAgent>
67
+ <EventCode>PC</EventCode>
68
+ </TrackDetail>
69
+ <TrackDetail>
70
+ <EventTime>7:03 am</EventTime>
71
+ <EventDate>April 28, 2015</EventDate>
72
+ <Event>Arrived at Post Office</Event>
73
+ <EventCity>HANNA CITY</EventCity>
74
+ <EventState>IL</EventState>
75
+ <EventZIPCode>61536</EventZIPCode>
76
+ <EventCountry/>
77
+ <FirmName/>
78
+ <Name/>
79
+ <AuthorizedAgent>false</AuthorizedAgent>
80
+ <EventCode>07</EventCode>
81
+ </TrackDetail>
82
+ <TrackDetail>
83
+ <EventTime>4:05 am</EventTime>
84
+ <EventDate>April 28, 2015</EventDate>
85
+ <Event>Departed USPS Facility</Event>
86
+ <EventCity>HAZELWOOD</EventCity>
87
+ <EventState>MO</EventState>
88
+ <EventZIPCode>63042</EventZIPCode>
89
+ <EventCountry/>
90
+ <FirmName/>
91
+ <Name/>
92
+ <AuthorizedAgent>false</AuthorizedAgent>
93
+ <EventCode>EF</EventCode>
94
+ </TrackDetail>
95
+ <TrackDetail>
96
+ <EventTime>4:04 pm</EventTime>
97
+ <EventDate>April 27, 2015</EventDate>
98
+ <Event>Arrived at USPS Facility</Event>
99
+ <EventCity>HAZELWOOD</EventCity>
100
+ <EventState>MO</EventState>
101
+ <EventZIPCode>63042</EventZIPCode>
102
+ <EventCountry/>
103
+ <FirmName/>
104
+ <Name/>
105
+ <AuthorizedAgent>false</AuthorizedAgent>
106
+ <EventCode>10</EventCode>
107
+ </TrackDetail>
108
+ <TrackDetail>
109
+ <EventTime>12:18 am</EventTime>
110
+ <EventDate>April 26, 2015</EventDate>
111
+ <Event>Departed USPS Facility</Event>
112
+ <EventCity>COPPELL</EventCity>
113
+ <EventState>TX</EventState>
114
+ <EventZIPCode>75099</EventZIPCode>
115
+ <EventCountry/>
116
+ <FirmName/>
117
+ <Name/>
118
+ <AuthorizedAgent>false</AuthorizedAgent>
119
+ <EventCode>EF</EventCode>
120
+ </TrackDetail>
121
+ <TrackDetail>
122
+ <EventTime>7:19 pm</EventTime>
123
+ <EventDate>April 25, 2015</EventDate>
124
+ <Event>Arrived at USPS Origin Facility</Event>
125
+ <EventCity>COPPELL</EventCity>
126
+ <EventState>TX</EventState>
127
+ <EventZIPCode>75099</EventZIPCode>
128
+ <EventCountry/>
129
+ <FirmName/>
130
+ <Name/>
131
+ <AuthorizedAgent>false</AuthorizedAgent>
132
+ <EventCode>10</EventCode>
133
+ </TrackDetail>
134
+ <TrackDetail>
135
+ <EventTime>6:04 pm</EventTime>
136
+ <EventDate>April 25, 2015</EventDate>
137
+ <Event>Accepted at USPS Origin Sort Facility</Event>
138
+ <EventCity>ARGYLE</EventCity>
139
+ <EventState>TX</EventState>
140
+ <EventZIPCode>76226</EventZIPCode>
141
+ <EventCountry/>
142
+ <FirmName/>
143
+ <Name/>
144
+ <AuthorizedAgent>false</AuthorizedAgent>
145
+ <EventCode>OA</EventCode>
146
+ </TrackDetail>
147
+ <TrackDetail>
148
+ <EventTime>11:36 pm</EventTime>
149
+ <EventDate>April 23, 2015</EventDate>
150
+ <Event>Shipping Label Created</Event>
151
+ <EventCity>ARGYLE</EventCity>
152
+ <EventState>TX</EventState>
153
+ <EventZIPCode>76226</EventZIPCode>
154
+ <EventCountry/>
155
+ <FirmName/>
156
+ <Name/>
157
+ <AuthorizedAgent>false</AuthorizedAgent>
158
+ <EventCode>GX</EventCode>
159
+ </TrackDetail>
160
+ </TrackInfo>
161
+ <TrackInfo ID="5555555555555555555555">
162
+ <Class>First-Class Package Service</Class>
163
+ <ClassOfMailCode>FC</ClassOfMailCode>
164
+ <DestinationCity>HANNA CITY</DestinationCity>
165
+ <DestinationState>IL</DestinationState>
166
+ <DestinationZip>61536</DestinationZip>
167
+ <EmailEnabled>true</EmailEnabled>
168
+ <ExpectedDeliveryDate>April 28, 2015</ExpectedDeliveryDate>
169
+ <KahalaIndicator>false</KahalaIndicator>
170
+ <MailTypeCode>DM</MailTypeCode>
171
+ <MPDATE>2015-04-24 02:08:04.000000</MPDATE>
172
+ <MPSUFFIX>495865558</MPSUFFIX>
173
+ <OriginCity>ARGYLE</OriginCity>
174
+ <OriginState>TX</OriginState>
175
+ <OriginZip>76226</OriginZip>
176
+ <PodEnabled>false</PodEnabled>
177
+ <PredictedDeliveryDate>April 28, 2015</PredictedDeliveryDate>
178
+ <RestoreEnabled>false</RestoreEnabled>
179
+ <RramEnabled>false</RramEnabled>
180
+ <RreEnabled>false</RreEnabled>
181
+ <Service>USPS Tracking&lt;SUP&gt;&amp;#153;&lt;/SUP&gt;</Service>
182
+ <ServiceTypeCode>001</ServiceTypeCode>
183
+ <Status>Delivered, In/At Mailbox</Status>
184
+ <StatusCategory>Delivered</StatusCategory>
185
+ <StatusSummary>Your item was delivered in or at the mailbox at 9:01 am on April 28, 2015 in HANNA CITY, IL 61536.</StatusSummary>
186
+ <TABLECODE>T</TABLECODE>
187
+ <TrackSummary>
188
+ <EventTime>9:01 am</EventTime>
189
+ <EventDate>April 28, 2015</EventDate>
190
+ <Event>Delivered, In/At Mailbox</Event>
191
+ <EventCity>HANNA CITY</EventCity>
192
+ <EventState>IL</EventState>
193
+ <EventZIPCode>61536</EventZIPCode>
194
+ <EventCountry/>
195
+ <FirmName/>
196
+ <Name/>
197
+ <AuthorizedAgent>false</AuthorizedAgent>
198
+ <EventCode>01</EventCode>
199
+ <DeliveryAttributeCode>01</DeliveryAttributeCode>
200
+ </TrackSummary>
201
+ <TrackDetail>
202
+ <EventTime>11:36 pm</EventTime>
203
+ <EventDate>April 23, 2015</EventDate>
204
+ <Event>Shipping Label Created</Event>
205
+ <EventCity>ARGYLE</EventCity>
206
+ <EventState>TX</EventState>
207
+ <EventZIPCode>76226</EventZIPCode>
208
+ <EventCountry/>
209
+ <FirmName/>
210
+ <Name/>
211
+ <AuthorizedAgent>false</AuthorizedAgent>
212
+ <EventCode>GX</EventCode>
213
+ </TrackDetail>
214
+ </TrackInfo>
215
+ <TrackInfo ID="9405510ee200828613653750">
216
+ <Error>
217
+ <Number>-2147219302</Number>
218
+ <Description>The Postal Service could not locate the tracking information for your request. Please verify your tracking number and try again later.</Description>
219
+ <HelpFile/>
220
+ <HelpContext/>
221
+ </Error>
222
+ </TrackInfo>
223
+ </TrackResponse>
@@ -297,4 +297,68 @@ class RemoteFedExTest < Minitest::Test
297
297
  @carrier.find_tracking_info('abc')
298
298
  end
299
299
  end
300
+
301
+ def test_cant_obtain_multiple_shipping_labels
302
+ assert_raises(ActiveShipping::Error,"Multiple packages are not supported yet.") do
303
+ @carrier.create_shipment(
304
+ location_fixtures[:beverly_hills_with_name],
305
+ location_fixtures[:new_york_with_name],
306
+ [package_fixtures[:wii],package_fixtures[:wii]],
307
+ :test => true,
308
+ :reference_number => {
309
+ :value => "FOO-123",
310
+ :code => "PO"
311
+ }
312
+ )
313
+ end
314
+ end
315
+
316
+ def test_obtain_shipping_label_with_single_element_array
317
+ response = @carrier.create_shipment(
318
+ location_fixtures[:beverly_hills_with_name],
319
+ location_fixtures[:new_york_with_name],
320
+ [package_fixtures[:wii]],
321
+ :test => true,
322
+ :reference_number => {
323
+ :value => "FOO-123",
324
+ :code => "PO"
325
+ }
326
+ )
327
+
328
+ assert response.success?
329
+ refute_empty response.labels
330
+ refute_empty response.labels.first.img_data
331
+ end
332
+
333
+ def test_obtain_shipping_label
334
+ response = @carrier.create_shipment(
335
+ location_fixtures[:beverly_hills_with_name],
336
+ location_fixtures[:new_york_with_name],
337
+ package_fixtures[:wii],
338
+ :test => true,
339
+ :reference_number => {
340
+ :value => "FOO-123",
341
+ :code => "PO"
342
+ }
343
+ )
344
+
345
+ assert response.success?
346
+ refute_empty response.labels
347
+ refute_empty response.labels.first.img_data
348
+ end
349
+
350
+ def test_obtain_shipping_label_with_signature_option
351
+ packages = package_fixtures.values_at(:wii)
352
+ packages.each {|p| p.options[:signature_option] = :adult }
353
+
354
+ response = @carrier.create_shipment(
355
+ location_fixtures[:beverly_hills_with_name],
356
+ location_fixtures[:new_york_with_name],
357
+ packages,
358
+ {:test => true}
359
+ )
360
+
361
+ signature_option = response.params["ProcessShipmentReply"]["CompletedShipmentDetail"]["CompletedPackageDetails"]["SignatureOption"]
362
+ assert_equal FedEx::SIGNATURE_OPTION_CODES[:adult], signature_option
363
+ end
300
364
  end
data/test/test_helper.rb CHANGED
@@ -112,6 +112,16 @@ module ActiveShipping::Test
112
112
  :postal_code => 'K1P 1J1',
113
113
  :phone => '1-613-580-2400',
114
114
  :fax => '1-613-580-2495'),
115
+ :beverly_hills_with_name => Location.new(
116
+ :name => 'JP Silvashy',
117
+ :country => 'US',
118
+ :state => 'CA',
119
+ :city => 'Beverly Hills',
120
+ :address1 => '455 N. Rexford Dr.',
121
+ :address2 => '3rd Floor',
122
+ :zip => '90210',
123
+ :phone => '1-310-285-1013',
124
+ :fax => '1-310-275-8159'),
115
125
  :beverly_hills => Location.new(
116
126
  :country => 'US',
117
127
  :state => 'CA',
@@ -425,4 +425,45 @@ class FedExTest < Minitest::Test
425
425
  @carrier.find_tracking_info('abc')
426
426
  end
427
427
  end
428
+
429
+ def test_create_shipment
430
+ confirm_response = xml_fixture('fedex/create_shipment_response')
431
+ @carrier.stubs(:commit).returns(confirm_response)
432
+
433
+ response = @carrier.create_shipment(
434
+ location_fixtures[:beverly_hills],
435
+ location_fixtures[:new_york],
436
+ package_fixtures.values_at(:chocolate_stuff),
437
+ :test => true
438
+ )
439
+
440
+ # These assertions are to check that the xml fixture is extracted properly.
441
+ assert_equal 1, response.labels.count
442
+ assert_equal response.labels.first.tracking_number, "794637052920"
443
+ assert_equal response.labels.first.img_data.size, 8286
444
+ end
445
+
446
+ def test_create_shipment_signature_option
447
+ packages = package_fixtures.values_at(:chocolate_stuff)
448
+ packages.each {|p| p.options[:signature_option] = :indirect }
449
+ result = Nokogiri::XML(@carrier.send(:build_shipment_request,
450
+ location_fixtures[:beverly_hills],
451
+ location_fixtures[:annapolis],
452
+ packages,
453
+ :test => true))
454
+ assert_equal result.search('SpecialServicesRequested/SpecialServiceTypes').text, "SIGNATURE_OPTION"
455
+ assert_equal result.search('SpecialServicesRequested/SignatureOptionDetail').text.strip, "INDIRECT"
456
+ end
457
+
458
+ def test_create_shipment_reference
459
+ packages = package_fixtures.values_at(:wii)
460
+ packages.each {|p| p.options[:reference_numbers] = [{:value => "FOO-123"}] }
461
+
462
+ result = Nokogiri::XML(@carrier.send(:build_shipment_request,
463
+ location_fixtures[:beverly_hills],
464
+ location_fixtures[:annapolis],
465
+ packages,
466
+ :test => true))
467
+ assert_equal result.search('RequestedPackageLineItems/CustomerReferences/Value').text, "FOO-123"
468
+ end
428
469
  end
@@ -266,12 +266,12 @@ class UPSTest < Minitest::Test
266
266
 
267
267
  # These tracking numbers are part of the fixture data. What we're trying
268
268
  # to verify is that the data in the XML is extracted properly.
269
- tracking = response.labels.map { |label| label[:tracking_number] }
269
+ tracking = response.labels.map { |label| label.tracking_number }
270
270
  assert_includes tracking, "1ZA03R691594829862"
271
271
  assert_includes tracking, "1ZA03R691592132475"
272
272
  assert_includes tracking, "1ZA03R691590470881"
273
273
 
274
- pictures = response.labels.map { |label| label[:image] }
274
+ pictures = response.labels.map { |label| label.img_data }
275
275
  refute_includes pictures, nil
276
276
  end
277
277
 
@@ -299,10 +299,10 @@ class UPSTest < Minitest::Test
299
299
 
300
300
  # These tracking numbers are part of the fixture data. What we're trying
301
301
  # to verify is that the data in the XML is extracted properly.
302
- tracking = response.labels.map { |label| label[:tracking_number] }
302
+ tracking = response.labels.map { |label| label.tracking_number }
303
303
  assert_includes tracking, "1ZA03R691591538440"
304
304
 
305
- pictures = response.labels.map { |label| label[:image] }
305
+ pictures = response.labels.map { |label| label.img_data }
306
306
  refute_includes pictures, nil
307
307
  end
308
308
 
@@ -6,7 +6,13 @@ class USPSTest < Minitest::Test
6
6
  def setup
7
7
  @carrier = USPS.new(:login => 'login')
8
8
  @tracking_response = xml_fixture('usps/tracking_response')
9
+ @batch_tracking_response = xml_fixture('usps/tracking_response_batch')
9
10
  @tracking_response_failure = xml_fixture('usps/tracking_response_failure')
11
+ @tracking_infos_array = [
12
+ {number: '9102901000462189604217', :destination_zip => '12345', :mailing_date => Date.new(2010,1,30)},
13
+ {number: '5555555555555555555555'},
14
+ {number: '9405510ee200828613653750'}
15
+ ]
10
16
  end
11
17
 
12
18
  def test_tracking_request_should_create_correct_xml
@@ -159,6 +165,45 @@ class USPSTest < Minitest::Test
159
165
  assert_equal true, response.delivered?
160
166
  end
161
167
 
168
+ def test_tracking_batch_request_should_create_correct_xml
169
+ @carrier.expects(:commit).with(:track, xml_fixture('usps/tracking_request_batch'),false).returns(@batch_tracking_response)
170
+ @carrier.batch_find_tracking_info(@tracking_infos_array)
171
+ end
172
+
173
+ def test_batch_find_tracking_info_should_return_a_tracking_response_array
174
+ @carrier.expects(:commit).returns(@batch_tracking_response)
175
+ responses = @carrier.batch_find_tracking_info(@tracking_infos_array, :test => true)
176
+ assert_equal 3, responses.length
177
+ assert responses.all? { |x| x.instance_of? ActiveShipping::TrackingResponse}
178
+ end
179
+
180
+ def test_batch_find_tracking_info_should_have_correct_data
181
+ @carrier.expects(:commit).returns(@batch_tracking_response)
182
+ responses = @carrier.batch_find_tracking_info(@tracking_infos_array, :test => true)
183
+ response = responses[0]
184
+ assert_equal ["GX", "OA", "10", "EF", "10", "EF", "07", "PC", "OF", "01"], response.shipment_events.map(&:type_code)
185
+ assert_equal Time.parse('April 28, 2015'), response.scheduled_delivery_date
186
+ assert_equal Time.parse('2015-04-28 09:01:00 UTC'), response.actual_delivery_date
187
+ assert_equal '9102901000462189604217', response.tracking_number
188
+ end
189
+
190
+ def test_batch_find_tracking_info_should_tolerate_failure
191
+ @carrier.expects(:commit).returns(@batch_tracking_response)
192
+ responses = @carrier.batch_find_tracking_info(@tracking_infos_array, :test => true)
193
+ response = responses[2]
194
+ refute response.success?
195
+ assert_equal "The Postal Service could not locate the tracking information for your request. Please verify your tracking number and try again later.",
196
+ response.message
197
+ end
198
+
199
+ def test_batch_find_tracking_info_should_handle_not_found_error
200
+ @carrier.expects(:commit).returns(xml_fixture('usps/tracking_response_test_error'))
201
+ e = assert_raises ResponseError do
202
+ @carrier.find_tracking_info(@batch_tracking_response, :test => true)
203
+ end
204
+ assert_equal "This Information has not been included in this Test Server.", e.message
205
+ end
206
+
162
207
  def test_size_codes
163
208
  assert_equal 'REGULAR', USPS.size_code_for(Package.new(2, [1, 12, 1], :units => :imperial))
164
209
  assert_equal 'LARGE', USPS.size_code_for(Package.new(2, [12.1, 1, 1], :units => :imperial))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_shipping
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James MacAulay
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-05-27 00:00:00.000000000 Z
14
+ date: 2015-05-29 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: quantified
@@ -185,6 +185,7 @@ files:
185
185
  - lib/active_shipping/delivery_date_estimate.rb
186
186
  - lib/active_shipping/delivery_date_estimates_response.rb
187
187
  - lib/active_shipping/errors.rb
188
+ - lib/active_shipping/label.rb
188
189
  - lib/active_shipping/label_response.rb
189
190
  - lib/active_shipping/location.rb
190
191
  - lib/active_shipping/package.rb
@@ -246,6 +247,7 @@ files:
246
247
  - test/fixtures/xml/correios/clothes_response.xml
247
248
  - test/fixtures/xml/correios/poster_response.xml
248
249
  - test/fixtures/xml/correios/shoes_response.xml
250
+ - test/fixtures/xml/fedex/create_shipment_response.xml
249
251
  - test/fixtures/xml/fedex/freight_rate_request.xml
250
252
  - test/fixtures/xml/fedex/freight_rate_response.xml
251
253
  - test/fixtures/xml/fedex/invalid_fedex_reply.xml
@@ -321,8 +323,10 @@ files:
321
323
  - test/fixtures/xml/usps/first_class_packages_without_mail_type_response.xml
322
324
  - test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml
323
325
  - test/fixtures/xml/usps/tracking_request.xml
326
+ - test/fixtures/xml/usps/tracking_request_batch.xml
324
327
  - test/fixtures/xml/usps/tracking_response.xml
325
328
  - test/fixtures/xml/usps/tracking_response_alt.xml
329
+ - test/fixtures/xml/usps/tracking_response_batch.xml
326
330
  - test/fixtures/xml/usps/tracking_response_failure.xml
327
331
  - test/fixtures/xml/usps/tracking_response_not_available.xml
328
332
  - test/fixtures/xml/usps/tracking_response_test_error.xml
@@ -356,7 +360,6 @@ files:
356
360
  - test/unit/carriers/ups_test.rb
357
361
  - test/unit/carriers/usps_test.rb
358
362
  - test/unit/carriers_test.rb
359
- - test/unit/label_response_test.rb
360
363
  - test/unit/location_test.rb
361
364
  - test/unit/package_test.rb
362
365
  - test/unit/rate_estimate_test.rb
@@ -434,6 +437,7 @@ test_files:
434
437
  - test/fixtures/xml/correios/clothes_response.xml
435
438
  - test/fixtures/xml/correios/poster_response.xml
436
439
  - test/fixtures/xml/correios/shoes_response.xml
440
+ - test/fixtures/xml/fedex/create_shipment_response.xml
437
441
  - test/fixtures/xml/fedex/freight_rate_request.xml
438
442
  - test/fixtures/xml/fedex/freight_rate_response.xml
439
443
  - test/fixtures/xml/fedex/invalid_fedex_reply.xml
@@ -509,8 +513,10 @@ test_files:
509
513
  - test/fixtures/xml/usps/first_class_packages_without_mail_type_response.xml
510
514
  - test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml
511
515
  - test/fixtures/xml/usps/tracking_request.xml
516
+ - test/fixtures/xml/usps/tracking_request_batch.xml
512
517
  - test/fixtures/xml/usps/tracking_response.xml
513
518
  - test/fixtures/xml/usps/tracking_response_alt.xml
519
+ - test/fixtures/xml/usps/tracking_response_batch.xml
514
520
  - test/fixtures/xml/usps/tracking_response_failure.xml
515
521
  - test/fixtures/xml/usps/tracking_response_not_available.xml
516
522
  - test/fixtures/xml/usps/tracking_response_test_error.xml
@@ -544,7 +550,6 @@ test_files:
544
550
  - test/unit/carriers/ups_test.rb
545
551
  - test/unit/carriers/usps_test.rb
546
552
  - test/unit/carriers_test.rb
547
- - test/unit/label_response_test.rb
548
553
  - test/unit/location_test.rb
549
554
  - test/unit/package_test.rb
550
555
  - test/unit/rate_estimate_test.rb
@@ -1,15 +0,0 @@
1
- require 'test_helper'
2
-
3
- class LabelResponseTest < Minitest::Test
4
- include ActiveShipping::Test::Fixtures
5
-
6
- def test_build_label_from_xml_response
7
- str = xml_fixture('ups/shipment_accept_response')
8
- mapping = Hash.from_xml(str).values.first
9
- response = LabelResponse.new(true, nil, mapping)
10
-
11
- assert_equal 1, response.labels.count
12
- assert_equal '1ZA03R691591538440', response.labels.first[:tracking_number]
13
- assert response.labels.first[:image]
14
- end
15
- end