reactive_shipping 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +33 -0
- data/.yardopts +13 -0
- data/CHANGELOG.md +225 -0
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +21 -0
- data/README.md +158 -0
- data/Rakefile +35 -0
- data/dev.yml +17 -0
- data/gemfiles/activesupport42.gemfile +5 -0
- data/gemfiles/activesupport50.gemfile +6 -0
- data/gemfiles/activesupport51.gemfile +5 -0
- data/gemfiles/activesupport52.gemfile +5 -0
- data/gemfiles/activesupport_master.gemfile +5 -0
- data/lib/certs/eParcel.dtd +111 -0
- data/lib/reactive_shipping.rb +26 -0
- data/lib/reactive_shipping/address_validation_response.rb +30 -0
- data/lib/reactive_shipping/carrier.rb +184 -0
- data/lib/reactive_shipping/carriers.rb +35 -0
- data/lib/reactive_shipping/carriers/australia_post.rb +248 -0
- data/lib/reactive_shipping/carriers/benchmark_carrier.rb +31 -0
- data/lib/reactive_shipping/carriers/bogus_carrier.rb +12 -0
- data/lib/reactive_shipping/carriers/canada_post.rb +263 -0
- data/lib/reactive_shipping/carriers/canada_post_pws.rb +908 -0
- data/lib/reactive_shipping/carriers/fedex.rb +797 -0
- data/lib/reactive_shipping/carriers/kunaki.rb +155 -0
- data/lib/reactive_shipping/carriers/new_zealand_post.rb +260 -0
- data/lib/reactive_shipping/carriers/shipwire.rb +178 -0
- data/lib/reactive_shipping/carriers/stamps.rb +860 -0
- data/lib/reactive_shipping/carriers/ups.rb +1060 -0
- data/lib/reactive_shipping/carriers/usps.rb +708 -0
- data/lib/reactive_shipping/carriers/usps_returns.rb +86 -0
- data/lib/reactive_shipping/delivery_date_estimate.rb +20 -0
- data/lib/reactive_shipping/delivery_date_estimates_response.rb +11 -0
- data/lib/reactive_shipping/errors.rb +35 -0
- data/lib/reactive_shipping/external_return_label_request.rb +417 -0
- data/lib/reactive_shipping/external_return_label_response.rb +26 -0
- data/lib/reactive_shipping/label.rb +10 -0
- data/lib/reactive_shipping/label_response.rb +10 -0
- data/lib/reactive_shipping/location.rb +166 -0
- data/lib/reactive_shipping/package.rb +165 -0
- data/lib/reactive_shipping/package_item.rb +60 -0
- data/lib/reactive_shipping/rate_estimate.rb +197 -0
- data/lib/reactive_shipping/rate_response.rb +33 -0
- data/lib/reactive_shipping/response.rb +44 -0
- data/lib/reactive_shipping/shipment_event.rb +22 -0
- data/lib/reactive_shipping/shipment_packer.rb +108 -0
- data/lib/reactive_shipping/shipping_response.rb +34 -0
- data/lib/reactive_shipping/tracking_response.rb +120 -0
- data/lib/reactive_shipping/version.rb +3 -0
- data/reactive_shipping.gemspec +38 -0
- data/shipit.rubygems.yml +1 -0
- data/test/console.rb +39 -0
- data/test/credentials.yml +76 -0
- data/test/fixtures/files/label1.pdf +0 -0
- data/test/fixtures/files/ups-shipping-label.gif +0 -0
- data/test/fixtures/json/australia_post/calculate_domestic.json +13 -0
- data/test/fixtures/json/australia_post/calculate_domestic_2.json +19 -0
- data/test/fixtures/json/australia_post/calculate_international.json +12 -0
- data/test/fixtures/json/australia_post/calculate_international_2.json +15 -0
- data/test/fixtures/json/australia_post/error_message.json +5 -0
- data/test/fixtures/json/australia_post/service_domestic.json +117 -0
- data/test/fixtures/json/australia_post/service_domestic_2.json +117 -0
- data/test/fixtures/json/australia_post/service_international.json +76 -0
- data/test/fixtures/json/australia_post/service_international_2.json +59 -0
- data/test/fixtures/json/newzealandpost/domestic_book.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_default.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_error.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_poster.json +1 -0
- data/test/fixtures/json/newzealandpost/domestic_small_half_pound.json +1 -0
- data/test/fixtures/json/newzealandpost/international_book.json +1 -0
- data/test/fixtures/json/newzealandpost/international_new_zealand_wii.json +1 -0
- data/test/fixtures/json/newzealandpost/international_small_half_pound.json +1 -0
- data/test/fixtures/json/newzealandpost/international_wii.json +1 -0
- data/test/fixtures/xml/canadapost/example_request.xml +25 -0
- data/test/fixtures/xml/canadapost/example_response.xml +130 -0
- data/test/fixtures/xml/canadapost/example_response_error.xml +16 -0
- data/test/fixtures/xml/canadapost/example_response_french.xml +122 -0
- data/test/fixtures/xml/canadapost/example_response_with_nil_value.xml +164 -0
- data/test/fixtures/xml/canadapost/example_response_with_postal_outlet.xml +155 -0
- data/test/fixtures/xml/canadapost/example_response_with_postal_outlet_french.xml +274 -0
- data/test/fixtures/xml/canadapost/example_response_with_strange_delivery_date.xml +130 -0
- data/test/fixtures/xml/canadapost_pws/dnc_tracking_details_en.xml +112 -0
- data/test/fixtures/xml/canadapost_pws/merchant_details_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/merchant_details_response.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/option_response.xml +13 -0
- data/test/fixtures/xml/canadapost_pws/option_response_no_conflicts.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/rates_info.xml +190 -0
- data/test/fixtures/xml/canadapost_pws/rates_info_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/receipt_response.xml +42 -0
- data/test/fixtures/xml/canadapost_pws/receipt_response_no_priced_options.xml +36 -0
- data/test/fixtures/xml/canadapost_pws/register_token_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/register_token_response.xml +3 -0
- data/test/fixtures/xml/canadapost_pws/service_options_response.xml +42 -0
- data/test/fixtures/xml/canadapost_pws/services_error.xml +6 -0
- data/test/fixtures/xml/canadapost_pws/services_response.xml +32 -0
- data/test/fixtures/xml/canadapost_pws/shipment_domestic.xml +69 -0
- data/test/fixtures/xml/canadapost_pws/shipment_response.xml +20 -0
- data/test/fixtures/xml/canadapost_pws/shipment_us.xml +69 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_en.xml +152 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_en_error.xml +7 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_en_undelivered.xml +116 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_fr.xml +156 -0
- data/test/fixtures/xml/canadapost_pws/tracking_details_no_expected_delivery_date.xml +40 -0
- data/test/fixtures/xml/fedex/create_shipment_response.xml +2 -0
- data/test/fixtures/xml/fedex/freight_rate_request.xml +82 -0
- data/test/fixtures/xml/fedex/freight_rate_response.xml +506 -0
- data/test/fixtures/xml/fedex/invalid_fedex_reply.xml +27 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_commercial_rate_request.xml +79 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_no_saturday_rate_request.xml +79 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_request.xml +80 -0
- data/test/fixtures/xml/fedex/ottawa_to_beverly_hills_rate_response.xml +214 -0
- data/test/fixtures/xml/fedex/raterequest_reply.xml +213 -0
- data/test/fixtures/xml/fedex/raterequest_response_with_ground_home_delivery.xml +206 -0
- data/test/fixtures/xml/fedex/reply_without_notifications.xml +185 -0
- data/test/fixtures/xml/fedex/tracking_request.xml +29 -0
- data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
- data/test/fixtures/xml/fedex/tracking_response_empty_status_detail.xml +84 -0
- data/test/fixtures/xml/fedex/tracking_response_failure_code_9045.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_failure_code_9080.xml +51 -0
- data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
- data/test/fixtures/xml/fedex/tracking_response_invalid_tracking_number.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_missing_status_code.xml +89 -0
- data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
- data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
- data/test/fixtures/xml/fedex/tracking_response_unable_to_process.xml +32 -0
- data/test/fixtures/xml/fedex/tracking_response_with_blank_state.xml +107 -0
- data/test/fixtures/xml/fedex/unknown_fedex_document_reply.xml +3 -0
- data/test/fixtures/xml/kunaki/invalid_state_response.xml +3 -0
- data/test/fixtures/xml/kunaki/no_valid_items_response.xml +3 -0
- data/test/fixtures/xml/kunaki/successful_rates_response.xml +3 -0
- data/test/fixtures/xml/kunaki/unsuccessful_rates_response.xml +9 -0
- data/test/fixtures/xml/shipwire/international_rates_response.xml +17 -0
- data/test/fixtures/xml/shipwire/new_carrier_rate_response.xml +18 -0
- data/test/fixtures/xml/shipwire/no_rates_response.xml +7 -0
- data/test/fixtures/xml/shipwire/rates_response.xml +36 -0
- data/test/fixtures/xml/shipwire/rates_response_no_estimate.xml +14 -0
- data/test/fixtures/xml/stamps/authenticate_user_request.xml +15 -0
- data/test/fixtures/xml/stamps/authenticate_user_response.xml +10 -0
- data/test/fixtures/xml/stamps/cleanse_address_request.xml +19 -0
- data/test/fixtures/xml/stamps/cleanse_address_response.xml +27 -0
- data/test/fixtures/xml/stamps/create_indicium_request.xml +69 -0
- data/test/fixtures/xml/stamps/create_indicium_response.xml +40 -0
- data/test/fixtures/xml/stamps/expired_authenticator_response.xml +15 -0
- data/test/fixtures/xml/stamps/get_account_info_request.xml +11 -0
- data/test/fixtures/xml/stamps/get_account_info_response.xml +36 -0
- data/test/fixtures/xml/stamps/get_purchase_status_request.xml +12 -0
- data/test/fixtures/xml/stamps/get_purchase_status_response.xml +16 -0
- data/test/fixtures/xml/stamps/get_rates_request.xml +19 -0
- data/test/fixtures/xml/stamps/get_rates_response.xml +351 -0
- data/test/fixtures/xml/stamps/purchase_postage_request.xml +13 -0
- data/test/fixtures/xml/stamps/purchase_postage_response.xml +17 -0
- data/test/fixtures/xml/stamps/track_shipment_request.xml +12 -0
- data/test/fixtures/xml/stamps/track_shipment_response.xml +45 -0
- data/test/fixtures/xml/ups/access_request.xml +6 -0
- data/test/fixtures/xml/ups/delivered_shipment_with_refund.xml +290 -0
- data/test/fixtures/xml/ups/delivered_shipment_without_events_tracking_response.xml +62 -0
- data/test/fixtures/xml/ups/delivery_dates_response.xml +140 -0
- data/test/fixtures/xml/ups/example_tracking_response.xml +53 -0
- data/test/fixtures/xml/ups/in_transit_shipment.xml +183 -0
- data/test/fixtures/xml/ups/out_for_delivery_shipment.xml +165 -0
- data/test/fixtures/xml/ups/package_exceeds_maximum_length.xml +12 -0
- data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
- data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -0
- data/test/fixtures/xml/ups/shipment_accept_response.xml +42 -0
- data/test/fixtures/xml/ups/shipment_confirm_response.xml +33 -0
- data/test/fixtures/xml/ups/shipment_from_tiger_direct.xml +222 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +290 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response_with_insured.xml +289 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_with_origin_account_response.xml +311 -0
- data/test/fixtures/xml/ups/tracking_request.xml +9 -0
- data/test/fixtures/xml/ups/triple_accept_response.xml +72 -0
- data/test/fixtures/xml/ups/triple_confirm_response.xml +32 -0
- data/test/fixtures/xml/ups/void_shipment_response.xml +11 -0
- data/test/fixtures/xml/usps/api_error_rate_response.xml +53 -0
- data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_base_rate_response.xml +2 -0
- data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_commercial_plus_rate_response.xml +258 -0
- data/test/fixtures/xml/usps/beverly_hills_to_new_york_book_rate_response.xml +108 -0
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_base_rate_response.xml +84 -0
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_commercial_plus_rate_response.xml +212 -0
- data/test/fixtures/xml/usps/beverly_hills_to_ottawa_american_wii_rate_response.xml +230 -0
- data/test/fixtures/xml/usps/first_class_packages_with_invalid_mail_type_response.xml +12 -0
- data/test/fixtures/xml/usps/first_class_packages_with_mail_type_response.xml +16 -0
- data/test/fixtures/xml/usps/first_class_packages_without_mail_type_response.xml +12 -0
- data/test/fixtures/xml/usps/invalid_xml_response.xml +10 -0
- data/test/fixtures/xml/usps/invalid_xml_tracking_response_error.xml +2 -0
- data/test/fixtures/xml/usps/tracking_request.xml +10 -0
- data/test/fixtures/xml/usps/tracking_request_batch.xml +12 -0
- data/test/fixtures/xml/usps/tracking_response.xml +162 -0
- data/test/fixtures/xml/usps/tracking_response_alt.xml +53 -0
- data/test/fixtures/xml/usps/tracking_response_batch.xml +231 -0
- data/test/fixtures/xml/usps/tracking_response_failure.xml +11 -0
- data/test/fixtures/xml/usps/tracking_response_not_available.xml +12 -0
- data/test/fixtures/xml/usps/tracking_response_test_error.xml +8 -0
- data/test/fixtures/xml/usps/us_rate_request.xml +18 -0
- data/test/fixtures/xml/usps/us_rate_request_large.xml +18 -0
- data/test/fixtures/xml/usps/world_rate_request_only_country.xml +22 -0
- data/test/fixtures/xml/usps/world_rate_request_with_value.xml +24 -0
- data/test/fixtures/xml/usps/world_rate_request_without_value.xml +24 -0
- data/test/fixtures/xml/usps_returns/external_return_label_response.xml +2 -0
- data/test/fixtures/xml/usps_returns/external_return_label_response_failure.xml +10 -0
- data/test/remote/australia_post_test.rb +140 -0
- data/test/remote/canada_post_pws_platform_test.rb +259 -0
- data/test/remote/canada_post_pws_test.rb +169 -0
- data/test/remote/canada_post_test.rb +55 -0
- data/test/remote/fedex_test.rb +400 -0
- data/test/remote/kunaki_test.rb +37 -0
- data/test/remote/new_zealand_post_test.rb +149 -0
- data/test/remote/shipwire_test.rb +84 -0
- data/test/remote/stamps_test.rb +396 -0
- data/test/remote/usps_returns_test.rb +72 -0
- data/test/remote/usps_test.rb +243 -0
- data/test/test_helper.rb +296 -0
- data/test/unit/carrier_test.rb +130 -0
- data/test/unit/carriers/australia_post_test.rb +181 -0
- data/test/unit/carriers/benchmark_test.rb +18 -0
- data/test/unit/carriers/canada_post_pws_rating_test.rb +379 -0
- data/test/unit/carriers/canada_post_pws_register_test.rb +76 -0
- data/test/unit/carriers/canada_post_pws_shipping_test.rb +258 -0
- data/test/unit/carriers/canada_post_pws_test.rb +59 -0
- data/test/unit/carriers/canada_post_pws_tracking_test.rb +154 -0
- data/test/unit/carriers/canada_post_test.rb +148 -0
- data/test/unit/carriers/fedex_test.rb +693 -0
- data/test/unit/carriers/kunaki_test.rb +56 -0
- data/test/unit/carriers/new_zealand_post_test.rb +177 -0
- data/test/unit/carriers/shipwire_test.rb +188 -0
- data/test/unit/carriers/stamps_test.rb +245 -0
- data/test/unit/carriers/ups_test.rb +580 -0
- data/test/unit/carriers/usps_returns_test.rb +45 -0
- data/test/unit/carriers/usps_test.rb +633 -0
- data/test/unit/carriers_test.rb +16 -0
- data/test/unit/external_return_label_request_test.rb +258 -0
- data/test/unit/location_test.rb +234 -0
- data/test/unit/package_item_test.rb +232 -0
- data/test/unit/package_test.rb +404 -0
- data/test/unit/rate_estimate_test.rb +93 -0
- data/test/unit/response_test.rb +38 -0
- data/test/unit/shipment_event_test.rb +20 -0
- data/test/unit/shipment_packer_test.rb +212 -0
- data/test/unit/tracking_response_test.rb +41 -0
- metadata +684 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
|
|
3
|
+
class USPSReturns < Carrier
|
|
4
|
+
|
|
5
|
+
self.retry_safe = true
|
|
6
|
+
|
|
7
|
+
cattr_reader :name
|
|
8
|
+
@@name = "USPS Returns"
|
|
9
|
+
|
|
10
|
+
LIVE_DOMAIN = 'returns.usps.com'
|
|
11
|
+
LIVE_RESOURCE = 'Services/ExternalCreateReturnLabel.svc/ExternalCreateReturnLabel'
|
|
12
|
+
|
|
13
|
+
TEST_DOMAIN = 'returns.usps.com'
|
|
14
|
+
TEST_RESOURCE = 'Services/ExternalCreateReturnLabel.svc/ExternalCreateReturnLabel'
|
|
15
|
+
|
|
16
|
+
API_CODES = {
|
|
17
|
+
:external_return_label_request => 'externalReturnLabelRequest'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
USE_SSL = {
|
|
21
|
+
:external_return_label_request => true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def requirements
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def external_return_label_request(label, options = {})
|
|
29
|
+
response = commit(:external_return_label_request, label.to_xml, (options[:test] || false))
|
|
30
|
+
parse_external_return_label_response(response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def parse_external_return_label_response(response)
|
|
36
|
+
tracking_number, postal_routing, return_label, message = '', '', '', '', ''
|
|
37
|
+
xml = Nokogiri::XML(response)
|
|
38
|
+
error = external_return_label_errors(xml)
|
|
39
|
+
if error.is_a?(Hash) && error.size > 0
|
|
40
|
+
message << "#{error[:error][:code]}: #{error[:error][:message]}"
|
|
41
|
+
else
|
|
42
|
+
tracking_number = xml.at('TrackingNumber').try(:text)
|
|
43
|
+
postal_routing = xml.at('PostalRouting').try(:text)
|
|
44
|
+
return_label = xml.at('ReturnLabel').try(:text)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
ExternalReturnLabelResponse.new(message.length == 0, message, Hash.from_xml(response),
|
|
48
|
+
:xml => response,
|
|
49
|
+
:carrier => @@name,
|
|
50
|
+
:request => last_request,
|
|
51
|
+
:return_label => return_label,
|
|
52
|
+
:postal_routing => postal_routing,
|
|
53
|
+
:tracking_number => tracking_number
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def external_return_label_errors(document)
|
|
58
|
+
return {} unless document.respond_to?(:elements)
|
|
59
|
+
res = {}
|
|
60
|
+
if node = document.at('*/errors')
|
|
61
|
+
if node.at('ExternalReturnLabelError')
|
|
62
|
+
if message = node.at('ExternalReturnLabelError/InternalErrorDescription').try(:text)
|
|
63
|
+
code = node.at('ExternalReturnLabelError/InternalErrorNumber').try(:text) || ''
|
|
64
|
+
res = {:error => {:code => code, :message => message}}
|
|
65
|
+
elsif message = node.at('ExternalReturnLabelError/ExternalErrorDescription').try(:text)
|
|
66
|
+
code = node.at('ExternalReturnLabelError/ExternalErrorNumber').try(:text) || ''
|
|
67
|
+
res = {:error => {:code => code, :message => message}}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
res
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def commit(action, request, test = false)
|
|
75
|
+
ssl_get(request_url(action, request, test))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def request_url(action, request, test)
|
|
79
|
+
scheme = USE_SSL[action] ? 'https://' : 'http://'
|
|
80
|
+
host = test ? TEST_DOMAIN : LIVE_DOMAIN
|
|
81
|
+
resource = test ? TEST_RESOURCE : LIVE_RESOURCE
|
|
82
|
+
"#{scheme}#{host}/#{resource}?#{API_CODES[action]}=#{URI.encode(request)}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class DeliveryDateEstimate
|
|
3
|
+
attr_reader :origin
|
|
4
|
+
attr_reader :destination
|
|
5
|
+
attr_reader :carrier
|
|
6
|
+
attr_reader :service_name
|
|
7
|
+
attr_reader :service_code
|
|
8
|
+
attr_reader :date
|
|
9
|
+
attr_reader :guaranteed
|
|
10
|
+
attr_reader :business_transit_days
|
|
11
|
+
|
|
12
|
+
def initialize(origin, destination, carrier, service_name, options={})
|
|
13
|
+
@origin, @destination, @carrier, @service_name = origin, destination, carrier, service_name
|
|
14
|
+
@service_code = options[:service_code]
|
|
15
|
+
@date = options[:date]
|
|
16
|
+
@guaranteed = options[:guaranteed]
|
|
17
|
+
@business_transit_days = options[:business_transit_days]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class DeliveryDateEstimatesResponse < Response
|
|
3
|
+
attr_reader :delivery_estimates
|
|
4
|
+
|
|
5
|
+
def initialize(success, message, params = {}, options = {})
|
|
6
|
+
@delivery_estimates = Array(options[:delivery_estimates])
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class Error < ActiveUtils::ActiveUtilsError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class ResponseError < ReactiveShipping::Error
|
|
6
|
+
attr_reader :response
|
|
7
|
+
|
|
8
|
+
def initialize(response = nil)
|
|
9
|
+
if response.is_a? Response
|
|
10
|
+
super(response.message)
|
|
11
|
+
@response = response
|
|
12
|
+
else
|
|
13
|
+
super(response)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ResponseContentError < ReactiveShipping::Error
|
|
19
|
+
def initialize(exception, content_body = nil)
|
|
20
|
+
super([exception.message, content_body].compact.join(" \n\n"))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class ShipmentNotFound < ReactiveShipping::Error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class USPSValidationError < StandardError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class USPSMissingRequiredTagError < StandardError
|
|
31
|
+
def initialize(tag, prop)
|
|
32
|
+
super("Missing required tag #{tag} set by property #{prop}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
module ReactiveShipping
|
|
2
|
+
class ExternalReturnLabelRequest
|
|
3
|
+
CAP_STRING_LEN = 100
|
|
4
|
+
|
|
5
|
+
USPS_EMAIL_REGEX = /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/
|
|
6
|
+
|
|
7
|
+
LABEL_FORMAT = {
|
|
8
|
+
'Instructions' => 'null',
|
|
9
|
+
'No Instructions' => 'NOI',
|
|
10
|
+
'Double Label' => 'TWO'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
SERVICE_TYPE_CODE = [
|
|
14
|
+
'044', '019', '596', '020', '597','022', '024', '017', '018'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
CALL_CENTER_OR_SELF_SERVICE = ['CallCenter', 'Customer']
|
|
18
|
+
|
|
19
|
+
LABEL_DEFINITION = ['4X6', 'Zebra-4X6', '4X4', '3X6']
|
|
20
|
+
|
|
21
|
+
IMAGE_TYPE = ['PDF', 'TIF']
|
|
22
|
+
|
|
23
|
+
attr_reader :customer_name,
|
|
24
|
+
:customer_address1,
|
|
25
|
+
:customer_address2,
|
|
26
|
+
:customer_city,
|
|
27
|
+
:customer_state,
|
|
28
|
+
:customer_zipcode,
|
|
29
|
+
:customer_urbanization,
|
|
30
|
+
:company_name,
|
|
31
|
+
:attention,
|
|
32
|
+
:label_format,
|
|
33
|
+
:label_definition,
|
|
34
|
+
:service_type_code,
|
|
35
|
+
:merchandise_description,
|
|
36
|
+
:insurance_amount,
|
|
37
|
+
:address_override_notification,
|
|
38
|
+
:packaging_information,
|
|
39
|
+
:packaging_information2,
|
|
40
|
+
:call_center_or_self_service,
|
|
41
|
+
:image_type,
|
|
42
|
+
:address_validation,
|
|
43
|
+
:sender_name,
|
|
44
|
+
:sender_email,
|
|
45
|
+
:recipient_name,
|
|
46
|
+
:recipient_email,
|
|
47
|
+
:recipient_bcc,
|
|
48
|
+
:merchant_account_id,
|
|
49
|
+
:mid
|
|
50
|
+
|
|
51
|
+
def initialize(options = {})
|
|
52
|
+
options.each do |pair|
|
|
53
|
+
self.public_send("#{pair[0]}=".to_sym, pair[1]) if self.respond_to?("#{pair[0]}=".to_sym)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
verify_or_raise_required
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.from_hash(options={})
|
|
60
|
+
self.new(options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Sent by the system containing the returns label attachment and message.
|
|
64
|
+
def recipient_bcc=(v)
|
|
65
|
+
@recipient_bcc = validate_email(v, __method__)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Sent by the system containing the returns label attachment and message.
|
|
69
|
+
# <em>Optional</em>.
|
|
70
|
+
def recipient_email=(v)
|
|
71
|
+
@recipient_email = validate_email(v, __method__)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The name in an email sent by the system containing the returns label attachment.
|
|
75
|
+
# <em>Optional</em>.
|
|
76
|
+
def recipient_name=(v)
|
|
77
|
+
@recipient_name = nil
|
|
78
|
+
if (v = sanitize(v)) && v.length > 0
|
|
79
|
+
@recipient_name = v
|
|
80
|
+
else
|
|
81
|
+
raise USPSValidationError, "'#{v}' is not a valid string in #{__method__}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The From address in an email sent by the system containing the returns
|
|
86
|
+
# label attachment and message, Defaults to DONOTREPLY@USPSReturns.com
|
|
87
|
+
# if a recipient email is entered and a sender email is not.
|
|
88
|
+
# <em>Optional</em>.
|
|
89
|
+
def sender_email=(v)
|
|
90
|
+
@sender_email = validate_email(v, __method__)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The From name in an email sent by the system containing the returns
|
|
94
|
+
# label attachment. Defaults to “Merchant Returns” if a recipient name
|
|
95
|
+
# is entered and a sender name is not.
|
|
96
|
+
# <em>Optional</em>.
|
|
97
|
+
def sender_name=(v)
|
|
98
|
+
@sender_name = nil
|
|
99
|
+
if (v = sanitize(v)) && v.length > 0
|
|
100
|
+
@sender_name = v
|
|
101
|
+
else
|
|
102
|
+
raise USPSValidationError, "'#{v}' is not a valid string in #{__method__}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Used to override the validation of the customer address.
|
|
107
|
+
# If true, the address will be validated against WebTools.
|
|
108
|
+
# If false, the system will bypass the validation.
|
|
109
|
+
# <em>Optional</em>.
|
|
110
|
+
def address_validation=(v)
|
|
111
|
+
@address_validation = to_bool(v, true)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Used to select the format of the return label.
|
|
115
|
+
# <em>Optional</em>.
|
|
116
|
+
# * PDF <em>Default</em>.
|
|
117
|
+
# * TIF
|
|
118
|
+
def image_type=(v)
|
|
119
|
+
@image_type = validate_set_inclusion(v.to_s.upcase, IMAGE_TYPE, __method__)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Used to determine if the returns label request is coming from a
|
|
123
|
+
# merchant call center agent or an end customer.
|
|
124
|
+
# <b>Required</b>.
|
|
125
|
+
# [CallCenter]
|
|
126
|
+
# [Customer]
|
|
127
|
+
def call_center_or_self_service=(v)
|
|
128
|
+
@call_center_or_self_service = validate_set_inclusion(v, CALL_CENTER_OR_SELF_SERVICE, __method__)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Package information can be one of three types: RMA, Invoice or
|
|
132
|
+
# Order number. This will appear on the second label generated when
|
|
133
|
+
# the LabelFormat “TWO” is selected.
|
|
134
|
+
# <em>Optional</em>.
|
|
135
|
+
def packaging_information2=(v)
|
|
136
|
+
@packaging_information2 = validate_string_length(v, 15, __method__)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Package information can be one of three types: RMA, Invoice or
|
|
140
|
+
# Order number. This will appear on the generated label.
|
|
141
|
+
# <em>Optional</em>.
|
|
142
|
+
def packaging_information=(v)
|
|
143
|
+
@packaging_information = validate_string_length(v, 15, __method__)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Override address if more address information
|
|
147
|
+
# is needed or system cannot find address. If
|
|
148
|
+
# the address_override_notification value is
|
|
149
|
+
# true then any address error being passed from
|
|
150
|
+
# WebTools would be bypassed and a successful
|
|
151
|
+
# response will be sent.
|
|
152
|
+
# <b>Required</b>.
|
|
153
|
+
def address_override_notification=(v)
|
|
154
|
+
@address_validation = to_bool(v)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Insured amount of package.
|
|
158
|
+
def insurance_amount=(v)
|
|
159
|
+
@insurance_amount = nil
|
|
160
|
+
if (1..200).include?(v.to_f)
|
|
161
|
+
@insurance_amount = v
|
|
162
|
+
else
|
|
163
|
+
raise USPSValidationError, "#{__method__} must be a numerical value between 1 and 200, found value '#{v}'."
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Description of the merchandise.
|
|
168
|
+
# <em>Optional</em>.
|
|
169
|
+
def merchandise_description=(v)
|
|
170
|
+
@merchandise_description = validate_string_length(v, 255, __method__)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Service type of the label as specified in the merchant profile setup.
|
|
174
|
+
# <b>Required</b>.
|
|
175
|
+
# [044] (Parcel Return Service)
|
|
176
|
+
# [019] (Priority Mail Returns service)
|
|
177
|
+
# [596] (Priority Mail Returns service, Insurance <= $200)
|
|
178
|
+
# [020] (First-Class Package Return service)
|
|
179
|
+
# [597] (First-Class Package Return service, Insurance <= $200)
|
|
180
|
+
# [022] (Ground Return Service)
|
|
181
|
+
# [024] (PRS – Full Network)
|
|
182
|
+
# [017] (PRS – Full Network, Insurance <=$200)
|
|
183
|
+
# [018] (PRS – Full Network, Insurance >$200)
|
|
184
|
+
def service_type_code=(v)
|
|
185
|
+
@service_type_code = validate_set_inclusion(v, SERVICE_TYPE_CODE, __method__)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Size of the label.
|
|
189
|
+
# <b>Required</b>.
|
|
190
|
+
# * 4X6
|
|
191
|
+
# * Zebra-4X6
|
|
192
|
+
# * 4X4
|
|
193
|
+
# * 3X6
|
|
194
|
+
def label_definition=(v)
|
|
195
|
+
@label_definition = validate_set_inclusion(v, LABEL_DEFINITION, __method__)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def label_format
|
|
199
|
+
@label_format && LABEL_FORMAT[@label_format]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Format in which the label(s) will be printed.
|
|
203
|
+
# * null (“Instructions”)
|
|
204
|
+
# * NOI (“No Instructions”)
|
|
205
|
+
# * TWO (“Double Label”)
|
|
206
|
+
def label_format=(v)
|
|
207
|
+
@label_format = validate_set_inclusion(v, LABEL_FORMAT.keys, __method__)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# The intended recipient of the returned package (e.g. Returns Department).
|
|
211
|
+
# <em>Optional</em>.
|
|
212
|
+
def attention=(v)
|
|
213
|
+
@attention = validate_string_length(v, 38, __method__)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# The name of the company to which the package is being returned.
|
|
217
|
+
# <em>Optional</em>.
|
|
218
|
+
def company_name=(v)
|
|
219
|
+
@company_name = validate_string_length(v, 38, __method__)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# <b>Required</b>.
|
|
223
|
+
def merchant_account_id=(v)
|
|
224
|
+
@merchant_account_id = nil
|
|
225
|
+
if v.to_i > 0
|
|
226
|
+
@merchant_account_id = v
|
|
227
|
+
else
|
|
228
|
+
raise USPSValidationError, "#{__method__} must be a valid positive integer, found value '#{v}'."
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# <b>Required</b>.
|
|
233
|
+
def mid=(v)
|
|
234
|
+
@mid = nil
|
|
235
|
+
if v.to_s =~ /^\d{6,9}$/
|
|
236
|
+
@mid = v
|
|
237
|
+
else
|
|
238
|
+
raise USPSValidationError, "#{__method__} must be a valid integer between 6 and 9 digits in length, found value '#{v}'."
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Urbanization of customer returning the package (only applicable to Puerto Rico addresses).
|
|
243
|
+
# <em>Optional</em>.
|
|
244
|
+
def customer_urbanization=(v)
|
|
245
|
+
@customer_urbanization = validate_string_length(v, 32, __method__)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Name of customer returning package.
|
|
249
|
+
# <b>Required</b>.
|
|
250
|
+
def customer_name=(v)
|
|
251
|
+
@customer_name = validate_range(v, 1, 32, __method__)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Address of the customer returning the package.
|
|
255
|
+
# <b>Required</b>.
|
|
256
|
+
def customer_address1=(v)
|
|
257
|
+
@customer_address1 = validate_range(v, 1, 32, __method__)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Secondary address unit designator / number of customer
|
|
261
|
+
# returning the package. (such as an apartment or
|
|
262
|
+
# suite number, e.g. APT 202, STE 100)
|
|
263
|
+
def customer_address2=(v)
|
|
264
|
+
@customer_address2 = validate_range(v, 0, 32, __method__)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# City of customer returning the package.
|
|
268
|
+
# <b>Required</b>.
|
|
269
|
+
def customer_city=(v)
|
|
270
|
+
@customer_city = validate_range(v, 1, 32, __method__)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# State of customer returning the package.
|
|
274
|
+
# <b>Required</b>.
|
|
275
|
+
def customer_state=(v)
|
|
276
|
+
@customer_state = nil
|
|
277
|
+
if (v = sanitize(v)) && v =~ /^[a-zA-Z]{2}$/
|
|
278
|
+
@customer_state = v
|
|
279
|
+
else
|
|
280
|
+
raise USPSValidationError, "#{__method__} must be a String 2 chars in length, found value '#{v}'."
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Zipcode of customer returning the package.
|
|
285
|
+
# According to the USPS documentation, Zipcode is optional
|
|
286
|
+
# unless <tt>address_override_notification</tt> is true
|
|
287
|
+
# and <tt>address_validation</tt> is set to false.
|
|
288
|
+
# It's probably just easier to require Zipcodes.
|
|
289
|
+
# <b>Required</b>.
|
|
290
|
+
def customer_zipcode=(v)
|
|
291
|
+
@customer_zipcode = nil
|
|
292
|
+
if (v = sanitize(v))
|
|
293
|
+
v = v[0..4]
|
|
294
|
+
if v =~ /^\d{5}$/
|
|
295
|
+
@customer_zipcode = v
|
|
296
|
+
end
|
|
297
|
+
else
|
|
298
|
+
raise USPSValidationError, "#{__method__} must be a 5 digit number, found value '#{v}'."
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def verify_or_raise_required
|
|
303
|
+
%w(customer_name customer_address1 customer_city customer_state
|
|
304
|
+
customer_zipcode label_format label_definition service_type_code
|
|
305
|
+
call_center_or_self_service).each do |attr|
|
|
306
|
+
raise USPSMissingRequiredTagError.new(attr.camelize, attr) unless send(attr.to_sym)
|
|
307
|
+
end
|
|
308
|
+
# Safer than using inflection acroynms
|
|
309
|
+
raise USPSMissingRequiredTagError.new("MID", "mid") unless mid
|
|
310
|
+
raise USPSMissingRequiredTagError.new("MerchantAccountID", "merchant_account_id") unless merchant_account_id
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def to_xml
|
|
314
|
+
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
|
315
|
+
xml.ExternalReturnLabelRequest do
|
|
316
|
+
xml.CustomerName { xml.text(customer_name) }
|
|
317
|
+
xml.CustomerAddress1 { xml.text(customer_address1) }
|
|
318
|
+
xml.CustomerAddress2 { xml.text(customer_address2) } if customer_address2
|
|
319
|
+
xml.CustomerCity { xml.text(customer_city) }
|
|
320
|
+
xml.CustomerState { xml.text(customer_state) }
|
|
321
|
+
xml.CustomerZipCode { xml.text(customer_zipcode) } if customer_zipcode
|
|
322
|
+
xml.CustomerUrbanization { xml.text(customer_urbanization) } if customer_urbanization
|
|
323
|
+
|
|
324
|
+
xml.MerchantAccountID { xml.text(merchant_account_id) }
|
|
325
|
+
xml.MID { xml.text(mid) }
|
|
326
|
+
|
|
327
|
+
xml.SenderName { xml.text(sender_name) } if sender_name
|
|
328
|
+
xml.SenderEmail { xml.text(sender_email) } if sender_email
|
|
329
|
+
|
|
330
|
+
xml.RecipientName { xml.text(recipient_name) } if recipient_name
|
|
331
|
+
xml.RecipientEmail { xml.text(recipient_email) } if recipient_email
|
|
332
|
+
xml.RecipientBcc { xml.text(recipient_bcc) } if recipient_bcc
|
|
333
|
+
|
|
334
|
+
xml.LabelFormat { xml.text(label_format) } if label_format
|
|
335
|
+
xml.LabelDefinition { xml.text(label_definition) } if label_definition
|
|
336
|
+
xml.ServiceTypeCode { xml.text(service_type_code) } if service_type_code
|
|
337
|
+
|
|
338
|
+
xml.CompanyName { xml.text(company_name) } if company_name
|
|
339
|
+
xml.Attention { xml.text(attention) } if attention
|
|
340
|
+
|
|
341
|
+
xml.CallCenterOrSelfService { xml.text(call_center_or_self_service) }
|
|
342
|
+
|
|
343
|
+
xml.MerchandiseDescription { xml.text(merchandise_description) } if merchandise_description
|
|
344
|
+
xml.InsuranceAmount { xml.text(insurance_amount) } if insurance_amount
|
|
345
|
+
|
|
346
|
+
xml.AddressOverrideNotification { xml.text(!!address_override_notification) }
|
|
347
|
+
|
|
348
|
+
xml.PackageInformation { xml.text(packaging_information) } if packaging_information
|
|
349
|
+
xml.PackageInformation2 { xml.text(packaging_information2) } if packaging_information2
|
|
350
|
+
|
|
351
|
+
xml.ImageType { xml.text(image_type) } if image_type
|
|
352
|
+
xml.AddressValidation { xml.text(!!address_validation) }
|
|
353
|
+
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
xml_builder.to_xml
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
private
|
|
360
|
+
|
|
361
|
+
def to_bool(v, default = false)
|
|
362
|
+
v = v.to_s
|
|
363
|
+
if v =~ (/(true|yes|1)$/i)
|
|
364
|
+
true
|
|
365
|
+
elsif v =~ (/(false|no|0)$/i)
|
|
366
|
+
false
|
|
367
|
+
else
|
|
368
|
+
default
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def sanitize(v)
|
|
373
|
+
if v.is_a?(String)
|
|
374
|
+
v.strip!
|
|
375
|
+
v[0..CAP_STRING_LEN - 1]
|
|
376
|
+
else
|
|
377
|
+
nil
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def validate_range(v, min, max, meth)
|
|
382
|
+
if (v = sanitize(v).to_s) && ((min.to_i)..(max.to_i)).include?(v.length)
|
|
383
|
+
if v.length == 0
|
|
384
|
+
nil
|
|
385
|
+
else
|
|
386
|
+
v
|
|
387
|
+
end
|
|
388
|
+
else
|
|
389
|
+
raise USPSValidationError, "#{meth} must be a String between #{min.to_i} and #{max.to_i} chars in length, found value '#{v}'."
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def validate_string_length(s, max_len, meth)
|
|
394
|
+
if (s = sanitize(s)) && s.length <= max_len.to_i
|
|
395
|
+
s
|
|
396
|
+
else
|
|
397
|
+
raise USPSValidationError, "#{meth} must be a String no more than #{max_len} chars in length, found value '#{s}'."
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def validate_set_inclusion(v, set, meth)
|
|
402
|
+
if set.respond_to?(:include?) && set.include?(v)
|
|
403
|
+
v
|
|
404
|
+
else
|
|
405
|
+
raise USPSValidationError, "#{v} is not valid in #{meth}, try any of the following: #{(set.respond_to?(:join) && set.join(',')) || ''}"
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def validate_email(v, meth)
|
|
410
|
+
if (v = sanitize(v)) && v =~ USPS_EMAIL_REGEX
|
|
411
|
+
v
|
|
412
|
+
else
|
|
413
|
+
raise USPSValidationError, "'#{v}' is not a valid e-mail in #{meth}"
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|