ups-ruby 0.14.1 → 0.23.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -4
  3. data/lib/ups/builders/builder_base.rb +38 -24
  4. data/lib/ups/builders/international_invoice_builder.rb +12 -2
  5. data/lib/ups/builders/organisation_builder.rb +22 -0
  6. data/lib/ups/builders/package_builder.rb +69 -0
  7. data/lib/ups/builders/ship_confirm_builder.rb +40 -1
  8. data/lib/ups/builders/track_builder.rb +37 -0
  9. data/lib/ups/connection.rb +22 -0
  10. data/lib/ups/parsers/ship_accept_parser.rb +26 -0
  11. data/lib/ups/parsers/track_parser.rb +51 -0
  12. data/lib/ups/version.rb +2 -2
  13. data/lib/ups.rb +3 -0
  14. data/spec/stubs/rates_success_with_packaging_type.xml +187 -0
  15. data/spec/stubs/rates_success_with_packaging_type_and_dimensions.xml +185 -0
  16. data/spec/stubs/ship_accept_success_with_packaging_type.xml +69 -0
  17. data/spec/stubs/ship_accept_success_without_negotiated_price.xml +59 -0
  18. data/spec/stubs/ship_confirm_success_with_packaging_type.xml +33 -0
  19. data/spec/stubs/track_success.xml +105 -0
  20. data/spec/support/AccessRequest.xsd +12 -0
  21. data/spec/support/IF.xsd +352 -0
  22. data/spec/support/RateRequest.xsd +454 -1
  23. data/spec/support/ShipAcceptRequest.xsd +4 -15
  24. data/spec/support/ShipConfirmRequest.xsd +99 -367
  25. data/spec/support/shipping_options.rb +42 -0
  26. data/spec/support/xsd_validator.rb +1 -1
  27. data/spec/ups/builders/organisation_builder_spec.rb +51 -0
  28. data/spec/ups/builders/rate_builder_spec.rb +1 -0
  29. data/spec/ups/builders/ship_confirm_builder_spec.rb +2 -0
  30. data/spec/ups/connection/rates_standard_spec.rb +94 -1
  31. data/spec/ups/connection/ship_spec.rb +55 -1
  32. data/spec/ups/connection/track_spec.rb +47 -0
  33. metadata +15 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7fc9c4d705a7e2b8af7c6cac249f53e6907a91937c1a48beaa3304e1c39577c
4
- data.tar.gz: 20f50696539dcedf51233307ce029612a6087b61b4151146a1cc4be81c2f3cae
3
+ metadata.gz: 2a1733aa1ac4da9f295b01e07f0041d3de24a69df86e376a6e36f4bbd4114a45
4
+ data.tar.gz: 60cd995558755b4ae0a1cd5764d17f57502be21734fdbdd4ec81d353aab413b0
5
5
  SHA512:
6
- metadata.gz: 441652f86d95832337057fda51f3cf13ae8d2b24dc4fa58eb5f7f15b332cbd312c033c3bbe2836e2f6f36a83f02703c1585c8436e8aca3806a0e93323520bd1c
7
- data.tar.gz: 5d9e1509a67f97a54e1cb6e1363f1739945566c97d104b89fbbde30520110376c2c6d094fba614a43f6a8ef5ade6640ebe3c42d8e158fb011626287f392eecce
6
+ metadata.gz: 1a3e526f3189c142034e65f789cace831865e2f6acc7d43b4e2d3292b859d45f833bef80f3143246d6795914b876175b6dcddfd1b83f08e4b594f3d1103f663c
7
+ data.tar.gz: e4a123acb383f2ead01ed9a2a874a8570243bf8c14176348aeacb820a1a5d9c6a5c60d2f8de09e6b90f22654488cbf2014139b2bb660ab7ac111a246e1cf46cf
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ups-ruby (0.14.1)
4
+ ups-ruby (0.23.0)
5
5
  excon (~> 0.45, >= 0.45.3)
6
6
  insensitive_hash (~> 0.3.3)
7
7
  levenshtein-ffi (~> 1.1)
@@ -13,8 +13,8 @@ GEM
13
13
  codeclimate-test-reporter (1.0.5)
14
14
  simplecov
15
15
  docile (1.1.5)
16
- excon (0.69.1)
17
- ffi (1.11.3)
16
+ excon (0.83.0)
17
+ ffi (1.15.3)
18
18
  insensitive_hash (0.3.3)
19
19
  json (1.8.6)
20
20
  levenshtein-ffi (1.1.0)
@@ -23,7 +23,7 @@ GEM
23
23
  minitest (5.7.0)
24
24
  nokogiri (1.10.3)
25
25
  mini_portile2 (~> 2.4.0)
26
- ox (2.11.0)
26
+ ox (2.14.5)
27
27
  rake (12.0.0)
28
28
  simplecov (0.10.0)
29
29
  docile (~> 1.1.0)
@@ -69,10 +69,14 @@ module UPS
69
69
  # @param [String] action The UPS API Action requested
70
70
  # @param [String] option The UPS API Option
71
71
  # @return [void]
72
- def add_request(action, option)
72
+ def add_request(action, option, sub_version: nil)
73
73
  root << Element.new('Request').tap do |request|
74
74
  request << element_with_value('RequestAction', action)
75
75
  request << element_with_value('RequestOption', option)
76
+
77
+ unless sub_version.nil?
78
+ request << element_with_value('SubVersion', sub_version)
79
+ end
76
80
  end
77
81
  end
78
82
 
@@ -143,12 +147,7 @@ module UPS
143
147
  # @param [Hash] opts A Hash of data to build the requested section
144
148
  # @return [void]
145
149
  def add_package(opts = {})
146
- shipment_root << Element.new('Package').tap do |org|
147
- org << packaging_type
148
- org << element_with_value('Description', 'Rate')
149
- org << package_weight(opts[:weight], opts[:unit])
150
- org << package_dimensions(opts[:dimensions]) if opts[:dimensions]
151
- end
150
+ shipment_root << PackageBuilder.new('Package', opts).to_xml
152
151
  end
153
152
 
154
153
  # Adds a PaymentInformation section to the XML document being built
@@ -165,6 +164,12 @@ module UPS
165
164
  end
166
165
  end
167
166
 
167
+ def add_itemized_payment_information(ship_number)
168
+ shipment_charge << Element.new('BillShipper').tap do |bill_shipper|
169
+ bill_shipper << element_with_value('AccountNumber', ship_number)
170
+ end
171
+ end
172
+
168
173
  # Adds a RateInformation/NegotiatedRatesIndicator section to the XML
169
174
  # document being built
170
175
  #
@@ -186,6 +191,27 @@ module UPS
186
191
  end
187
192
  end
188
193
 
194
+ # Adds Direct Delivery Only indicator to the shipment service options
195
+ #
196
+ # @return [void]
197
+ def add_shipment_direct_delivery_only
198
+ shipment_service_options << Element.new('DirectDeliveryOnlyIndicator')
199
+ end
200
+
201
+ # Adds MasterCartonIndicator to the shipment
202
+ #
203
+ # @return [void]
204
+ def add_master_carton_indicator
205
+ shipment_root << Element.new('MasterCartonIndicator')
206
+ end
207
+
208
+ # Adds MasterCartonID to the shipment
209
+ #
210
+ # @return [void]
211
+ def add_master_carton_id(master_carton_id)
212
+ shipment_root << element_with_value('MasterCartonID', master_carton_id)
213
+ end
214
+
189
215
  # Returns a String representation of the XML document being built
190
216
  #
191
217
  # @return [String]
@@ -211,23 +237,11 @@ module UPS
211
237
  end
212
238
  end
213
239
 
214
- def packaging_type
215
- code_description 'PackagingType', '02', 'Customer Supplied'
216
- end
217
-
218
- def package_weight(weight, unit)
219
- Element.new('PackageWeight').tap do |org|
220
- org << unit_of_measurement(unit)
221
- org << element_with_value('Weight', weight)
222
- end
223
- end
224
-
225
- def package_dimensions(dimensions)
226
- Element.new('Dimensions').tap do |org|
227
- org << unit_of_measurement(dimensions[:unit])
228
- org << element_with_value('Length', dimensions[:length].to_s[0..8])
229
- org << element_with_value('Width', dimensions[:width].to_s[0..8])
230
- org << element_with_value('Height', dimensions[:height].to_s[0..8])
240
+ def shipment_charge
241
+ @shipment_charge ||= begin
242
+ element = Element.new('ShipmentCharge')
243
+ shipment_root << (Element.new('ItemizedPaymentInformation') << element)
244
+ element
231
245
  end
232
246
  end
233
247
 
@@ -23,13 +23,21 @@ module UPS
23
23
  end
24
24
 
25
25
  def invoice_number
26
- element_with_value('InvoiceNumber', opts[:invoice_number]) if opts[:invoice_number]
26
+ element_with_value('InvoiceNumber', opts[:invoice_number])
27
27
  end
28
28
 
29
29
  def invoice_date
30
30
  element_with_value('InvoiceDate', opts[:invoice_date])
31
31
  end
32
32
 
33
+ def terms_of_shipment
34
+ element_with_value('TermsOfShipment', opts[:terms_of_shipment])
35
+ end
36
+
37
+ def declaration_statement
38
+ element_with_value('DeclarationStatement', opts[:declaration_statement])
39
+ end
40
+
33
41
  def reason_for_export
34
42
  element_with_value('ReasonForExport', opts[:reason_for_export])
35
43
  end
@@ -62,8 +70,10 @@ module UPS
62
70
  def to_xml
63
71
  Element.new(name).tap do |international_form|
64
72
  international_form << form_type
65
- international_form << invoice_number
73
+ international_form << invoice_number if opts[:invoice_number]
66
74
  international_form << invoice_date
75
+ international_form << terms_of_shipment if opts[:terms_of_shipment]
76
+ international_form << declaration_statement if opts[:declaration_statement]
67
77
  international_form << reason_for_export
68
78
  international_form << currency_code
69
79
  international_form << freight_charge
@@ -58,6 +58,13 @@ module UPS
58
58
  element_with_value('TaxIdentificationNumber', opts[:sender_vat_number] || '')
59
59
  end
60
60
 
61
+ # Returns an XML representation of the email address of the company
62
+ #
63
+ # @return [Ox::Element] XML representation of email address
64
+ def email_address
65
+ element_with_value('EMailAddress', opts[:email_address].to_s[0..50])
66
+ end
67
+
61
68
  # Returns an XML representation of address
62
69
  #
63
70
  # @return [Ox::Element] An instance of {AddressBuilder} containing the
@@ -66,6 +73,19 @@ module UPS
66
73
  AddressBuilder.new(opts).to_xml
67
74
  end
68
75
 
76
+ # Returns an XML representation of vendor info (ioss number and more) of the company
77
+ #
78
+ # @return [Ox::Element] XML representation of sender_ioss_number
79
+ def vendor_info
80
+ ioss_vendor_collect_id = '0356'
81
+
82
+ Element.new('VendorInfo').tap do |vendor_info|
83
+ vendor_info << element_with_value('VendorCollectIDNumber', opts[:sender_ioss_number] || '')
84
+ vendor_info << element_with_value('VendorCollectIDTypeCode', ioss_vendor_collect_id)
85
+ vendor_info << element_with_value('ConsigneeType', '02')
86
+ end
87
+ end
88
+
69
89
  # Returns an XML representation of a UPS Organization
70
90
  #
71
91
  # @return [Ox::Element] XML representation of the current object
@@ -76,6 +96,8 @@ module UPS
76
96
  org << attention_name
77
97
  org << address
78
98
  org << tax_identification_number
99
+ org << email_address
100
+ org << vendor_info unless opts[:sender_ioss_number].to_s.empty?
79
101
  end
80
102
  end
81
103
  end
@@ -0,0 +1,69 @@
1
+ require 'ox'
2
+
3
+ module UPS
4
+ module Builders
5
+ class PackageBuilder < BuilderBase
6
+ include Ox
7
+
8
+ attr_accessor :name, :opts
9
+
10
+ def initialize(name, opts = {})
11
+ self.name = name
12
+ self.opts = opts
13
+ end
14
+
15
+ def packaging_type(packaging_options_hash)
16
+ code_description 'PackagingType', packaging_options_hash[:code], packaging_options_hash[:description]
17
+ end
18
+
19
+ def reference_number
20
+ Element.new('ReferenceNumber').tap do |org|
21
+ org << element_with_value('Code', opts[:reference_number][:type]) if opts[:reference_number][:type]
22
+ org << element_with_value('Value', opts[:reference_number][:value])
23
+ end
24
+ end
25
+
26
+ def reference_number_2
27
+ Element.new('ReferenceNumber').tap do |org|
28
+ org << element_with_value('Code', opts[:reference_number_2][:type]) if opts[:reference_number_2][:type]
29
+ org << element_with_value('Value', opts[:reference_number_2][:value])
30
+ end
31
+ end
32
+
33
+ def description
34
+ element_with_value('Description', 'Rate')
35
+ end
36
+
37
+ def package_weight(weight, unit)
38
+ Element.new('PackageWeight').tap do |org|
39
+ org << unit_of_measurement(unit)
40
+ org << element_with_value('Weight', weight)
41
+ end
42
+ end
43
+
44
+ def customer_supplied_packaging
45
+ { code: '02', description: 'Customer Supplied Package' }
46
+ end
47
+
48
+ def package_dimensions(dimensions)
49
+ Element.new('Dimensions').tap do |org|
50
+ org << unit_of_measurement(dimensions[:unit])
51
+ org << element_with_value('Length', dimensions[:length].to_s[0..8])
52
+ org << element_with_value('Width', dimensions[:width].to_s[0..8])
53
+ org << element_with_value('Height', dimensions[:height].to_s[0..8])
54
+ end
55
+ end
56
+
57
+ def to_xml
58
+ Element.new(name).tap do |product|
59
+ product << reference_number if opts[:reference_number]
60
+ product << reference_number_2 if opts[:reference_number_2]
61
+ product << packaging_type(opts[:packaging_type] || customer_supplied_packaging)
62
+ product << description
63
+ product << package_weight(opts[:weight], opts[:unit])
64
+ product << package_dimensions(opts[:dimensions]) if opts[:dimensions]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -16,7 +16,7 @@ module UPS
16
16
  def initialize
17
17
  super 'ShipmentConfirmRequest'
18
18
 
19
- add_request 'ShipConfirm', 'validate'
19
+ add_request 'ShipConfirm', 'validate', sub_version: '1807'
20
20
  end
21
21
 
22
22
  # Adds a LabelSpecification section to the XML document being built
@@ -54,6 +54,10 @@ module UPS
54
54
  service_description)
55
55
  end
56
56
 
57
+ def add_invoice_line_total(value, currency_code)
58
+ shipment_root << invoice_line_total(value, currency_code)
59
+ end
60
+
57
61
  # Adds Description to XML document being built
58
62
  #
59
63
  # @param [String] description The description for goods being sent
@@ -74,6 +78,31 @@ module UPS
74
78
  shipment_root << reference_number(opts[:code], opts[:value])
75
79
  end
76
80
 
81
+ def update_and_validate_for_worldwide_economy!
82
+ shipment_charge << element_with_value('Type', '01')
83
+
84
+ packages = document.locate('ShipmentConfirmRequest/Shipment/Package')
85
+
86
+ bill_shipper_account_number = document.locate(
87
+ 'ShipmentConfirmRequest/Shipment/ItemizedPaymentInformation/ShipmentCharge/BillShipper/AccountNumber/*'
88
+ ).first
89
+
90
+ unless packages.count == 1
91
+ raise InvalidAttributeError,
92
+ 'Worldwide Economy shipment must be single-piece'
93
+ end
94
+
95
+ unless packages.first.locate('PackagingType/Code/*').first == '02'
96
+ raise InvalidAttributeError,
97
+ 'Worldwide Economy shipment must use Customer Supplied Package'
98
+ end
99
+
100
+ unless bill_shipper_account_number.to_s.length > 0
101
+ raise InvalidAttributeError,
102
+ 'Worldwide Economy shipment must have "Bill Shipper" Itemized Payment Information'
103
+ end
104
+ end
105
+
77
106
  private
78
107
 
79
108
  def gif?(string)
@@ -107,6 +136,16 @@ module UPS
107
136
  'Code' => code.to_s,
108
137
  'Value' => value.to_s)
109
138
  end
139
+
140
+ def invoice_line_total(value, currency_code)
141
+ multi_valued('InvoiceLineTotal',
142
+ 'CurrencyCode' => currency_code.to_s,
143
+ 'MonetaryValue' => value.to_s)
144
+ end
145
+
146
+ def service_code
147
+ document.locate('ShipmentConfirmRequest/Shipment/Service/Code/*').first
148
+ end
110
149
  end
111
150
  end
112
151
  end
@@ -0,0 +1,37 @@
1
+ require 'ox'
2
+
3
+ module UPS
4
+ module Builders
5
+ # The {TrackBuilder} class builds UPS XML Track Objects.
6
+ #
7
+ # @author Stephan van Diepen
8
+ # @since 0.17.1
9
+ class TrackBuilder < BuilderBase
10
+ include Ox
11
+
12
+ # Initializes a new {TrackBuilder} object
13
+ #
14
+ def initialize
15
+ super 'TrackRequest'
16
+ end
17
+
18
+ # Adds an TrackingNumber to the XML document being built
19
+ # according to user inputs
20
+ #
21
+ # @return [void]
22
+ def add_tracking_number(number)
23
+ root << element_with_value('TrackingNumber', number)
24
+ end
25
+
26
+ # Adds an OptionCode to the XML document being built
27
+ # according to user inputs
28
+ #
29
+ # @return [void]
30
+ def add_option_code(option_code)
31
+ root << Element.new('Request').tap do |request|
32
+ request << element_with_value('RequestOption', option_code)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -21,6 +21,7 @@ module UPS
21
21
  SHIP_CONFIRM_PATH = '/ups.app/xml/ShipConfirm'
22
22
  SHIP_ACCEPT_PATH = '/ups.app/xml/ShipAccept'
23
23
  ADDRESS_PATH = '/ups.app/xml/XAV'
24
+ TRACK_PATH = '/ups.app/xml/Track'
24
25
 
25
26
  DEFAULT_PARAMS = {
26
27
  test_mode: false
@@ -79,6 +80,27 @@ module UPS
79
80
  make_accept_request(accept_builder)
80
81
  end
81
82
 
83
+ # Makes a request to Track the status for a shipment.
84
+ #
85
+ # A pre-configured {Builders::TrackBuilder} object can be passed as the first
86
+ # option or a block yielded to configure a new {Builders::TrackBuilder}
87
+ # object.
88
+ #
89
+ # @param [Builders::TrackBuilder] track_builder A pre-configured
90
+ # {Builders::TrackBuilder} object to use
91
+ # @yield [track_builder] A TrackBuilder object for configuring
92
+ # the shipment information sent
93
+ def track(track_builder = nil)
94
+ if track_builder.nil? && block_given?
95
+ track_builder = UPS::Builders::TrackBuilder.new
96
+ yield track_builder
97
+ end
98
+
99
+ response = get_response(TRACK_PATH, track_builder.to_xml)
100
+
101
+ UPS::Parsers::TrackParser.new(response.body)
102
+ end
103
+
82
104
  private
83
105
 
84
106
  def build_url(path)
@@ -42,8 +42,34 @@ module UPS
42
42
  [UPS::Models::PackageResult.new(package_results)]
43
43
  end
44
44
 
45
+ def master_carton_id
46
+ shipment_results[:MasterCartonID]
47
+ end
48
+
49
+ def total_charge
50
+ return shipment_charge unless negotiated_rate
51
+
52
+ negotiated_rate
53
+ end
54
+
55
+ def negotiated_rate
56
+ negotiated_rate_response && negotiated_rate_response[:NetSummaryCharges][:GrandTotal][:MonetaryValue].to_f
57
+ end
58
+
59
+ def currency_code
60
+ shipment_results[:ShipmentCharges][:TotalCharges][:CurrencyCode]
61
+ end
62
+
45
63
  private
46
64
 
65
+ def negotiated_rate_response
66
+ shipment_results[:NegotiatedRates]
67
+ end
68
+
69
+ def shipment_charge
70
+ shipment_results[:ShipmentCharges][:TotalCharges][:MonetaryValue].to_f
71
+ end
72
+
47
73
  def form_graphic
48
74
  shipment_results[:Form]
49
75
  end