active_shipping 1.3.0 → 1.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +5 -0
- data/Rakefile +5 -0
- data/active_shipping.gemspec +1 -0
- data/lib/active_shipping.rb +2 -0
- data/lib/active_shipping/carriers.rb +1 -0
- data/lib/active_shipping/carriers/fedex.rb +1 -1
- data/lib/active_shipping/carriers/ups.rb +19 -2
- data/lib/active_shipping/carriers/usps_returns.rb +86 -0
- data/lib/active_shipping/errors.rb +11 -2
- data/lib/active_shipping/external_return_label_request.rb +421 -0
- data/lib/active_shipping/external_return_label_response.rb +26 -0
- data/lib/active_shipping/version.rb +1 -1
- data/test/console.rb +40 -0
- data/test/credentials.yml +9 -0
- data/test/fixtures/xml/fedex/tracking_response_invalid_tracking_number.xml +52 -0
- data/test/fixtures/xml/usps/invalid_xml_response.xml +10 -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/ups_test.rb +29 -0
- data/test/remote/usps_returns_test.rb +72 -0
- data/test/unit/carriers/fedex_test.rb +12 -0
- data/test/unit/carriers/ups_test.rb +19 -0
- data/test/unit/carriers/usps_returns_test.rb +45 -0
- data/test/unit/external_return_label_request_test.rb +212 -0
- metadata +35 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02e598e6f3f22df641b4384ef5bfac4ccf12adf5
|
4
|
+
data.tar.gz: 3f85700bd9c69ba3d5c3b21b84d7c42c9ec66487
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34bdf173c3c6b0aee4b3ef7fdb36a4c502e87d4a79d14d9d5bff1a545638f97143ba9504424d77d6bfbd73f9f5c5659ac650e9adc1bd180d0974b5f9de39a0a4
|
7
|
+
data.tar.gz: 73b86a63d94164dc3a7c2f0fb3b3d7c5c1a6f2c248308426b3fffb96142d70a76cc572202d9b03aea745472871d66a7f6061d2eb233c7e0f69a6da6c8c3dba91
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# ActiveShipping CHANGELOG
|
2
2
|
|
3
|
+
### v1.4.0
|
4
|
+
|
5
|
+
- Added support for USPS merchant returns service
|
6
|
+
- Added support for UPS SurePost
|
7
|
+
- Added support for UPS third party billing
|
8
|
+
- Fix FedEx tracking response errors
|
9
|
+
- Add rake console command for development
|
10
|
+
|
3
11
|
### v1.3.0
|
4
12
|
|
5
13
|
- Support voiding labels on UPS
|
data/README.md
CHANGED
@@ -16,6 +16,7 @@ Active Shipping is currently being used and improved in a production environment
|
|
16
16
|
|
17
17
|
* [UPS](http://www.ups.com)
|
18
18
|
* [USPS](http://www.usps.com)
|
19
|
+
* [USPS Returns](http://returns.usps.com)
|
19
20
|
* [FedEx](http://www.fedex.com)
|
20
21
|
* [Canada Post](http://www.canadapost.ca)
|
21
22
|
* [New Zealand Post](http://www.nzpost.co.nz)
|
@@ -116,6 +117,10 @@ To log requests and responses, just set the `logger` on your carrier class to so
|
|
116
117
|
|
117
118
|
(This logging functionality is provided by the [`PostsData` module](https://github.com/Shopify/active_utils/blob/master/lib/active_utils/posts_data.rb) in the `active_utils` dependency.)
|
118
119
|
|
120
|
+
To debug API requests and your code you can run `rake console` to start a Pry session with `ActiveShipping` included
|
121
|
+
and instances of the various carriers set up with your test credentials.
|
122
|
+
Look at the file `test/console.rb` to see the other goodies it provides.
|
123
|
+
|
119
124
|
After you've pushed your well-tested changes to your github fork, make a pull request and we'll take it from there! For more information, see CONTRIBUTING.md.
|
120
125
|
|
121
126
|
## Legal Mumbo Jumbo
|
data/Rakefile
CHANGED
data/active_shipping.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.add_development_dependency('mocha', '~> 1')
|
25
25
|
s.add_development_dependency('timecop')
|
26
26
|
s.add_development_dependency('business_time')
|
27
|
+
s.add_development_dependency('pry')
|
27
28
|
|
28
29
|
s.files = `git ls-files`.split($/)
|
29
30
|
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/lib/active_shipping.rb
CHANGED
@@ -43,3 +43,5 @@ require 'active_shipping/shipment_packer'
|
|
43
43
|
require 'active_shipping/carrier'
|
44
44
|
require 'active_shipping/carriers'
|
45
45
|
require 'active_shipping/errors'
|
46
|
+
require 'active_shipping/external_return_label_request'
|
47
|
+
require 'active_shipping/external_return_label_response'
|
@@ -24,6 +24,7 @@ ActiveShipping::Carriers.register :BenchmarkCarrier, 'active_shipping/carriers/b
|
|
24
24
|
ActiveShipping::Carriers.register :BogusCarrier, 'active_shipping/carriers/bogus_carrier'
|
25
25
|
ActiveShipping::Carriers.register :UPS, 'active_shipping/carriers/ups'
|
26
26
|
ActiveShipping::Carriers.register :USPS, 'active_shipping/carriers/usps'
|
27
|
+
ActiveShipping::Carriers.register :USPSReturns, 'active_shipping/carriers/usps_returns'
|
27
28
|
ActiveShipping::Carriers.register :FedEx, 'active_shipping/carriers/fedex'
|
28
29
|
ActiveShipping::Carriers.register :Shipwire, 'active_shipping/carriers/shipwire'
|
29
30
|
ActiveShipping::Carriers.register :Kunaki, 'active_shipping/carriers/kunaki'
|
@@ -575,7 +575,7 @@ module ActiveShipping
|
|
575
575
|
when '9040'
|
576
576
|
raise ActiveShipping::ShipmentNotFound, first_notification.at('Message').text
|
577
577
|
else
|
578
|
-
raise ActiveShipping::ResponseContentError, first_notification.at('Message').text
|
578
|
+
raise ActiveShipping::ResponseContentError, StandardError.new(first_notification.at('Message').text)
|
579
579
|
end
|
580
580
|
end
|
581
581
|
|
@@ -65,8 +65,11 @@ module ActiveShipping
|
|
65
65
|
"83" => "UPS Today Dedicated Courier",
|
66
66
|
"84" => "UPS Today Intercity",
|
67
67
|
"85" => "UPS Today Express",
|
68
|
-
"86" => "UPS Today Express Saver"
|
69
|
-
|
68
|
+
"86" => "UPS Today Express Saver",
|
69
|
+
"92" => "UPS SurePost (USPS) < 1lb",
|
70
|
+
"93" => "UPS SurePost (USPS) > 1lb",
|
71
|
+
"94" => "UPS SurePost (USPS) BPM",
|
72
|
+
"95" => "UPS SurePost (USPS) Media",
|
70
73
|
}
|
71
74
|
|
72
75
|
CANADA_ORIGIN_SERVICES = {
|
@@ -375,6 +378,20 @@ module ActiveShipping
|
|
375
378
|
end
|
376
379
|
end
|
377
380
|
end
|
381
|
+
elsif options[:bill_third_party]
|
382
|
+
xml.PaymentInformation do
|
383
|
+
xml.BillThirdParty do
|
384
|
+
xml.BillThirdPartyShipper do
|
385
|
+
xml.AccountNumber(options[:billing_account])
|
386
|
+
xml.ThirdParty do
|
387
|
+
xml.Address do
|
388
|
+
xml.PostalCode(options[:billing_zip])
|
389
|
+
xml.CountryCode(options[:billing_country])
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
378
395
|
else
|
379
396
|
xml.ItemizedPaymentInformation do
|
380
397
|
xml.ShipmentCharge do
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module ActiveShipping
|
2
|
+
|
3
|
+
class USPSReturns < Carrier
|
4
|
+
|
5
|
+
self.retry_safe = true
|
6
|
+
|
7
|
+
cattr_reader :name
|
8
|
+
@@name = "USPS Returns"
|
9
|
+
|
10
|
+
LIVE_DOMAIN = 'returns.usps.com'
|
11
|
+
LIVE_RESOURCE = 'Services/ExternalCreateReturnLabel.svc/ExternalCreateReturnLabel'
|
12
|
+
|
13
|
+
TEST_DOMAIN = 'returns.usps.com'
|
14
|
+
TEST_RESOURCE = 'Services/ExternalCreateReturnLabel.svc/ExternalCreateReturnLabel'
|
15
|
+
|
16
|
+
API_CODES = {
|
17
|
+
:external_return_label_request => 'externalReturnLabelRequest'
|
18
|
+
}
|
19
|
+
|
20
|
+
USE_SSL = {
|
21
|
+
:external_return_label_request => true
|
22
|
+
}
|
23
|
+
|
24
|
+
def requirements
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
def external_return_label_request(label, options = {})
|
29
|
+
response = commit(:external_return_label_request, label.to_xml, (options[:test] || false))
|
30
|
+
parse_external_return_label_response(response)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def parse_external_return_label_response(response)
|
36
|
+
tracking_number, postal_routing, return_label, message = '', '', '', '', ''
|
37
|
+
xml = Nokogiri::XML(response)
|
38
|
+
error = external_return_label_errors(xml)
|
39
|
+
if error.is_a?(Hash) && error.size > 0
|
40
|
+
message << "#{error[:error][:code]}: #{error[:error][:message]}"
|
41
|
+
else
|
42
|
+
tracking_number = xml.at('TrackingNumber').try(:text)
|
43
|
+
postal_routing = xml.at('PostalRouting').try(:text)
|
44
|
+
return_label = xml.at('ReturnLabel').try(:text)
|
45
|
+
end
|
46
|
+
|
47
|
+
ExternalReturnLabelResponse.new(message.length == 0, message, Hash.from_xml(response),
|
48
|
+
:xml => response,
|
49
|
+
:carrier => @@name,
|
50
|
+
:request => last_request,
|
51
|
+
:return_label => return_label,
|
52
|
+
:postal_routing => postal_routing,
|
53
|
+
:tracking_number => tracking_number
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def external_return_label_errors(document)
|
58
|
+
return {} unless document.respond_to?(:elements)
|
59
|
+
res = {}
|
60
|
+
if node = document.at('*/errors')
|
61
|
+
if node.at('ExternalReturnLabelError')
|
62
|
+
if message = node.at('ExternalReturnLabelError/InternalErrorDescription').try(:text)
|
63
|
+
code = node.at('ExternalReturnLabelError/InternalErrorNumber').try(:text) || ''
|
64
|
+
res = {:error => {:code => code, :message => message}}
|
65
|
+
elsif message = node.at('ExternalReturnLabelError/ExternalErrorDescription').try(:text)
|
66
|
+
code = node.at('ExternalReturnLabelError/ExternalErrorNumber').try(:text) || ''
|
67
|
+
res = {:error => {:code => code, :message => message}}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
res
|
72
|
+
end
|
73
|
+
|
74
|
+
def commit(action, request, test = false)
|
75
|
+
ssl_get(request_url(action, request, test))
|
76
|
+
end
|
77
|
+
|
78
|
+
def request_url(action, request, test)
|
79
|
+
scheme = USE_SSL[action] ? 'https://' : 'http://'
|
80
|
+
host = test ? TEST_DOMAIN : LIVE_DOMAIN
|
81
|
+
resource = test ? TEST_RESOURCE : LIVE_RESOURCE
|
82
|
+
"#{scheme}#{host}/#{resource}?#{API_CODES[action]}=#{URI.encode(request)}"
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -16,11 +16,20 @@ module ActiveShipping
|
|
16
16
|
end
|
17
17
|
|
18
18
|
class ResponseContentError < ActiveShipping::Error
|
19
|
-
def initialize(exception, content_body)
|
20
|
-
super(
|
19
|
+
def initialize(exception, content_body = nil)
|
20
|
+
super([exception.message, content_body].compact.join(" \n\n"))
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
class ShipmentNotFound < ActiveShipping::Error
|
25
25
|
end
|
26
|
+
|
27
|
+
class USPSValidationError < StandardError
|
28
|
+
end
|
29
|
+
|
30
|
+
class USPSMissingRequiredTagError < StandardError
|
31
|
+
def initialize(tag, prop)
|
32
|
+
super("Missing required tag #{tag} set by property #{prop}")
|
33
|
+
end
|
34
|
+
end
|
26
35
|
end
|
@@ -0,0 +1,421 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module ActiveShipping
|
3
|
+
|
4
|
+
class ExternalReturnLabelRequest
|
5
|
+
|
6
|
+
CAP_STRING_LEN = 100
|
7
|
+
|
8
|
+
USPS_EMAIL_REGEX = /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/
|
9
|
+
|
10
|
+
LABEL_FORMAT = {
|
11
|
+
'Instructions' => 'null',
|
12
|
+
'No Instructions' => 'NOI',
|
13
|
+
'Double Label' => 'TWO'
|
14
|
+
}
|
15
|
+
|
16
|
+
SERVICE_TYPE_CODE = [
|
17
|
+
'044', '019', '596', '020', '597','022', '024', '017', '018'
|
18
|
+
]
|
19
|
+
|
20
|
+
CALL_CENTER_OR_SELF_SERVICE = ['CallCenter', 'Customer']
|
21
|
+
|
22
|
+
LABEL_DEFINITION = ['4X6', 'Zebra-4X6', '4X4', '3X6']
|
23
|
+
|
24
|
+
IMAGE_TYPE = ['PDF', 'TIF']
|
25
|
+
|
26
|
+
attr_reader :customer_name,
|
27
|
+
:customer_address1,
|
28
|
+
:customer_address2,
|
29
|
+
:customer_city,
|
30
|
+
:customer_state,
|
31
|
+
:customer_zipcode,
|
32
|
+
:customer_urbanization,
|
33
|
+
:company_name,
|
34
|
+
:attention,
|
35
|
+
:label_format,
|
36
|
+
:label_definition,
|
37
|
+
:service_type_code,
|
38
|
+
:merchandise_description,
|
39
|
+
:insurance_amount,
|
40
|
+
:address_override_notification,
|
41
|
+
:packaging_information,
|
42
|
+
:packaging_information2,
|
43
|
+
:call_center_or_self_service,
|
44
|
+
:image_type,
|
45
|
+
:address_validation,
|
46
|
+
:sender_name,
|
47
|
+
:sender_email,
|
48
|
+
:recipient_name,
|
49
|
+
:recipient_email,
|
50
|
+
:recipient_bcc,
|
51
|
+
:merchant_account_id,
|
52
|
+
:mid
|
53
|
+
|
54
|
+
def initialize(options = {})
|
55
|
+
options.each do |pair|
|
56
|
+
self.public_send("#{pair[0]}=".to_sym, pair[1]) if self.respond_to?("#{pair[0]}=".to_sym)
|
57
|
+
end
|
58
|
+
|
59
|
+
verify_or_raise_required
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.from_hash(options={})
|
63
|
+
self.new(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Sent by the system containing the returns label attachment and message.
|
67
|
+
def recipient_bcc=(v)
|
68
|
+
@recipient_bcc = validate_email(v, __method__)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sent by the system containing the returns label attachment and message.
|
72
|
+
# <em>Optional</em>.
|
73
|
+
def recipient_email=(v)
|
74
|
+
@recipient_email = validate_email(v, __method__)
|
75
|
+
end
|
76
|
+
|
77
|
+
# The name in an email sent by the system containing the returns label attachment.
|
78
|
+
# <em>Optional</em>.
|
79
|
+
def recipient_name=(v)
|
80
|
+
@recipient_name = nil
|
81
|
+
if (v = sanitize(v)) && v.length > 0
|
82
|
+
@recipient_name = v
|
83
|
+
else
|
84
|
+
raise USPSValidationError, "'#{v}' is not a valid string in #{__method__}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# The From address in an email sent by the system containing the returns
|
89
|
+
# label attachment and message, Defaults to DONOTREPLY@USPSReturns.com
|
90
|
+
# if a recipient email is entered and a sender email is not.
|
91
|
+
# <em>Optional</em>.
|
92
|
+
def sender_email=(v)
|
93
|
+
@sender_email = validate_email(v, __method__)
|
94
|
+
end
|
95
|
+
|
96
|
+
# The From name in an email sent by the system containing the returns
|
97
|
+
# label attachment. Defaults to “Merchant Returns” if a recipient name
|
98
|
+
# is entered and a sender name is not.
|
99
|
+
# <em>Optional</em>.
|
100
|
+
def sender_name=(v)
|
101
|
+
@sender_name = nil
|
102
|
+
if (v = sanitize(v)) && v.length > 0
|
103
|
+
@sender_name = v
|
104
|
+
else
|
105
|
+
raise USPSValidationError, "'#{v}' is not a valid string in #{__method__}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Used to override the validation of the customer address.
|
110
|
+
# If true, the address will be validated against WebTools.
|
111
|
+
# If false, the system will bypass the validation.
|
112
|
+
# <em>Optional</em>.
|
113
|
+
def address_validation=(v)
|
114
|
+
@address_validation = to_bool(v, true)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Used to select the format of the return label.
|
118
|
+
# <em>Optional</em>.
|
119
|
+
# * PDF <em>Default</em>.
|
120
|
+
# * TIF
|
121
|
+
def image_type=(v)
|
122
|
+
@image_type = validate_set_inclusion(v.to_s.upcase, IMAGE_TYPE, __method__)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Used to determine if the returns label request is coming from a
|
126
|
+
# merchant call center agent or an end customer.
|
127
|
+
# <b>Required</b>.
|
128
|
+
# [CallCenter]
|
129
|
+
# [Customer]
|
130
|
+
def call_center_or_self_service=(v)
|
131
|
+
@call_center_or_self_service = validate_set_inclusion(v, CALL_CENTER_OR_SELF_SERVICE, __method__)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Package information can be one of three types: RMA, Invoice or
|
135
|
+
# Order number. This will appear on the second label generated when
|
136
|
+
# the LabelFormat “TWO” is selected.
|
137
|
+
# <em>Optional</em>.
|
138
|
+
def packaging_information2=(v)
|
139
|
+
@packaging_information2 = validate_string_length(v, 15, __method__)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Package information can be one of three types: RMA, Invoice or
|
143
|
+
# Order number. This will appear on the generated label.
|
144
|
+
# <em>Optional</em>.
|
145
|
+
def packaging_information=(v)
|
146
|
+
@packaging_information = validate_string_length(v, 15, __method__)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Override address if more address information
|
150
|
+
# is needed or system cannot find address. If
|
151
|
+
# the address_override_notification value is
|
152
|
+
# true then any address error being passed from
|
153
|
+
# WebTools would be bypassed and a successful
|
154
|
+
# response will be sent.
|
155
|
+
# <b>Required</b>.
|
156
|
+
def address_override_notification=(v)
|
157
|
+
@address_validation = to_bool(v)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Insured amount of package.
|
161
|
+
def insurance_amount=(v)
|
162
|
+
@insurance_amount = nil
|
163
|
+
if (1..200).include?(v.to_f)
|
164
|
+
@insurance_amount = v
|
165
|
+
else
|
166
|
+
raise USPSValidationError, "#{__method__} must be a numerical value between 1 and 200, found value '#{v}'."
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Description of the merchandise.
|
171
|
+
# <em>Optional</em>.
|
172
|
+
def merchandise_description=(v)
|
173
|
+
@merchandise_description = validate_string_length(v, 255, __method__)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Service type of the label as specified in the merchant profile setup.
|
177
|
+
# <b>Required</b>.
|
178
|
+
# [044] (Parcel Return Service)
|
179
|
+
# [019] (Priority Mail Returns service)
|
180
|
+
# [596] (Priority Mail Returns service, Insurance <= $200)
|
181
|
+
# [020] (First-Class Package Return service)
|
182
|
+
# [597] (First-Class Package Return service, Insurance <= $200)
|
183
|
+
# [022] (Ground Return Service)
|
184
|
+
# [024] (PRS – Full Network)
|
185
|
+
# [017] (PRS – Full Network, Insurance <=$200)
|
186
|
+
# [018] (PRS – Full Network, Insurance >$200)
|
187
|
+
def service_type_code=(v)
|
188
|
+
@service_type_code = validate_set_inclusion(v, SERVICE_TYPE_CODE, __method__)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Size of the label.
|
192
|
+
# <b>Required</b>.
|
193
|
+
# * 4X6
|
194
|
+
# * Zebra-4X6
|
195
|
+
# * 4X4
|
196
|
+
# * 3X6
|
197
|
+
def label_definition=(v)
|
198
|
+
@label_definition = validate_set_inclusion(v, LABEL_DEFINITION, __method__)
|
199
|
+
end
|
200
|
+
|
201
|
+
def label_format
|
202
|
+
@label_format && LABEL_FORMAT[@label_format]
|
203
|
+
end
|
204
|
+
|
205
|
+
# Format in which the label(s) will be printed.
|
206
|
+
# * null (“Instructions”)
|
207
|
+
# * NOI (“No Instructions”)
|
208
|
+
# * TWO (“Double Label”)
|
209
|
+
def label_format=(v)
|
210
|
+
@label_format = validate_set_inclusion(v, LABEL_FORMAT.keys, __method__)
|
211
|
+
end
|
212
|
+
|
213
|
+
# The intended recipient of the returned package (e.g. Returns Department).
|
214
|
+
# <em>Optional</em>.
|
215
|
+
def attention=(v)
|
216
|
+
@attention = validate_string_length(v, 38, __method__)
|
217
|
+
end
|
218
|
+
|
219
|
+
# The name of the company to which the package is being returned.
|
220
|
+
# <em>Optional</em>.
|
221
|
+
def company_name=(v)
|
222
|
+
@company_name = validate_string_length(v, 38, __method__)
|
223
|
+
end
|
224
|
+
|
225
|
+
# <b>Required</b>.
|
226
|
+
def merchant_account_id=(v)
|
227
|
+
@merchant_account_id = nil
|
228
|
+
if v.to_i > 0
|
229
|
+
@merchant_account_id = v
|
230
|
+
else
|
231
|
+
raise USPSValidationError, "#{__method__} must be a valid positive integer, found value '#{v}'."
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# <b>Required</b>.
|
236
|
+
def mid=(v)
|
237
|
+
@mid = nil
|
238
|
+
if v.to_s =~ /^\d{6,9}$/
|
239
|
+
@mid = v
|
240
|
+
else
|
241
|
+
raise USPSValidationError, "#{__method__} must be a valid integer between 6 and 9 digits in length, found value '#{v}'."
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Urbanization of customer returning the package (only applicable to Puerto Rico addresses).
|
246
|
+
# <em>Optional</em>.
|
247
|
+
def customer_urbanization=(v)
|
248
|
+
@customer_urbanization = validate_string_length(v, 32, __method__)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Name of customer returning package.
|
252
|
+
# <b>Required</b>.
|
253
|
+
def customer_name=(v)
|
254
|
+
@customer_name = validate_range(v, 1, 32, __method__)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Address of the customer returning the package.
|
258
|
+
# <b>Required</b>.
|
259
|
+
def customer_address1=(v)
|
260
|
+
@customer_address1 = validate_range(v, 1, 32, __method__)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Secondary address unit designator / number of customer
|
264
|
+
# returning the package. (such as an apartment or
|
265
|
+
# suite number, e.g. APT 202, STE 100)
|
266
|
+
def customer_address2=(v)
|
267
|
+
@customer_address2 = validate_range(v, 0, 32, __method__)
|
268
|
+
end
|
269
|
+
|
270
|
+
# City of customer returning the package.
|
271
|
+
# <b>Required</b>.
|
272
|
+
def customer_city=(v)
|
273
|
+
@customer_city = validate_range(v, 1, 32, __method__)
|
274
|
+
end
|
275
|
+
|
276
|
+
# State of customer returning the package.
|
277
|
+
# <b>Required</b>.
|
278
|
+
def customer_state=(v)
|
279
|
+
@customer_state = nil
|
280
|
+
if (v = sanitize(v)) && v =~ /^[a-zA-Z]{2}$/
|
281
|
+
@customer_state = v
|
282
|
+
else
|
283
|
+
raise USPSValidationError, "#{__method__} must be a String 2 chars in length, found value '#{v}'."
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Zipcode of customer returning the package.
|
288
|
+
# According to the USPS documentation, Zipcode is optional
|
289
|
+
# unless <tt>address_override_notification</tt> is true
|
290
|
+
# and <tt>address_validation</tt> is set to false.
|
291
|
+
# It's probably just easier to require Zipcodes.
|
292
|
+
# <b>Required</b>.
|
293
|
+
def customer_zipcode=(v)
|
294
|
+
@customer_zipcode = nil
|
295
|
+
if (v = sanitize(v))
|
296
|
+
v = v[0..4]
|
297
|
+
if v =~ /^\d{5}$/
|
298
|
+
@customer_zipcode = v
|
299
|
+
end
|
300
|
+
else
|
301
|
+
raise USPSValidationError, "#{__method__} must be a 5 digit number, found value '#{v}'."
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def verify_or_raise_required
|
306
|
+
%w(customer_name customer_address1 customer_city customer_state
|
307
|
+
customer_zipcode label_format label_definition service_type_code
|
308
|
+
call_center_or_self_service).each do |attr|
|
309
|
+
raise USPSMissingRequiredTagError.new(attr.camelize, attr) unless send(attr.to_sym)
|
310
|
+
end
|
311
|
+
# Safer than using inflection acroynms
|
312
|
+
raise USPSMissingRequiredTagError.new("MID", "mid") unless mid
|
313
|
+
raise USPSMissingRequiredTagError.new("MerchantAccountID", "merchant_account_id") unless merchant_account_id
|
314
|
+
end
|
315
|
+
|
316
|
+
def to_xml
|
317
|
+
xml_builder = Nokogiri::XML::Builder.new do |xml|
|
318
|
+
xml.ExternalReturnLabelRequest do
|
319
|
+
xml.CustomerName { xml.text(customer_name) }
|
320
|
+
xml.CustomerAddress1 { xml.text(customer_address1) }
|
321
|
+
xml.CustomerAddress2 { xml.text(customer_address2) } if customer_address2
|
322
|
+
xml.CustomerCity { xml.text(customer_city) }
|
323
|
+
xml.CustomerState { xml.text(customer_state) }
|
324
|
+
xml.CustomerZipCode { xml.text(customer_zipcode) } if customer_zipcode
|
325
|
+
xml.CustomerUrbanization { xml.text(customer_urbanization) } if customer_urbanization
|
326
|
+
|
327
|
+
xml.MerchantAccountID { xml.text(merchant_account_id) }
|
328
|
+
xml.MID { xml.text(mid) }
|
329
|
+
|
330
|
+
xml.SenderName { xml.text(sender_name) } if sender_name
|
331
|
+
xml.SenderEmail { xml.text(sender_email) } if sender_email
|
332
|
+
|
333
|
+
xml.RecipientName { xml.text(recipient_name) } if recipient_name
|
334
|
+
xml.RecipientEmail { xml.text(recipient_email) } if recipient_email
|
335
|
+
xml.RecipientBcc { xml.text(recipient_bcc) } if recipient_bcc
|
336
|
+
|
337
|
+
xml.LabelFormat { xml.text(label_format) } if label_format
|
338
|
+
xml.LabelDefinition { xml.text(label_definition) } if label_definition
|
339
|
+
xml.ServiceTypeCode { xml.text(service_type_code) } if service_type_code
|
340
|
+
|
341
|
+
xml.CompanyName { xml.text(company_name) } if company_name
|
342
|
+
xml.Attention { xml.text(attention) } if attention
|
343
|
+
|
344
|
+
xml.CallCenterOrSelfService { xml.text(call_center_or_self_service) }
|
345
|
+
|
346
|
+
xml.MerchandiseDescription { xml.text(merchandise_description) } if merchandise_description
|
347
|
+
xml.InsuranceAmount { xml.text(insurance_amount) } if insurance_amount
|
348
|
+
|
349
|
+
xml.AddressOverrideNotification { xml.text(!!address_override_notification) }
|
350
|
+
|
351
|
+
xml.PackageInformation { xml.text(packaging_information) } if packaging_information
|
352
|
+
xml.PackageInformation2 { xml.text(packaging_information2) } if packaging_information2
|
353
|
+
|
354
|
+
xml.ImageType { xml.text(image_type) } if image_type
|
355
|
+
xml.AddressValidation { xml.text(!!address_validation) }
|
356
|
+
|
357
|
+
end
|
358
|
+
end
|
359
|
+
xml_builder.to_xml
|
360
|
+
end
|
361
|
+
|
362
|
+
private
|
363
|
+
|
364
|
+
def to_bool(v, default = false)
|
365
|
+
v = v.to_s
|
366
|
+
if v =~ (/(true|yes|1)$/i)
|
367
|
+
true
|
368
|
+
elsif v =~ (/(false|no|0)$/i)
|
369
|
+
false
|
370
|
+
else
|
371
|
+
default
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def sanitize(v)
|
376
|
+
if v.is_a?(String)
|
377
|
+
v.strip!
|
378
|
+
v[0..CAP_STRING_LEN - 1]
|
379
|
+
else
|
380
|
+
nil
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def validate_range(v, min, max, meth)
|
385
|
+
if (v = sanitize(v).to_s) && ((min.to_i)..(max.to_i)).include?(v.length)
|
386
|
+
if v.length == 0
|
387
|
+
nil
|
388
|
+
else
|
389
|
+
v
|
390
|
+
end
|
391
|
+
else
|
392
|
+
raise USPSValidationError, "#{meth} must be a String between #{min.to_i} and #{max.to_i} chars in length, found value '#{v}'."
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def validate_string_length(s, max_len, meth)
|
397
|
+
if (s = sanitize(s)) && s.length <= max_len.to_i
|
398
|
+
s
|
399
|
+
else
|
400
|
+
raise USPSValidationError, "#{meth} must be a String no more than #{max_len} chars in length, found value '#{s}'."
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def validate_set_inclusion(v, set, meth)
|
405
|
+
if set.respond_to?(:include?) && set.include?(v)
|
406
|
+
v
|
407
|
+
else
|
408
|
+
raise USPSValidationError, "#{v} is not valid in #{meth}, try any of the following: #{(set.respond_to?(:join) && set.join(',')) || ''}"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def validate_email(v, meth)
|
413
|
+
if (v = sanitize(v)) && v =~ USPS_EMAIL_REGEX
|
414
|
+
v
|
415
|
+
else
|
416
|
+
raise USPSValidationError, "'#{v}' is not a valid e-mail in #{meth}"
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
end
|
421
|
+
end
|