simple_shipping 0.4.6

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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.metrics +6 -0
  4. data/.rspec +4 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.simplecov +43 -0
  8. data/Gemfile +26 -0
  9. data/Gemfile.lock +201 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.markdown +207 -0
  12. data/Rakefile +68 -0
  13. data/VERSION +1 -0
  14. data/coverage/.resultset.json +1579 -0
  15. data/lib/colorized_text.rb +34 -0
  16. data/lib/simple_shipping.rb +27 -0
  17. data/lib/simple_shipping/abstract.rb +11 -0
  18. data/lib/simple_shipping/abstract/builder.rb +47 -0
  19. data/lib/simple_shipping/abstract/client.rb +111 -0
  20. data/lib/simple_shipping/abstract/model.rb +40 -0
  21. data/lib/simple_shipping/abstract/request.rb +27 -0
  22. data/lib/simple_shipping/abstract/response.rb +26 -0
  23. data/lib/simple_shipping/address.rb +22 -0
  24. data/lib/simple_shipping/contact.rb +24 -0
  25. data/lib/simple_shipping/demo.rb +9 -0
  26. data/lib/simple_shipping/demo/base.rb +71 -0
  27. data/lib/simple_shipping/demo/fedex.rb +46 -0
  28. data/lib/simple_shipping/demo/ups.rb +68 -0
  29. data/lib/simple_shipping/exceptions.rb +40 -0
  30. data/lib/simple_shipping/fedex.rb +14 -0
  31. data/lib/simple_shipping/fedex/client.rb +41 -0
  32. data/lib/simple_shipping/fedex/package_builder.rb +24 -0
  33. data/lib/simple_shipping/fedex/party_builder.rb +50 -0
  34. data/lib/simple_shipping/fedex/request.rb +54 -0
  35. data/lib/simple_shipping/fedex/response.rb +5 -0
  36. data/lib/simple_shipping/fedex/shipment_builder.rb +123 -0
  37. data/lib/simple_shipping/fedex/shipment_request.rb +14 -0
  38. data/lib/simple_shipping/fedex/shipment_response.rb +12 -0
  39. data/lib/simple_shipping/package.rb +43 -0
  40. data/lib/simple_shipping/party.rb +21 -0
  41. data/lib/simple_shipping/shipment.rb +42 -0
  42. data/lib/simple_shipping/ups.rb +24 -0
  43. data/lib/simple_shipping/ups/client.rb +46 -0
  44. data/lib/simple_shipping/ups/package_builder.rb +101 -0
  45. data/lib/simple_shipping/ups/party_builder.rb +38 -0
  46. data/lib/simple_shipping/ups/request.rb +27 -0
  47. data/lib/simple_shipping/ups/response.rb +63 -0
  48. data/lib/simple_shipping/ups/ship_accept_request.rb +21 -0
  49. data/lib/simple_shipping/ups/ship_accept_response.rb +6 -0
  50. data/lib/simple_shipping/ups/ship_client.rb +70 -0
  51. data/lib/simple_shipping/ups/ship_confirm_request.rb +22 -0
  52. data/lib/simple_shipping/ups/ship_confirm_response.rb +6 -0
  53. data/lib/simple_shipping/ups/shipment_builder.rb +66 -0
  54. data/lib/simple_shipping/ups/shipment_request.rb +22 -0
  55. data/lib/simple_shipping/ups/shipment_response.rb +6 -0
  56. data/lib/simple_shipping/ups/void_client.rb +50 -0
  57. data/lib/simple_shipping/ups/void_request.rb +42 -0
  58. data/lib/simple_shipping/ups/void_response.rb +6 -0
  59. data/lib/tasks/demo.rake +58 -0
  60. data/script/ups_certification.rb +140 -0
  61. data/simple_shipping.gemspec +168 -0
  62. data/spec/fixtures/fedex_shipment_request.soap.xml.erb +85 -0
  63. data/spec/fixtures/fedex_shipment_response.soap.xml.erb +182 -0
  64. data/spec/fixtures/ups_shipment_request.soap.xml.erb +88 -0
  65. data/spec/fixtures/ups_shipment_response.soap.xml.erb +58 -0
  66. data/spec/fixtures/ups_shipment_response_with_faked_label_data.soap.xml.erb +54 -0
  67. data/spec/fixtures/ups_void_request.soap.xml.erb +29 -0
  68. data/spec/fixtures/ups_void_response.soap.xml.erb +21 -0
  69. data/spec/lib/simple_shipping/address_spec.rb +19 -0
  70. data/spec/lib/simple_shipping/contact_spec.rb +28 -0
  71. data/spec/lib/simple_shipping/exceptions_spec.rb +58 -0
  72. data/spec/lib/simple_shipping/fedex/package_builder_spec.rb +5 -0
  73. data/spec/lib/simple_shipping/fedex/party_builder_spec.rb +5 -0
  74. data/spec/lib/simple_shipping/fedex/response/shipment_reponse_spec.rb +5 -0
  75. data/spec/lib/simple_shipping/fedex/response_spec.rb +5 -0
  76. data/spec/lib/simple_shipping/fedex/shipment_builder_spec.rb +23 -0
  77. data/spec/lib/simple_shipping/package_spec.rb +32 -0
  78. data/spec/lib/simple_shipping/party_spec.rb +18 -0
  79. data/spec/lib/simple_shipping/shipment_spec.rb +35 -0
  80. data/spec/lib/simple_shipping/ups/package_builder_spec.rb +26 -0
  81. data/spec/lib/simple_shipping/ups/party_builder_spec.rb +47 -0
  82. data/spec/lib/simple_shipping/ups/response/shipment_response_spec.rb +5 -0
  83. data/spec/lib/simple_shipping/ups/response_spec.rb +33 -0
  84. data/spec/lib/simple_shipping/ups/shipment_builder_spec.rb +19 -0
  85. data/spec/requests/fedex_spec.rb +47 -0
  86. data/spec/requests/ups_spec.rb +75 -0
  87. data/spec/spec_helper.rb +47 -0
  88. data/spec/support/custom_matchers/basic_matcher.rb +13 -0
  89. data/spec/support/custom_matchers/have_attribute_matcher.rb +22 -0
  90. data/spec/support/custom_matchers/have_default_value_matcher.rb +26 -0
  91. data/spec/support/custom_matchers/have_errors_on_matcher.rb +23 -0
  92. data/spec/support/custom_matchers/validate_inclusion_of_matcher.rb +37 -0
  93. data/spec/support/custom_matchers/validate_presence_of_matcher.rb +24 -0
  94. data/spec/support/custom_matchers/validate_submodel_matcher.rb +44 -0
  95. data/spec/support/shared_behaviours/builders_behaviour.rb +9 -0
  96. data/spec/support/shared_behaviours/responses_behaviour.rb +10 -0
  97. data/tmp/metric_fu/_data/20131210.yml +9964 -0
  98. data/wsdl/fedex/ship_service_v10.wsdl +5566 -0
  99. data/wsdl/ups/Ship.wsdl +120 -0
  100. data/wsdl/ups/Void.wsdl +58 -0
  101. metadata +308 -0
@@ -0,0 +1,14 @@
1
+ module SimpleShipping::Fedex
2
+ # The model that represents shipment request to FedEx.
3
+ class ShipmentRequest < Request
4
+ def initialize(credentials, shipment)
5
+ super
6
+ @type = :process_shipment
7
+ end
8
+
9
+ # :nodoc:
10
+ def response_class
11
+ ShipmentResponse
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module SimpleShipping::Fedex
2
+ # A wrapper for UPS ShipmentResponse.
3
+ class ShipmentResponse < Response
4
+ # Get the label as abstract64 encoded data
5
+ # response.label_image_base64 # => "odGqk/KmgLaawV..."
6
+ # This can be used directly in an HTML image tag with
7
+ # src="data:image/gif;base64,..."
8
+ def label_image_base64
9
+ value_of(:process_shipment_reply, :completed_shipment_detail, :completed_package_details, :label, :parts, :image)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ module SimpleShipping
2
+ # Represents a package which should be sent from {SimpleShipping::Party shipper}
3
+ # to {SimpleShipping::Party recipient}.
4
+ #
5
+ # == Attributes:
6
+ # * _dimenstion_units_ (:in, :cm). Default is :in.
7
+ # * _weight_units_ (:kg, :lb). Default is :lb.
8
+ # * _legth_ (in dimension units)
9
+ # * _width_ (in dimension units)
10
+ # * _height_ (in dimension units)
11
+ # * _weight_ (in weight units)
12
+ # * _packaging_type_
13
+ #
14
+ # == Packaging type values:
15
+ # * :envelope
16
+ # * :your
17
+ # * :tube
18
+ # * :pak
19
+ # * :box
20
+ # * :box_10kg
21
+ # * :box_25kg
22
+ class Package < Abstract::Model
23
+ attr_accessor :length, :width, :height, :dimension_units
24
+ attr_accessor :weight, :weight_units, :packaging_type
25
+ attr_accessor :insured_value, :declared_value
26
+
27
+ validates_presence_of :length, :width, :height, :dimension_units, :if => :custom_package?
28
+ validates_presence_of :weight, :weight_units
29
+
30
+ validates_inclusion_of :weight_units , :in => [:kg, :lb]
31
+ validates_inclusion_of :dimension_units, :in => [:in, :cm], :if => :custom_package?
32
+ validates_inclusion_of :packaging_type , :in => [:envelope, :pak, :tube, :your, :box, :box_10kg, :box_25kg]
33
+
34
+ set_default_values :packaging_type => :your,
35
+ :weight_units => :lb,
36
+ :dimension_units => :in
37
+
38
+ # Whether the package is a custom package.
39
+ def custom_package?
40
+ packaging_type == :your
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleShipping
2
+ # Party is a person or company who takes a part in shipment process.
3
+ # Party is used to represent a shipper or a recipient.
4
+ #
5
+ # == Attributes
6
+ # * _contact_ (instance of {SimpleShipping::Contact})
7
+ # * _address_ (instance of {SimpleShipping::Address})
8
+ # * _account_number_ (optional, but in some cases required)
9
+ #
10
+ # If one of attributes is missed an appropriate exception will be raised
11
+ # when you build a request.
12
+ class Party < Abstract::Model
13
+ attr_accessor :contact,
14
+ :address,
15
+ :account_number
16
+
17
+ validates_presence_of :contact, :address
18
+ validates_submodel :address, :as => SimpleShipping::Address
19
+ validates_submodel :contact, :as => SimpleShipping::Contact
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ module SimpleShipping
2
+ # Represents a shipment.
3
+ #
4
+ # == Attributes:
5
+ # * _shipper_ (an instance of {SimpleShipping::Party}
6
+ # * _recipient_ (an instance of {SimpleShipping::Party}
7
+ # * _package_ (an instance of {SimpleShipping::Package}
8
+ # * _payor_ (:shipper, :recipient). Default value is :shipper
9
+ class Shipment < Abstract::Model
10
+ attr_accessor :shipper,
11
+ :recipient,
12
+ :package,
13
+ :payor
14
+
15
+ set_default_values :payor => :shipper
16
+
17
+ validates_presence_of :shipper, :recipient, :package, :payor
18
+ validates_inclusion_of :payor, :in => [:shipper, :recipient]
19
+ validates_submodel :shipper , :as => SimpleShipping::Party
20
+ validates_submodel :recipient, :as => SimpleShipping::Party
21
+ validates_submodel :package , :as => SimpleShipping::Package
22
+ validate :validate_payor_account_number
23
+
24
+ # Account number of payor.
25
+ def payor_account_number
26
+ case payor
27
+ when :shipper
28
+ shipper.account_number if shipper.respond_to?(:account_number)
29
+ when :recipient
30
+ recipient.account_number if recipient.respond_to?(:account_number)
31
+ end
32
+ end
33
+
34
+ # Validate presence of payor account number.
35
+ #
36
+ # @return [void]
37
+ def validate_payor_account_number
38
+ errors.add(:abstract, "Payor account number is missing") unless payor_account_number
39
+ end
40
+ private :validate_payor_account_number
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ # Namespace for UPS provider.
2
+ module SimpleShipping::Ups
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Client
6
+ autoload :ShipClient
7
+ autoload :VoidClient
8
+
9
+ autoload :Request
10
+ autoload :Response
11
+ autoload :PackageBuilder
12
+ autoload :PartyBuilder
13
+ autoload :ShipmentBuilder
14
+
15
+ autoload :ShipConfirmResponse
16
+ autoload :ShipAcceptResponse
17
+ autoload :ShipmentResponse
18
+ autoload :VoidResponse
19
+
20
+ autoload :ShipConfirmRequest
21
+ autoload :ShipAcceptRequest
22
+ autoload :ShipmentRequest
23
+ autoload :VoidRequest
24
+ end
@@ -0,0 +1,46 @@
1
+ module SimpleShipping::Ups
2
+ # Abstract class for all UPS clients.
3
+ # The problem with UPS is that its WSDL imports schemas. However schema imports are not supported
4
+ # by Savon as by v.2.1.0. See: https://github.com/savonrb/wasabi/issues/1
5
+ # Because of this we have to manually:
6
+ # 1. Assign additional namespaces
7
+ # 2. Switch to :qualified :elemen_form_default
8
+ # (to have all requests elements prepended with namespace
9
+ # if its namespace is not specified explicitly)
10
+ # 3. Explicitly prepend namespace to all elements which not belong to WSDL target namespace
11
+ # i.e. UPSSecurity, Request/RequestOptions etc
12
+ #
13
+ class Client < SimpleShipping::Abstract::Client
14
+
15
+ # @param [Hash] options Savon client options
16
+ def client_options(options = {})
17
+ super.deep_merge(
18
+ :element_form_default => :qualified,
19
+ :namespaces => {
20
+ 'xmlns:upss' => "http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0",
21
+ 'xmlns:common' => "http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0",
22
+ },
23
+ :soap_header => soap_header
24
+ )
25
+ end
26
+ protected :client_options
27
+
28
+ # @return [Hash] of SOAP envelope header contents
29
+ def soap_header
30
+ {
31
+ 'upss:UPSSecurity' => {
32
+ 'upss:UsernameToken' => {
33
+ 'upss:Username' => @credentials.username,
34
+ 'upss:Password' => @credentials.password,
35
+ :order! => ['upss:Username', 'upss:Password']
36
+ },
37
+ 'upss:ServiceAccessToken' => {
38
+ 'upss:AccessLicenseNumber' => @credentials.access_license_number
39
+ },
40
+ :order! => ['upss:UsernameToken', 'upss:ServiceAccessToken']
41
+ }
42
+ }
43
+ end
44
+ protected :soap_header
45
+ end
46
+ end
@@ -0,0 +1,101 @@
1
+ # Builds hash for Savon which represents {Package package}.
2
+ module SimpleShipping::Ups
3
+ # Builds hash structure for Savon that represents package element in UPS's API.
4
+ class PackageBuilder < SimpleShipping::Abstract::Builder
5
+ # Mapping for UPS packaging types
6
+ # Not all UPS values listed here in order to provide common interface with FedEx.
7
+ PACKAGING_TYPES = {
8
+ :envelope => '01', # letter
9
+ :your => '02', # customer supplied
10
+ :tube => '03', # tube
11
+ :pak => '04', # UPS Packaging
12
+ :box => '2b', # medium box
13
+ :box_10kg => '25',
14
+ :box_10kg => '24'
15
+ }
16
+
17
+ # Mapping for UPS weight units.
18
+ WEIGHT_UNITS = {
19
+ :kg => 'KGS',
20
+ :lb => 'LBS'
21
+ }
22
+
23
+ # Mapping for UPS dimension units.
24
+ DIMENSION_UNITS = {
25
+ :in => 'IN',
26
+ :cm => 'CM'
27
+ }
28
+
29
+ # Custom package order.
30
+ CUSTOM_PACKAGE_ORDER = %w(Packaging PackageServiceOptions Dimensions PackageWeight)
31
+
32
+ # Standard package order.
33
+ STANDARD_PACKAGE_ORDER = %w(Packaging PackageServiceOptions PackageWeight)
34
+
35
+ # Build basic skeleton for package element. It can be customized by
36
+ # overriding some subelements.
37
+ #
38
+ # @return [Hash]
39
+ def base_package
40
+ base = {
41
+ 'Packaging' => {
42
+ 'Code' => PACKAGING_TYPES[@model.packaging_type]
43
+ },
44
+ 'PackageWeight' => {
45
+ 'UnitOfMeasurement' => {
46
+ 'Code' => WEIGHT_UNITS[@model.weight_units]
47
+ },
48
+ 'Weight' => @model.weight,
49
+ :order! => ['UnitOfMeasurement', 'Weight']
50
+ },
51
+ 'PackageServiceOptions' => {}
52
+ }
53
+
54
+ if @model.insured_value
55
+ base['PackageServiceOptions']['InsuredValue'] = {
56
+ 'CurrencyCode' => 'USD',
57
+ 'MonetaryValue' => @model.insured_value
58
+ }
59
+ end
60
+
61
+ if @model.declared_value
62
+ base['PackageServiceOptions']['DeclaredValue'] = {
63
+ 'CurrencyCode' => 'USD',
64
+ 'MonetaryValue' => @model.declared_value
65
+ }
66
+ end
67
+
68
+ base
69
+ end
70
+
71
+ # Build a hash from a custom {Package package} which will be used by Savon.
72
+ # A custom package requires specification of LWH dimensions.
73
+ def custom_package
74
+ base_package.tap do |package|
75
+ package['Dimensions'] = {
76
+ 'UnitOfMeasurement' => {
77
+ 'Code' => DIMENSION_UNITS[@model.dimension_units].clone
78
+ },
79
+ 'Length' => @model.length,
80
+ 'Width' => @model.width,
81
+ 'Height' => @model.height,
82
+ :order! => ['UnitOfMeasurement', 'Length', 'Width', 'Height']
83
+ }
84
+ package[:order!] = CUSTOM_PACKAGE_ORDER.clone
85
+ end
86
+ end
87
+
88
+ # Build a hash from a standard {Package package} which will be used by Savon.
89
+ # A standard package requires no specification of LWH dimensions.
90
+ def standard_package
91
+ base_package.tap do |package|
92
+ package[:order!] = STANDARD_PACKAGE_ORDER.clone
93
+ end
94
+ end
95
+
96
+ # Build the package, whether custom or standard.
97
+ def build
98
+ @model.custom_package? ? custom_package : standard_package
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,38 @@
1
+ module SimpleShipping::Ups
2
+ # Knows how to convert {Party} model to SOAP element for UPS.
3
+ class PartyBuilder < SimpleShipping::Abstract::Builder
4
+ # Builds a hash for Savon which represents {Party party}.
5
+ def build
6
+ contact = @model.contact
7
+ {'Name' => (contact.person_name || contact.company_name),
8
+ 'Phone' => {'Number' => contact.phone_number},
9
+ 'ShipperNumber' => @model.account_number,
10
+ 'Address' => build_address,
11
+ :order! => ['Name', 'Phone', 'ShipperNumber', 'Address']
12
+ }
13
+ end
14
+
15
+ # Build address element.
16
+ #
17
+ # @return [Hash]
18
+ def build_address
19
+ addr = @model.address
20
+ {'AddressLine' => [addr.street_line, addr.street_line_2, addr.street_line_3].compact,
21
+ 'City' => addr.city,
22
+ 'StateProvinceCode' => addr.state_code,
23
+ 'PostalCode' => addr.postal_code,
24
+ 'CountryCode' => addr.country_code,
25
+ :order! => ['AddressLine', 'City', 'StateProvinceCode', 'PostalCode', 'CountryCode']
26
+ }
27
+ end
28
+
29
+ # Validate presence of account_number.
30
+ #
31
+ # @return [void]
32
+ def validate
33
+ if @opts[:shipper] && !@model.account_number
34
+ raise SimpleShipping::ValidationError.new("account_number is required for party who is shipper")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ module SimpleShipping::Ups
2
+ # Builds complete request for UPS
3
+ class Request < SimpleShipping::Abstract::Request
4
+ # Value for <common:RequestOption> XML element in request.
5
+ REQUEST_OPTION = 'nonvalidate'
6
+
7
+ # Define label parameters according to UPS's API.
8
+ #
9
+ # @return [Hash]
10
+ def label_specification
11
+ { 'LabelImageFormat' => {'Code' => 'GIF'},
12
+ 'LabelStockSize' => {
13
+ 'Height' => '6',
14
+ 'Width' => '4',
15
+ :order! => ['Height', 'Width']
16
+ },
17
+ :order! => ['LabelImageFormat', 'LabelStockSize']
18
+ }
19
+ end
20
+
21
+ # The class of the response in the same name space.
22
+ def response_class
23
+ self.class.name.sub(/Request/, 'Response').constantize
24
+ end
25
+ private :response_class
26
+ end
27
+ end
@@ -0,0 +1,63 @@
1
+ module SimpleShipping::Ups
2
+ # Response from UPS.
3
+ class Response < SimpleShipping::Abstract::Response
4
+ # Digest what can be used to get a label.
5
+ #
6
+ # @return [String]
7
+ def digest
8
+ value_of(:shipment_results, :shipment_digest)
9
+ end
10
+
11
+ # Unique shipment ID returned by UPS.
12
+ #
13
+ # @return [Strig]
14
+ def shipment_identification_number
15
+ value_of(:shipment_results, :shipment_identification_number)
16
+ end
17
+
18
+ # Get package tracking number to look for delivery process on UPS site.
19
+ #
20
+ # @return [String] tracking number
21
+ def tracking_number
22
+ value_of(:shipment_results, :package_results, :tracking_number)
23
+ end
24
+
25
+ # Get the label as base64 encoded data
26
+ # response.label_image_base64 # => "odGqk/KmgLaawV..."
27
+ # This can be used directly in an HTML image tag with
28
+ # src="data:image/gif;base64,..."
29
+ def label_image_base64
30
+ value_of(:shipment_results, :package_results, :shipping_label, :graphic_image)
31
+ end
32
+
33
+ # Label image.
34
+ #
35
+ # @return [String] binary
36
+ def label_html
37
+ value = value_of(:shipment_results, :package_results, :shipping_label, :html_image)
38
+ Base64.decode64(value) if value
39
+ end
40
+
41
+ # Receipt.
42
+ #
43
+ # @return [String] binary
44
+ def receipt_html
45
+ value = value_of(:shipment_results, :control_log_receipt, :graphic_image)
46
+ Base64.decode64(value) if value
47
+ end
48
+
49
+ # Fetch the value of an XML attribute at the path specified as an array
50
+ # of node names but appends the implicit namespace on to the front of the
51
+ # path.
52
+ def value_of(*path)
53
+ super(*path.unshift(name_token))
54
+ end
55
+
56
+ # All UPS requests are namespaced within the same name of the class by
57
+ # convention.
58
+ def name_token
59
+ self.class.name.split('::').last.underscore.to_sym
60
+ end
61
+ private :name_token
62
+ end
63
+ end