simple_shipping 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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