reactive_shipping 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,26 @@
|
|
1
|
+
module ReactiveShipping #:nodoc:
|
2
|
+
|
3
|
+
class ExternalReturnLabelResponse < Response
|
4
|
+
attr_reader :carrier # symbol
|
5
|
+
attr_reader :carrier_name # string
|
6
|
+
attr_reader :tracking_number # string
|
7
|
+
attr_reader :return_label # string
|
8
|
+
attr_reader :postal_routing # string
|
9
|
+
|
10
|
+
def initialize(success, message, params = {}, options = {})
|
11
|
+
@carrier = options[:carrier].parameterize.to_sym
|
12
|
+
@carrier_name = options[:carrier]
|
13
|
+
@return_label = options[:return_label]
|
14
|
+
@tracking_number = options[:tracking_number]
|
15
|
+
@postal_routing = options[:postal_routing]
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_exception?
|
20
|
+
@status == :exception
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method(:exception?, :has_exception?)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module ReactiveShipping #:nodoc:
|
2
|
+
class Location
|
3
|
+
ADDRESS_TYPES = %w(residential commercial po_box)
|
4
|
+
|
5
|
+
ATTRIBUTE_ALIASES = {
|
6
|
+
name: [:name],
|
7
|
+
country: [:country_code, :country],
|
8
|
+
postal_code: [:postal_code, :zip, :postal],
|
9
|
+
province: [:province_code, :state_code, :territory_code, :region_code, :province, :state, :territory, :region],
|
10
|
+
city: [:city, :town],
|
11
|
+
address1: [:address1, :address, :street],
|
12
|
+
address2: [:address2],
|
13
|
+
address3: [:address3],
|
14
|
+
phone: [:phone, :phone_number],
|
15
|
+
fax: [:fax, :fax_number],
|
16
|
+
email: [:email],
|
17
|
+
address_type: [:address_type],
|
18
|
+
company_name: [:company, :company_name],
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
attr_reader :options,
|
22
|
+
:country,
|
23
|
+
:postal_code,
|
24
|
+
:province,
|
25
|
+
:city,
|
26
|
+
:name,
|
27
|
+
:address1,
|
28
|
+
:address2,
|
29
|
+
:address3,
|
30
|
+
:phone,
|
31
|
+
:fax,
|
32
|
+
:email,
|
33
|
+
:address_type,
|
34
|
+
:company_name
|
35
|
+
|
36
|
+
alias_method :zip, :postal_code
|
37
|
+
alias_method :postal, :postal_code
|
38
|
+
alias_method :state, :province
|
39
|
+
alias_method :territory, :province
|
40
|
+
alias_method :region, :province
|
41
|
+
alias_method :company, :company_name
|
42
|
+
|
43
|
+
def initialize(options = {})
|
44
|
+
@country = if options[:country].nil? || options[:country].is_a?(ActiveUtils::Country)
|
45
|
+
options[:country]
|
46
|
+
else
|
47
|
+
ActiveUtils::Country.find(options[:country])
|
48
|
+
end
|
49
|
+
|
50
|
+
@postal_code = options[:postal_code] || options[:postal] || options[:zip]
|
51
|
+
@province = options[:province] || options[:state] || options[:territory] || options[:region]
|
52
|
+
@city = options[:city]
|
53
|
+
@name = options[:name]
|
54
|
+
@address1 = options[:address1]
|
55
|
+
@address2 = options[:address2]
|
56
|
+
@address3 = options[:address3]
|
57
|
+
@phone = options[:phone]
|
58
|
+
@fax = options[:fax]
|
59
|
+
@email = options[:email]
|
60
|
+
@company_name = options[:company_name] || options[:company]
|
61
|
+
|
62
|
+
self.address_type = options[:address_type]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.from(object, options = {})
|
66
|
+
return object if object.is_a?(ReactiveShipping::Location)
|
67
|
+
|
68
|
+
attributes = {}
|
69
|
+
|
70
|
+
hash_access = object.respond_to?(:[])
|
71
|
+
|
72
|
+
ATTRIBUTE_ALIASES.each do |attribute, aliases|
|
73
|
+
aliases.detect do |sym|
|
74
|
+
value = object[sym] if hash_access
|
75
|
+
if !value &&
|
76
|
+
object.respond_to?(sym) &&
|
77
|
+
(!hash_access || !Hash.public_instance_methods.include?(sym))
|
78
|
+
value = object.send(sym)
|
79
|
+
end
|
80
|
+
|
81
|
+
attributes[attribute] = value if value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
attributes.delete(:address_type) unless ADDRESS_TYPES.include?(attributes[:address_type].to_s)
|
86
|
+
|
87
|
+
new(attributes.update(options))
|
88
|
+
end
|
89
|
+
|
90
|
+
def country_code(format = :alpha2)
|
91
|
+
@country.nil? ? nil : @country.code(format).value
|
92
|
+
end
|
93
|
+
|
94
|
+
def residential?
|
95
|
+
@address_type == 'residential'
|
96
|
+
end
|
97
|
+
|
98
|
+
def commercial?
|
99
|
+
@address_type == 'commercial'
|
100
|
+
end
|
101
|
+
|
102
|
+
def po_box?
|
103
|
+
@address_type == 'po_box'
|
104
|
+
end
|
105
|
+
|
106
|
+
def unknown?
|
107
|
+
country_code == 'ZZ'
|
108
|
+
end
|
109
|
+
|
110
|
+
def address_type=(value)
|
111
|
+
return unless value.present?
|
112
|
+
raise ArgumentError.new("address_type must be one of #{ADDRESS_TYPES.join(', ')}") unless ADDRESS_TYPES.include?(value.to_s)
|
113
|
+
@address_type = value.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_hash
|
117
|
+
{
|
118
|
+
country: country_code,
|
119
|
+
postal_code: postal_code,
|
120
|
+
province: province,
|
121
|
+
city: city,
|
122
|
+
name: name,
|
123
|
+
address1: address1,
|
124
|
+
address2: address2,
|
125
|
+
address3: address3,
|
126
|
+
phone: phone,
|
127
|
+
fax: fax,
|
128
|
+
email: email,
|
129
|
+
address_type: address_type,
|
130
|
+
company_name: company_name
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_s
|
135
|
+
prettyprint.gsub(/\n/, ' ')
|
136
|
+
end
|
137
|
+
|
138
|
+
def prettyprint
|
139
|
+
chunks = [@name, @address1, @address2, @address3]
|
140
|
+
chunks << [@city, @province, @postal_code].reject(&:blank?).join(', ')
|
141
|
+
chunks << @country
|
142
|
+
chunks.reject(&:blank?).join("\n")
|
143
|
+
end
|
144
|
+
|
145
|
+
def inspect
|
146
|
+
string = prettyprint
|
147
|
+
string << "\nPhone: #{@phone}" unless @phone.blank?
|
148
|
+
string << "\nFax: #{@fax}" unless @fax.blank?
|
149
|
+
string << "\nEmail: #{@email}" unless @email.blank?
|
150
|
+
string
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the postal code as a properly formatted Zip+4 code, e.g. "77095-2233"
|
154
|
+
def zip_plus_4
|
155
|
+
"#{$1}-#{$2}" if /(\d{5})-?(\d{4})/ =~ @postal_code
|
156
|
+
end
|
157
|
+
|
158
|
+
def address2_and_3
|
159
|
+
[address2, address3].reject(&:blank?).join(", ")
|
160
|
+
end
|
161
|
+
|
162
|
+
def ==(other)
|
163
|
+
to_hash == other.to_hash
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module ReactiveShipping #:nodoc:
|
2
|
+
class Package
|
3
|
+
cattr_accessor :default_options
|
4
|
+
attr_reader :options, :value, :currency
|
5
|
+
|
6
|
+
# Package.new(100, [10, 20, 30], :units => :metric)
|
7
|
+
# Package.new(Measured::Weight.new(100, :g), [10, 20, 30].map {|m| Length.new(m, :centimetres)})
|
8
|
+
# Package.new(100.grams, [10, 20, 30].map(&:centimetres))
|
9
|
+
def initialize(grams_or_ounces, dimensions, options = {})
|
10
|
+
options = @@default_options.update(options) if @@default_options
|
11
|
+
options.symbolize_keys!
|
12
|
+
@options = options
|
13
|
+
|
14
|
+
@dimensions = [dimensions].flatten.reject(&:nil?)
|
15
|
+
|
16
|
+
imperial = (options[:units] == :imperial)
|
17
|
+
|
18
|
+
weight_imperial = dimensions_imperial = imperial if options.include?(:units)
|
19
|
+
|
20
|
+
if options.include?(:weight_units)
|
21
|
+
weight_imperial = (options[:weight_units] == :imperial)
|
22
|
+
end
|
23
|
+
|
24
|
+
if options.include?(:dim_units)
|
25
|
+
dimensions_imperial = (options[:dim_units] == :imperial)
|
26
|
+
end
|
27
|
+
|
28
|
+
@weight_unit_system = weight_imperial ? :imperial : :metric
|
29
|
+
@dimensions_unit_system = dimensions_imperial ? :imperial : :metric
|
30
|
+
|
31
|
+
@weight = attribute_from_metric_or_imperial(grams_or_ounces, Measured::Weight, @weight_unit_system, :grams, :ounces)
|
32
|
+
|
33
|
+
if @dimensions.blank?
|
34
|
+
zero_length = Measured::Length.new(0, (dimensions_imperial ? :inches : :centimetres))
|
35
|
+
@dimensions = [zero_length] * 3
|
36
|
+
else
|
37
|
+
process_dimensions
|
38
|
+
end
|
39
|
+
|
40
|
+
@value = Package.cents_from(options[:value])
|
41
|
+
@currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency))
|
42
|
+
@cylinder = (options[:cylinder] || options[:tube]) ? true : false
|
43
|
+
@gift = options[:gift] ? true : false
|
44
|
+
@oversized = options[:oversized] ? true : false
|
45
|
+
@unpackaged = options[:unpackaged] ? true : false
|
46
|
+
end
|
47
|
+
|
48
|
+
def unpackaged?
|
49
|
+
@unpackaged
|
50
|
+
end
|
51
|
+
|
52
|
+
def oversized?
|
53
|
+
@oversized
|
54
|
+
end
|
55
|
+
|
56
|
+
def cylinder?
|
57
|
+
@cylinder
|
58
|
+
end
|
59
|
+
alias_method :tube?, :cylinder?
|
60
|
+
|
61
|
+
def gift?
|
62
|
+
@gift
|
63
|
+
end
|
64
|
+
|
65
|
+
def ounces(options = {})
|
66
|
+
weight(options).convert_to(:oz).value.to_f
|
67
|
+
end
|
68
|
+
alias_method :oz, :ounces
|
69
|
+
|
70
|
+
def grams(options = {})
|
71
|
+
weight(options).convert_to(:g).value.to_f
|
72
|
+
end
|
73
|
+
alias_method :g, :grams
|
74
|
+
|
75
|
+
def pounds(options = {})
|
76
|
+
weight(options).convert_to(:lb).value.to_f
|
77
|
+
end
|
78
|
+
alias_method :lb, :pounds
|
79
|
+
alias_method :lbs, :pounds
|
80
|
+
|
81
|
+
def kilograms(options = {})
|
82
|
+
weight(options).convert_to(:kg).value.to_f
|
83
|
+
end
|
84
|
+
alias_method :kg, :kilograms
|
85
|
+
alias_method :kgs, :kilograms
|
86
|
+
|
87
|
+
def inches(measurement = nil)
|
88
|
+
@inches ||= @dimensions.map { |m| m.convert_to(:in).value.to_f }
|
89
|
+
measurement.nil? ? @inches : measure(measurement, @inches)
|
90
|
+
end
|
91
|
+
alias_method :in, :inches
|
92
|
+
|
93
|
+
def centimetres(measurement = nil)
|
94
|
+
@centimetres ||= @dimensions.map { |m| m.convert_to(:cm).value.to_f }
|
95
|
+
measurement.nil? ? @centimetres : measure(measurement, @centimetres)
|
96
|
+
end
|
97
|
+
alias_method :cm, :centimetres
|
98
|
+
|
99
|
+
def weight(options = {})
|
100
|
+
case options[:type]
|
101
|
+
when nil, :actual
|
102
|
+
@weight
|
103
|
+
when :volumetric, :dimensional
|
104
|
+
@volumetric_weight ||= begin
|
105
|
+
m = Measured::Weight.new((centimetres(:box_volume) / 6.0), :grams)
|
106
|
+
@weight_unit_system == :imperial ? m.convert_to(:oz) : m
|
107
|
+
end
|
108
|
+
when :billable
|
109
|
+
[weight, weight(:type => :volumetric)].max
|
110
|
+
end
|
111
|
+
end
|
112
|
+
alias_method :mass, :weight
|
113
|
+
|
114
|
+
def self.cents_from(money)
|
115
|
+
return nil if money.nil?
|
116
|
+
if money.respond_to?(:cents)
|
117
|
+
return money.cents
|
118
|
+
else
|
119
|
+
case money
|
120
|
+
when Float
|
121
|
+
(money * 100).round
|
122
|
+
when String
|
123
|
+
money =~ /\./ ? (money.to_f * 100).round : money.to_i
|
124
|
+
else
|
125
|
+
money.to_i
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def attribute_from_metric_or_imperial(obj, klass, unit_system, metric_unit, imperial_unit)
|
133
|
+
if obj.is_a?(klass)
|
134
|
+
return obj
|
135
|
+
else
|
136
|
+
return klass.new(obj, (unit_system == :imperial ? imperial_unit : metric_unit))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def measure(measurement, ary)
|
141
|
+
case measurement
|
142
|
+
when Integer then ary[measurement]
|
143
|
+
when :x, :max, :length, :long then ary[2]
|
144
|
+
when :y, :mid, :width, :wide then ary[1]
|
145
|
+
when :z, :min, :height, :depth, :high, :deep then ary[0]
|
146
|
+
when :girth, :around, :circumference
|
147
|
+
self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1])
|
148
|
+
when :volume then self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume, ary)
|
149
|
+
when :box_volume then ary[0] * ary[1] * ary[2]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def process_dimensions
|
154
|
+
@dimensions = @dimensions.map do |l|
|
155
|
+
attribute_from_metric_or_imperial(l, Measured::Length, @dimensions_unit_system, :centimetres, :inches)
|
156
|
+
end.sort
|
157
|
+
# [1,2] => [1,1,2]
|
158
|
+
# [5] => [5,5,5]
|
159
|
+
# etc..
|
160
|
+
2.downto(@dimensions.length) do |_n|
|
161
|
+
@dimensions.unshift(@dimensions[0])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ReactiveShipping #:nodoc:
|
2
|
+
class PackageItem
|
3
|
+
attr_reader :sku, :hs_code, :value, :name, :weight, :quantity, :options
|
4
|
+
|
5
|
+
def initialize(name, grams_or_ounces, value, quantity, options = {})
|
6
|
+
@name = name
|
7
|
+
|
8
|
+
imperial = (options[:units] == :imperial)
|
9
|
+
|
10
|
+
@unit_system = imperial ? :imperial : :metric
|
11
|
+
|
12
|
+
@weight = grams_or_ounces
|
13
|
+
@weight = Measured::Weight.new(grams_or_ounces, (@unit_system == :imperial ? :oz : :g)) unless @weight.is_a?(Measured::Weight)
|
14
|
+
|
15
|
+
@value = Package.cents_from(value)
|
16
|
+
@quantity = quantity > 0 ? quantity : 1
|
17
|
+
|
18
|
+
@sku = options[:sku]
|
19
|
+
@hs_code = options[:hs_code]
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def weight(options = {})
|
24
|
+
case options[:type]
|
25
|
+
when nil, :actual
|
26
|
+
@weight
|
27
|
+
when :volumetric, :dimensional
|
28
|
+
@volumetric_weight ||= begin
|
29
|
+
m = Measured::Weight.new((centimetres(:box_volume) / 6.0), :grams)
|
30
|
+
@unit_system == :imperial ? m.in_ounces : m
|
31
|
+
end
|
32
|
+
when :billable
|
33
|
+
[weight, weight(:type => :volumetric)].max
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias_method :mass, :weight
|
37
|
+
|
38
|
+
def ounces(options = {})
|
39
|
+
weight(options).convert_to(:oz).value
|
40
|
+
end
|
41
|
+
alias_method :oz, :ounces
|
42
|
+
|
43
|
+
def grams(options = {})
|
44
|
+
weight(options).convert_to(:g).value
|
45
|
+
end
|
46
|
+
alias_method :g, :grams
|
47
|
+
|
48
|
+
def pounds(options = {})
|
49
|
+
weight(options).convert_to(:lb).value
|
50
|
+
end
|
51
|
+
alias_method :lb, :pounds
|
52
|
+
alias_method :lbs, :pounds
|
53
|
+
|
54
|
+
def kilograms(options = {})
|
55
|
+
weight(options).convert_to(:kg).value
|
56
|
+
end
|
57
|
+
alias_method :kg, :kilograms
|
58
|
+
alias_method :kgs, :kilograms
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module ReactiveShipping
|
2
|
+
|
3
|
+
# Class representing a shipping option with estimated price.
|
4
|
+
#
|
5
|
+
# @!attribute origin
|
6
|
+
# The origin of the shipment
|
7
|
+
# @return [ReactiveShipping::Location]
|
8
|
+
#
|
9
|
+
# @!attribute destination
|
10
|
+
# The destination of the shipment
|
11
|
+
# @return [ReactiveShipping::Location]
|
12
|
+
#
|
13
|
+
# @!attribute package_rates
|
14
|
+
# A list of rates for all the packages in the shipment
|
15
|
+
# @return [Array<{:rate => Integer, :package => ReactiveShipping::Package}>]
|
16
|
+
#
|
17
|
+
# @!attribute carrier
|
18
|
+
# The name of the carrier (e.g. 'USPS', 'FedEx')
|
19
|
+
# @return [String]
|
20
|
+
# @see ReactiveShipping::Carrier.name
|
21
|
+
#
|
22
|
+
# @!attribute service_name
|
23
|
+
# The name of the shipping service (e.g. 'First Class Ground')
|
24
|
+
# @return [String]
|
25
|
+
#
|
26
|
+
# @!attribute service_code
|
27
|
+
# The code of the shipping service
|
28
|
+
# @return [String]
|
29
|
+
#
|
30
|
+
# @!attribute description
|
31
|
+
# Public description of the shipping service (e.g. '2 days delivery')
|
32
|
+
# @return [String]
|
33
|
+
#
|
34
|
+
# @!attribute shipping_date
|
35
|
+
# The date on which the shipment will be expected. Normally, this means that the
|
36
|
+
# delivery date range can only be promised if the shipment is handed over on or
|
37
|
+
# before this date.
|
38
|
+
# @return [Date]
|
39
|
+
#
|
40
|
+
# @!attribute delivery_date
|
41
|
+
# The date on which the shipment will be delivered. This is usually only available
|
42
|
+
# for express shipments; in other cases a {#delivery_range} is given instead.
|
43
|
+
# @return [Date]
|
44
|
+
#
|
45
|
+
# @!attribute delivery_range
|
46
|
+
# The minimum and maximum date of when the shipment is expected to be delivered
|
47
|
+
# @return [Array<Date>]
|
48
|
+
#
|
49
|
+
# @!attribute currency
|
50
|
+
# ISO4217 currency code of the quoted rate estimates (e.g. `CAD`, `EUR`, or `USD`)
|
51
|
+
# @return [String]
|
52
|
+
# @see http://en.wikipedia.org/wiki/ISO_4217
|
53
|
+
#
|
54
|
+
# @!attribute negotiated_rate
|
55
|
+
# The negotiated rate in cents
|
56
|
+
# @return [Integer]
|
57
|
+
#
|
58
|
+
# @!attribute compare_price
|
59
|
+
# The comparable price in cents
|
60
|
+
# @return [Integer]
|
61
|
+
#
|
62
|
+
# @!attribute phone_required
|
63
|
+
# Specifies if a phone number is required for the shipping rate
|
64
|
+
# @return [Boolean]
|
65
|
+
#
|
66
|
+
# @!attribute insurance_price
|
67
|
+
# The price of insurance in cents
|
68
|
+
# @return [Integer]
|
69
|
+
#
|
70
|
+
# @!attribute delivery_category
|
71
|
+
# The general classification of the delivery method
|
72
|
+
# @return [String]
|
73
|
+
#
|
74
|
+
# @!attribute shipment_options
|
75
|
+
# Additional priced options bundled with the given rate estimate with price in cents
|
76
|
+
# @return [Array<{ code: String, price: Integer }>]
|
77
|
+
#
|
78
|
+
# @!attribute charge_items
|
79
|
+
# Breakdown of a shipping rate's price with amounts in cents.
|
80
|
+
# @return [Array<{ group: String, code: String, name: String, description: String, amount: Integer }>]
|
81
|
+
#
|
82
|
+
class RateEstimate
|
83
|
+
attr_accessor :origin, :destination, :package_rates,
|
84
|
+
:carrier, :service_name, :service_code, :description,
|
85
|
+
:shipping_date, :delivery_date, :delivery_range,
|
86
|
+
:currency, :negotiated_rate, :insurance_price,
|
87
|
+
:estimate_reference, :expires_at, :pickup_time,
|
88
|
+
:compare_price, :phone_required, :delivery_category,
|
89
|
+
:shipment_options, :charge_items, :messages
|
90
|
+
|
91
|
+
def initialize(origin, destination, carrier, service_name, options = {})
|
92
|
+
self.origin, self.destination, self.carrier, self.service_name = origin, destination, carrier, service_name
|
93
|
+
self.service_code = options[:service_code]
|
94
|
+
self.description = options[:description]
|
95
|
+
self.estimate_reference = options[:estimate_reference]
|
96
|
+
self.pickup_time = options[:pickup_time]
|
97
|
+
self.expires_at = options[:expires_at]
|
98
|
+
if options[:package_rates]
|
99
|
+
self.package_rates = options[:package_rates].map { |p| p.update(:rate => Package.cents_from(p[:rate])) }
|
100
|
+
else
|
101
|
+
self.package_rates = Array(options[:packages]).map { |p| {:package => p} }
|
102
|
+
end
|
103
|
+
self.total_price = options[:total_price]
|
104
|
+
self.negotiated_rate = options[:negotiated_rate]
|
105
|
+
self.compare_price = options[:compare_price]
|
106
|
+
self.phone_required = options[:phone_required]
|
107
|
+
self.currency = options[:currency]
|
108
|
+
self.delivery_range = options[:delivery_range]
|
109
|
+
self.shipping_date = options[:shipping_date]
|
110
|
+
self.delivery_date = @delivery_range.last
|
111
|
+
self.insurance_price = options[:insurance_price]
|
112
|
+
self.delivery_category = options[:delivery_category]
|
113
|
+
self.shipment_options = options[:shipment_options] || []
|
114
|
+
self.charge_items = options[:charge_items] || []
|
115
|
+
self.messages = options[:messages] || []
|
116
|
+
end
|
117
|
+
|
118
|
+
# The total price of the shipments in cents.
|
119
|
+
# @return [Integer]
|
120
|
+
def total_price
|
121
|
+
@total_price || @package_rates.sum { |pr| pr[:rate] }
|
122
|
+
rescue NoMethodError
|
123
|
+
raise ArgumentError.new("RateEstimate must have a total_price set, or have a full set of valid package rates.")
|
124
|
+
end
|
125
|
+
alias_method :price, :total_price
|
126
|
+
|
127
|
+
# Adds a package to this rate estimate
|
128
|
+
# @param package [ReactiveShipping::Package] The package to add.
|
129
|
+
# @param rate [#cents, Float, String, nil] The rate for this package. This is only required if
|
130
|
+
# there is no total price for this shipment
|
131
|
+
# @return [self]
|
132
|
+
def add(package, rate = nil)
|
133
|
+
cents = Package.cents_from(rate)
|
134
|
+
raise ArgumentError.new("New packages must have valid rate information since this RateEstimate has no total_price set.") if cents.nil? and total_price.nil?
|
135
|
+
@package_rates << {:package => package, :rate => cents}
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# The list of packages for which rate estimates are given.
|
140
|
+
# @return [Array<ReactiveShipping::Package>]
|
141
|
+
def packages
|
142
|
+
package_rates.map { |p| p[:package] }
|
143
|
+
end
|
144
|
+
|
145
|
+
# The number of packages for which rate estimates are given.
|
146
|
+
# @return [Integer]
|
147
|
+
def package_count
|
148
|
+
package_rates.length
|
149
|
+
end
|
150
|
+
|
151
|
+
protected
|
152
|
+
|
153
|
+
def delivery_range=(delivery_range)
|
154
|
+
@delivery_range = delivery_range ? delivery_range.map { |date| date_for(date) }.compact : []
|
155
|
+
end
|
156
|
+
|
157
|
+
def total_price=(total_price)
|
158
|
+
@total_price = Package.cents_from(total_price)
|
159
|
+
end
|
160
|
+
|
161
|
+
def negotiated_rate=(negotiated_rate)
|
162
|
+
@negotiated_rate = negotiated_rate ? Package.cents_from(negotiated_rate) : nil
|
163
|
+
end
|
164
|
+
|
165
|
+
def compare_price=(compare_price)
|
166
|
+
@compare_price = compare_price ? Package.cents_from(compare_price) : nil
|
167
|
+
end
|
168
|
+
|
169
|
+
def currency=(currency)
|
170
|
+
@currency = ActiveUtils::CurrencyCode.standardize(currency)
|
171
|
+
end
|
172
|
+
|
173
|
+
def phone_required=(phone_required)
|
174
|
+
@phone_required = !!phone_required
|
175
|
+
end
|
176
|
+
|
177
|
+
def shipping_date=(shipping_date)
|
178
|
+
@shipping_date = date_for(shipping_date)
|
179
|
+
end
|
180
|
+
|
181
|
+
def insurance_price=(insurance_price)
|
182
|
+
@insurance_price = Package.cents_from(insurance_price)
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# Returns a Date object for a given input
|
188
|
+
# @param date [String, Date, Time, DateTime, ...] The object to infer a date from.
|
189
|
+
# @return [Date, nil] The Date object absed on the input, or `nil` if no date
|
190
|
+
# could be determined.
|
191
|
+
def date_for(date)
|
192
|
+
date && Date.strptime(date.to_s, "%Y-%m-%d")
|
193
|
+
rescue ArgumentError
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|