usps-ruby-client 0.1.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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +41 -0
  6. data/.rubocop_todo.yml +0 -0
  7. data/.travis.yml +6 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +8 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +77 -0
  12. data/Rakefile +9 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/data/api/address-information-api.htm +8229 -0
  16. data/lib/data/api/domestic-mail-service-standards-api.htm +11016 -0
  17. data/lib/data/api/evs-international-label-api.htm +33291 -0
  18. data/lib/data/api/evs-label-api.htm +18895 -0
  19. data/lib/data/api/hold-for-pickup-facilities-lookup-api.htm +5530 -0
  20. data/lib/data/api/package-pickup-api.htm +19157 -0
  21. data/lib/data/api/rate-calculator-api.htm +16355 -0
  22. data/lib/data/api/scan-api.htm +5934 -0
  23. data/lib/data/api/service-delivery-calculator-get-locations-api.htm +12548 -0
  24. data/lib/data/api/track-and-confirm-api.htm +21288 -0
  25. data/lib/data/api/uspsreturnslabel-api.htm +9474 -0
  26. data/lib/helpers/erubis_helper.rb +10 -0
  27. data/lib/tasks/api.rake +246 -0
  28. data/lib/usps-ruby-client.rb +30 -0
  29. data/lib/usps/api/endpoints.rb +70 -0
  30. data/lib/usps/api/endpoints/carrier_pickup_schedule.rb +117 -0
  31. data/lib/usps/api/endpoints/city_state_lookup.rb +50 -0
  32. data/lib/usps/api/endpoints/e_vs_express_mail_intl.rb +359 -0
  33. data/lib/usps/api/endpoints/e_vs_first_class_mail_intl.rb +323 -0
  34. data/lib/usps/api/endpoints/e_vs_priority_mail_intl.rb +366 -0
  35. data/lib/usps/api/endpoints/e_vsgxg_get_label.rb +366 -0
  36. data/lib/usps/api/endpoints/e_vsi_cancel.rb +55 -0
  37. data/lib/usps/api/endpoints/hfp_facility_info.rb +71 -0
  38. data/lib/usps/api/endpoints/intl_rate_v2.rb +135 -0
  39. data/lib/usps/api/endpoints/pts_email.rb +73 -0
  40. data/lib/usps/api/endpoints/pts_pod.rb +86 -0
  41. data/lib/usps/api/endpoints/pts_rre.rb +72 -0
  42. data/lib/usps/api/endpoints/ptst_pod.rb +75 -0
  43. data/lib/usps/api/endpoints/rate_v4.rb +140 -0
  44. data/lib/usps/api/endpoints/scan.rb +121 -0
  45. data/lib/usps/api/endpoints/track_v2.rb +62 -0
  46. data/lib/usps/api/endpoints/usps_returns_label.rb +149 -0
  47. data/lib/usps/api/endpoints/verify.rb +75 -0
  48. data/lib/usps/api/endpoints/zip_code_lookup.rb +69 -0
  49. data/lib/usps/api/error.rb +6 -0
  50. data/lib/usps/api/errors/too_many_requests_error.rb +22 -0
  51. data/lib/usps/api/errors/usps_error.rb +30 -0
  52. data/lib/usps/api/templates/_build_xml.erb +7 -0
  53. data/lib/usps/api/templates/_options.erb +6 -0
  54. data/lib/usps/api/templates/_throw_argument_error.erb +3 -0
  55. data/lib/usps/api/templates/endpoints.erb +22 -0
  56. data/lib/usps/api/templates/method.erb +49 -0
  57. data/lib/usps/api/templates/method_spec.erb +22 -0
  58. data/lib/usps/api/xml.rb +8 -0
  59. data/lib/usps/client.rb +37 -0
  60. data/lib/usps/config.rb +50 -0
  61. data/lib/usps/faraday/connection.rb +34 -0
  62. data/lib/usps/faraday/request.rb +39 -0
  63. data/lib/usps/faraday/response/raise_error.rb +16 -0
  64. data/lib/usps/logger.rb +14 -0
  65. data/lib/usps/version.rb +4 -0
  66. data/usps-ruby-client.gemspec +46 -0
  67. metadata +307 -0
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module ErubisHelper
3
+ def self.tabs(amount = 1)
4
+ (0..amount).map { "\t" }.join
5
+ end
6
+
7
+ def self.spaces(amount = 1)
8
+ (0..amount).map { "\s" }.join
9
+ end
10
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+ require 'amazing_print'
3
+ require 'nokogiri'
4
+ require 'erubis'
5
+ require 'active_support'
6
+ require 'active_support/core_ext'
7
+ require 'pry'
8
+ require 'helpers/erubis_helper'
9
+
10
+ namespace :usps do
11
+ namespace :api do
12
+ task :update do
13
+ def dig_set(obj, keys, value)
14
+ key = keys.first
15
+ if keys.length == 1
16
+ obj[key] = value
17
+ else
18
+ obj[key] = {} unless obj[key]
19
+ dig_set(obj[key], keys.slice(1..-1), value)
20
+ end
21
+ end
22
+
23
+ def table_array_to_hash_array(columns, rows)
24
+ rows.map do |row|
25
+ row_data = {}
26
+ columns.each_with_index do |k, i|
27
+ row_data[k.to_s.gsub(/[[:space:]]+/, ' ').strip] =
28
+ row[i].is_a? String ? row[i].to_s.gsub(/[[:space:]]+/, ' ').strip : row[i]
29
+ end
30
+ row_data
31
+ end
32
+ end
33
+
34
+ def standardize_table_hash_array(arr)
35
+ arr.map do |option|
36
+ next if option['Tag Name'].split('/').pop.strip.casecmp('userid').zero?
37
+
38
+ {
39
+ type: option['Type'],
40
+ name: option['Tag Name'].split('/').pop.strip,
41
+ required: option['Occurs'] == 'Required',
42
+ description: option['Description'],
43
+ }
44
+ end.compact
45
+ end
46
+
47
+ def table_hash_array_to_shape(arr)
48
+ shape = {}
49
+
50
+ nesting = []
51
+ arr[0][:type] = '(Alias)' unless arr[0][:type].casecmp('(alias)').zero?
52
+ arr.each do |tag|
53
+ if tag[:type].casecmp('(alias)').zero?
54
+ if nesting.last == tag[:name]
55
+ nesting.pop
56
+ else
57
+ nesting.push(tag[:name])
58
+ dig_set(shape, nesting, tag.merge(children: {}))
59
+ end
60
+ elsif tag[:type].casecmp('(group)').zero?
61
+ if nesting.last == tag[:name]
62
+ nesting.pop(2)
63
+ else
64
+ nesting.push(:children)
65
+ nesting.push(tag[:name])
66
+ dig_set(shape, nesting, tag.merge(children: {}))
67
+ end
68
+ elsif nesting.length.positive?
69
+ nesting_with_children = nesting + [:children]
70
+ dig_set(shape, nesting_with_children,
71
+ shape.dig(*nesting_with_children).merge({ tag[:name] => tag.merge(children: {}) }))
72
+ else
73
+ shape[tag[:name]] = tag
74
+ end
75
+ end
76
+ shape
77
+ end
78
+
79
+ def pp_xml(xml = '')
80
+ doc = Nokogiri.XML(xml) do |config|
81
+ config.default_xml.noblanks
82
+ end
83
+ doc.to_xml(indent: 2)
84
+ end
85
+
86
+ @failed_methods = []
87
+
88
+ @endpoint_files = []
89
+
90
+ def parse_doc(path)
91
+ doc = Nokogiri::HTML(File.open(path))
92
+
93
+ table_of_contents = {}
94
+ raw_table_of_contents = doc.search('sdt[docparttype="Table of Contents"] p a').map do |link|
95
+ full_title = link.content.tr("\n", ' ')
96
+ next if full_title.blank?
97
+
98
+ full_version = full_title.match(/^[\d.]+/)[0]
99
+ major, minor, tiny = full_version.split('.')
100
+
101
+ title = full_title.match(/\s+[A-Za-z\s\t.:]*\s+/)[0].gsub(/[[:space:]]+/,
102
+ ' ').strip.chomp('.')
103
+
104
+ data = {
105
+ # full_title: full_title,
106
+ # link: link['href'],
107
+ anchor: link['href'].split('#')[1],
108
+ version: full_version,
109
+ major: major,
110
+ minor: minor,
111
+ tiny: tiny,
112
+ title: title,
113
+ }
114
+
115
+ table_of_contents[major] ||= []
116
+ table_of_contents[major] << data
117
+
118
+ data
119
+ end
120
+
121
+ data = table_of_contents.map do |section_number, section|
122
+ section_data = {}
123
+ section.each do |subsection|
124
+ header_node = doc.search("a[name=#{subsection[:anchor]}]")[0].parent
125
+ header_node = header_node.next_element if header_node.content.blank? && header_node.next_element
126
+
127
+ content_node = if header_node.next_element && header_node.next_element.content.present?
128
+ header_node.next_element
129
+ elsif header_node.parent.next_element && header_node.parent.next_element.content.present?
130
+ header_node.parent.next_element
131
+ else
132
+ header_node.parent.parent.next_element
133
+ end
134
+ next unless content_node # For section 1
135
+
136
+ section_data[:link] = subsection[:link]
137
+
138
+ if subsection[:version] == "#{section_number}.0"
139
+ section_data[:title] = subsection[:title]
140
+ elsif subsection[:title] == 'Overview'
141
+ section_data[:description] = content_node.content.tr("\n", ' ')
142
+ elsif subsection[:title] == 'API Signature'
143
+ columns, *rows = content_node.search('tr').map do |tr|
144
+ tr.search('td').map do |td|
145
+ td.content.gsub(/\n\s/, '').strip
146
+ end
147
+ end
148
+ section_data[:signature] = table_array_to_hash_array(columns, rows)
149
+ section_data[:url] =
150
+ "#{section_data[:signature][0]['Scheme']}#{section_data[:signature][0]['Host']}#{section_data[:signature][0]['Path'].chomp('?')}"
151
+ section_data[:group] =
152
+ section_data[:signature][0]['API'].sub('API=', '').gsub(/[[:space:]]+/,
153
+ ' ').strip
154
+ # ap section_data[:signature]
155
+ # ap section_data[:signature].map{|s| s[]}
156
+ # elsif subsection[:title] == "Request Descriptions"
157
+ # columns, *rows = content_node.search('tr').map{|tr| tr.search('td').map{|td| td.content.gsub(/\n\s/, '').strip}}
158
+ # section_data[:request_descriptions] = table_hash_array_to_shape(standardize_table_hash_array(table_array_to_hash_array(columns, rows)))
159
+ elsif subsection[:title] == 'Sample Request'
160
+ title, xml = content_node.content.gsub(/[[:space:]]+/, ' ').strip.split('<', 2)
161
+ xml = pp_xml("<#{xml}")
162
+ section_data[:sample_request] = <<~XML
163
+ #{title}
164
+ #{xml}
165
+ XML
166
+ elsif subsection[:title] == 'Response Descriptions' || subsection[:title] == 'Request Descriptions'
167
+ columns, *rows = content_node.search('tr').map do |tr|
168
+ next if tr.ancestors('table').length > 1
169
+
170
+ tr.search('td').map do |td|
171
+ has_table = td.search('table').length.positive?
172
+ if !has_table
173
+ td.content.gsub(/\n\s/, '').strip
174
+ else
175
+ table = td.search('table')[0]
176
+ c, *r = table.search('tr').map do |tr|
177
+ tr.search('td').map do |td|
178
+ td.content.gsub(/\n\s/, '').strip
179
+ end
180
+ end
181
+ table_array_to_hash_array(c, r)
182
+ end
183
+ end
184
+ end.compact
185
+ binding.pry if rows.blank?
186
+ section_data[subsection[:title].downcase.gsub(/\s+/, '_').to_sym] =
187
+ table_hash_array_to_shape(standardize_table_hash_array(table_array_to_hash_array(
188
+ columns, rows
189
+ )))
190
+ elsif subsection[:title] == 'Sample Response'
191
+ title, xml = content_node.content.gsub(/[[:space:]]+/, ' ').strip.split('<', 2)
192
+ xml = pp_xml("<#{xml}")
193
+ section_data[:sample_response] = <<~XML
194
+ #{title}
195
+ #{xml}
196
+ XML
197
+ end
198
+ end
199
+
200
+ section_data
201
+ end
202
+
203
+ method_template = Erubis::Eruby.new(File.read('lib/usps/api/templates/method.erb'))
204
+ method_spec_template = Erubis::Eruby.new(File.read('lib/usps/api/templates/method_spec.erb'))
205
+ data.each do |api|
206
+ next if api[:group].nil? || api[:group].empty?
207
+
208
+ # begin
209
+ rendered_method = method_template.result(group: api[:group], data: api)
210
+ File.write("lib/usps/api/endpoints/#{api[:group].underscore}.rb", rendered_method)
211
+
212
+ rendered_method_spec = method_spec_template.result(group: api[:group], data: api)
213
+ File.write("spec/usps/api/endpoints/#{api[:group].underscore}_spec.rb",
214
+ rendered_method_spec)
215
+
216
+ @endpoint_files << {
217
+ file: api[:group].underscore,
218
+ module: api[:group].camelize,
219
+ }
220
+ # rescue => e
221
+ # @failed_methods << {
222
+ # file: path,
223
+ # api: api[:group],
224
+ # }
225
+ # end
226
+ end
227
+ end
228
+
229
+ failed_docs = []
230
+ Dir.glob('lib/data/api/*.htm').each do |f|
231
+ ap f
232
+ # begin
233
+ parse_doc(f)
234
+ # rescue => e
235
+ # failed_docs << f
236
+ # end
237
+ end
238
+
239
+ endpoints_template = Erubis::Eruby.new(File.read('lib/usps/api/templates/endpoints.erb'))
240
+ File.write(
241
+ 'lib/usps/api/endpoints.rb',
242
+ endpoints_template.result(files: @endpoint_files.sort_by { |f| f[:file] })
243
+ )
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require 'usps/version'
3
+ require 'usps/logger'
4
+ require 'usps/config'
5
+
6
+ require 'active_support'
7
+ require 'active_support/core_ext'
8
+ require 'builder'
9
+ require 'faraday'
10
+ require 'faraday_middleware'
11
+ require 'json'
12
+ require 'logger'
13
+
14
+ require_relative 'usps/api/errors/usps_error'
15
+ require_relative 'usps/api/errors/too_many_requests_error'
16
+ # require_relative 'usps/api/error'
17
+ # require_relative 'usps/api/errors'
18
+ require_relative 'usps/faraday/response/raise_error'
19
+ require_relative 'usps/faraday/connection'
20
+ require_relative 'usps/faraday/request'
21
+ # require_relative 'usps/api/mixins'
22
+ require_relative 'usps/api/xml'
23
+ require_relative 'usps/api/endpoints'
24
+ # require_relative 'usps/pagination/cursor'
25
+ require_relative 'usps/client'
26
+
27
+ # module Usps
28
+ # class Error < StandardError; end
29
+ # # Your code goes here...
30
+ # end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ # This file was auto-generated by lib/tasks/web.rake
3
+
4
+ require_relative 'endpoints/carrier_pickup_schedule'
5
+ require_relative 'endpoints/city_state_lookup'
6
+ require_relative 'endpoints/e_vs_express_mail_intl'
7
+ require_relative 'endpoints/e_vs_first_class_mail_intl'
8
+ require_relative 'endpoints/e_vs_priority_mail_intl'
9
+ require_relative 'endpoints/e_vsgxg_get_label'
10
+ require_relative 'endpoints/e_vsi_cancel'
11
+ require_relative 'endpoints/hfp_facility_info'
12
+ require_relative 'endpoints/intl_rate_v2'
13
+ require_relative 'endpoints/pts_email'
14
+ require_relative 'endpoints/pts_pod'
15
+ require_relative 'endpoints/pts_rre'
16
+ require_relative 'endpoints/ptst_pod'
17
+ require_relative 'endpoints/rate_v4'
18
+ require_relative 'endpoints/scan'
19
+ require_relative 'endpoints/track_v2'
20
+ require_relative 'endpoints/usps_returns_label'
21
+ require_relative 'endpoints/verify'
22
+ require_relative 'endpoints/zip_code_lookup'
23
+
24
+ module Usps
25
+ module Api
26
+ module Endpoints
27
+ include CarrierPickupSchedule
28
+ include CityStateLookup
29
+ include EVSExpressMailIntl
30
+ include EVSFirstClassMailIntl
31
+ include EVSPriorityMailIntl
32
+ include EVSGXGGetLabel
33
+ include EVSICancel
34
+ include HFPFacilityInfo
35
+ include IntlRateV2
36
+ include PTSEmail
37
+ include PTSPod
38
+ include PTSRre
39
+ include PTSTPod
40
+ include RateV4
41
+ include SCAN
42
+ include TrackV2
43
+ include USPSReturnsLabel
44
+ include Verify
45
+ include ZipCodeLookup
46
+
47
+ ACTIONS = {
48
+ carrier_pickup_schedule: 'CarrierPickupSchedule',
49
+ city_state_lookup: 'CityStateLookup',
50
+ e_vs_express_mail_intl: 'EVSExpressMailIntl',
51
+ e_vs_first_class_mail_intl: 'EVSFirstClassMailIntl',
52
+ e_vs_priority_mail_intl: 'EVSPriorityMailIntl',
53
+ e_vsgxg_get_label: 'EVSGXGGetLabel',
54
+ e_vsi_cancel: 'EVSICancel',
55
+ hfp_facility_info: 'HFPFacilityInfo',
56
+ intl_rate_v2: 'IntlRateV2',
57
+ pts_email: 'PTSEmail',
58
+ pts_pod: 'PTSPod',
59
+ pts_rre: 'PTSRre',
60
+ ptst_pod: 'PTSTPod',
61
+ rate_v4: 'RateV4',
62
+ scan: 'SCAN',
63
+ track_v2: 'TrackV2',
64
+ usps_returns_label: 'USPSReturnsLabel',
65
+ verify: 'Verify',
66
+ zip_code_lookup: 'ZipCodeLookup',
67
+ }.freeze
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was auto-generated by lib/tasks/api.rake
4
+
5
+ module Usps
6
+ module Api
7
+ module Endpoints
8
+ module CarrierPickupSchedule
9
+ #
10
+ # Package Pickup Schedule API
11
+ #
12
+ # The Package Pickup Schedule API schedules a
13
+ # Package Pickup and provides the user a confirmation number for the scheduled
14
+ # pickup. Prior to making a Pickup Schedule call, it is recommended to use
15
+ # the Pickup Availability API to confirm that service is available.
16
+ #
17
+ # @option option [(Alias)] :CarrierPickupScheduleRequest (Required)
18
+ # @option option [String] :FirstName (Required)
19
+ # - Only alpha characters, apostrophes, spaces, periods, and hyphens "-" may be used. For example: <FirstName>John</FirstName>
20
+ # @option option [String] :LastName (Required)
21
+ # - Only alpha characters, apostrophes, spaces, periods and hyphens "-" may be used. For example: <LastName>Doe</LastName>
22
+ # @option option [String] :FirmName (Optional)
23
+ # - Only alpha and numeric characters, apostrophes, spaces, hyphens "-" and ampersands "&" may be used. Use this tag for a firm or company name. Some firms/companies that have their own ZIP codes require the use of firm name to properly identify their address. Note: FirmName is Optional except if the First Name and Last Name tags are null. For example: <FirmName>ABC Company</FirmName>
24
+ # @option option [String] :SuiteOrApt (Required)
25
+ # - Apartment or suite number. Optional except if needed to uniquely identify an address at a multiple dwelling address, for example, an apartment building. For example: <SuiteOrApt>Suite 777</SuiteOrApt>
26
+ # @option option [String] :Address2 (Required)
27
+ # - Street address. For example: <Address2>1390 Market Street</Address2>
28
+ # @option option [String] :Urbanization (Required)
29
+ # - Use this tag for Urbanization (for Puerto Rico only). ZIP Code prefixes 006 to 009, if area is so designated.
30
+ # @option option [String] :City (Required)
31
+ # - City name. Either ZIP5 or City and State are required. For example: <City>Houston</City>
32
+ # @option option [String] :State (Required)
33
+ # - State abbreviation. Either ZIP5 or City and State are required. For example: <State>TX</State>
34
+ # @option option [String] :ZIP5 (Required)
35
+ # - 5-digit ZIP Code. Either ZIP5 or City and State are required. For example: <ZIP5>77058</ZIP5>
36
+ # @option option [String] :ZIP4 (Required)
37
+ # - Use this tag for a 4 digit ZIP Code. For example: <ZIP4>1234</ZIP4>
38
+ # @option option [String] :Phone (Required)
39
+ # - Two formats are allowed: (###) 123-4567 or ###-123-4567. For example: <Phone>5555551234</Phone> or <Phone>555-555-1234</Phone>
40
+ # @option option [String] :Extension (Optional)
41
+ # - Optional value for Phone Extension. For example: <Extension>201</Extension>
42
+ # @option option [(Group)] :Package (Required)
43
+ # - No values entered with this tag. <ServiceType> and <Count> tags are embedded under this. Refer to the XML request example section, below, to see how these embedded tags are formatted. If the <Count> for a service type is zero, you do not need to encode a <Package> but you must have at least one <Package> with embedded <ServiceType> and <Count> tags.
44
+ # @option option [String] :ServiceType (Required)
45
+ # - This tag is embedded under the <Package> tag. If your pickup contains more than one Service Type, use additional <Package> tags for each service type with the accompanying <ServiceType> and <Count> tags. Refer to the XML Request Example below to see how these embedded tags are formatted. For example: <ServiceType>PriorityMailExpress</ServiceType>
46
+ # @option option [String] :Count (Required)
47
+ # - This tag is embedded under the <Package> tag. Enter the number of packages for the accompanying <ServiceType> tag. Maximum characters allowed: 3 or 999 packages. If your pickup contains more than one Service Type, use additional <Package> tags for each service type with the accompanying <ServiceType> and <Count> tags. Refer to the XML request example section, below, to see how these embedded tags are formatted. For example: <Count>2</Count>
48
+ # @option option [String] :EstimatedWeight (Required)
49
+ # - Enter the estimated aggregate weight (in pounds) of all packages being picked up. For example: <EstimatedWeight>14</EstimatedWeight>
50
+ # @option option [String] :PackageLocation (Required)
51
+ # - Enter one of the following values: Note: "Other" requires information in the value for the <SpecialInstructions> tag. For example: <PackageLocation>Front Door</PackageLocation>
52
+ # @option option [String] :SpecialInstructions (Optional)
53
+ # - Value Required when PackageLocation is “Other”. Only alpha, numeric, commas, periods, apostrophes, _, &, -, ( ), ?, #, / +, @ and space characters may be used. For example: <SpecialInstructions>Packages are behind the screen door.</SpecialInstructions>
54
+ # @option option [String] :EmailAddress (Optional)
55
+ # - If provided, email notifications will be sent confirming package pickup, or request changes and cancellations. Maximum characters allowed: 50. For example: <EmailAddress>cpapple@email.com</EmailAddress>
56
+
57
+ #
58
+ # @see
59
+ def carrier_pickup_schedule(options = {})
60
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request missing') if options[:carrier_pickup_schedule_request].nil?
61
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :first_name missing') if options[:carrier_pickup_schedule_request][:first_name].nil?
62
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :last_name missing') if options[:carrier_pickup_schedule_request][:last_name].nil?
63
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :suite_or_apt missing') if options[:carrier_pickup_schedule_request][:suite_or_apt].nil?
64
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :address2 missing') if options[:carrier_pickup_schedule_request][:address2].nil?
65
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :urbanization missing') if options[:carrier_pickup_schedule_request][:urbanization].nil?
66
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :city missing') if options[:carrier_pickup_schedule_request][:city].nil?
67
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :state missing') if options[:carrier_pickup_schedule_request][:state].nil?
68
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :zip5 missing') if options[:carrier_pickup_schedule_request][:zip5].nil?
69
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :zip4 missing') if options[:carrier_pickup_schedule_request][:zip4].nil?
70
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :phone missing') if options[:carrier_pickup_schedule_request][:phone].nil?
71
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :package missing') if options[:carrier_pickup_schedule_request][:package].nil?
72
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :package, :service_type missing') if options[:carrier_pickup_schedule_request][:package][:service_type].nil?
73
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :package, :count missing') if options[:carrier_pickup_schedule_request][:package][:count].nil?
74
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :package, :estimated_weight missing') if options[:carrier_pickup_schedule_request][:package][:estimated_weight].nil?
75
+ throw ArgumentError.new('Required arguments :carrier_pickup_schedule_request, :package, :package_location missing') if options[:carrier_pickup_schedule_request][:package][:package_location].nil?
76
+
77
+ request = build_request(:carrier_pickup_schedule, options)
78
+ get('https://secure.shippingapis.com/ShippingAPI.dll', {
79
+ API: 'CarrierPickupSchedule',
80
+ XML: request,
81
+ })
82
+ end
83
+
84
+ private
85
+
86
+ def tag_unless_blank(xml, tag_name, data)
87
+ xml.tag!(tag_name, data) unless data.blank? || data.nil?
88
+ end
89
+
90
+ def build_carrier_pickup_schedule_request(xml, options = {})
91
+ xml.tag!('FirstName', options[:carrier_pickup_schedule_request][:first_name])
92
+ xml.tag!('LastName', options[:carrier_pickup_schedule_request][:last_name])
93
+ tag_unless_blank(xml, 'FirmName', options[:carrier_pickup_schedule_request][:firm_name])
94
+ xml.tag!('SuiteOrApt', options[:carrier_pickup_schedule_request][:suite_or_apt])
95
+ xml.tag!('Address2', options[:carrier_pickup_schedule_request][:address2])
96
+ xml.tag!('Urbanization', options[:carrier_pickup_schedule_request][:urbanization])
97
+ xml.tag!('City', options[:carrier_pickup_schedule_request][:city])
98
+ xml.tag!('State', options[:carrier_pickup_schedule_request][:state])
99
+ xml.tag!('ZIP5', options[:carrier_pickup_schedule_request][:zip5])
100
+ xml.tag!('ZIP4', options[:carrier_pickup_schedule_request][:zip4])
101
+ xml.tag!('Phone', options[:carrier_pickup_schedule_request][:phone])
102
+ tag_unless_blank(xml, 'Extension', options[:carrier_pickup_schedule_request][:extension])
103
+ xml.tag!('Package') do
104
+ xml.tag!('ServiceType', options[:carrier_pickup_schedule_request][:package][:service_type])
105
+ xml.tag!('Count', options[:carrier_pickup_schedule_request][:package][:count])
106
+ xml.tag!('EstimatedWeight', options[:carrier_pickup_schedule_request][:package][:estimated_weight])
107
+ xml.tag!('PackageLocation', options[:carrier_pickup_schedule_request][:package][:package_location])
108
+ tag_unless_blank(xml, 'SpecialInstructions', options[:carrier_pickup_schedule_request][:package][:special_instructions])
109
+ tag_unless_blank(xml, 'EmailAddress', options[:carrier_pickup_schedule_request][:package][:email_address])
110
+ end
111
+ xml.target!
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+ end