shipppit-canada-post 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +312 -0
- data/Rakefile +2 -0
- data/canada-post-api.gemspec +32 -0
- data/lib/canada_post.rb +14 -0
- data/lib/canada_post/client.rb +45 -0
- data/lib/canada_post/credentials.rb +17 -0
- data/lib/canada_post/helpers.rb +15 -0
- data/lib/canada_post/rate.rb +27 -0
- data/lib/canada_post/request/base.rb +155 -0
- data/lib/canada_post/request/manifest.rb +103 -0
- data/lib/canada_post/request/rate.rb +122 -0
- data/lib/canada_post/request/registration.rb +49 -0
- data/lib/canada_post/request/shipment.rb +96 -0
- data/lib/canada_post/request/shipping.rb +232 -0
- data/lib/canada_post/shipment.rb +68 -0
- data/lib/canada_post/version.rb +3 -0
- data/spec/config/canada_post_credentials.example.yml +11 -0
- data/spec/manifest_spec.rb +40 -0
- data/spec/rate_spec.rb +96 -0
- data/spec/registration_spec.rb +27 -0
- data/spec/shipping_spec.rb +137 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/credentials.rb +16 -0
- data/spec/support/vcr.rb +14 -0
- metadata +197 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
module CanadaPost
|
2
|
+
module Request
|
3
|
+
class Shipment < Base
|
4
|
+
|
5
|
+
def process_request
|
6
|
+
api_response = client(shipment_url, build_xml, shipment_headers)
|
7
|
+
response = parse_response(api_response)
|
8
|
+
|
9
|
+
if success?(response)
|
10
|
+
rate_reply_details = response[:price_quotes][:price_quote] || []
|
11
|
+
rate_reply_details = [rate_reply_details] if rate_reply_details.is_a? Hash
|
12
|
+
rate_reply_details.map do |rate_reply|
|
13
|
+
CanadaPost::Shipment.new(rate_reply)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
error_message = if response[:messages]
|
17
|
+
response[:messages][:message][:description]
|
18
|
+
else
|
19
|
+
'api_response.response'
|
20
|
+
end
|
21
|
+
raise ShipmentError, error_message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def shipment_headers
|
28
|
+
api_url += "rs/{mailed by customer}/{mobo}/shipment"
|
29
|
+
end
|
30
|
+
|
31
|
+
def shipment_headers
|
32
|
+
{
|
33
|
+
'Content-type' => 'application/vnd.cpc.shipment-v7+xml',
|
34
|
+
'Accept' => 'application/vnd.cpc.shipment-v7+xml'
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_xml
|
39
|
+
ns = "http://www.canadapost.ca/ws/shipment-v7"
|
40
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
41
|
+
xml.send(:"mailing-scenario", xmlns: ns) {
|
42
|
+
add_requested_shipment(xml)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
builder.doc.root.to_xml
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_requested_shipment(xml)
|
49
|
+
xml.send(:"customer-number", @customer_number)
|
50
|
+
add_package(xml)
|
51
|
+
add_services(xml)
|
52
|
+
add_shipper(xml)
|
53
|
+
add_recipient(xml)
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_shipper(xml)
|
57
|
+
xml.send(:"origin-postal-code", @shipper[:postal_code])
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_recipient(xml)
|
61
|
+
xml.destination {
|
62
|
+
add_destination(xml)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_destination(xml)
|
67
|
+
if @recipient[:country_code] == "CA"
|
68
|
+
xml.domestic {
|
69
|
+
xml.send(:"postal-code", @recipient[:postal_code])
|
70
|
+
}
|
71
|
+
elsif @recipient[:country_code] == "US"
|
72
|
+
xml.send(:"united-states") {
|
73
|
+
xml.send(:"zip-code", @recipient[:postal_code])
|
74
|
+
}
|
75
|
+
else
|
76
|
+
xml.international {
|
77
|
+
xml.send(:"country-code", @recipient[:country_code])
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_services(xml)
|
83
|
+
if @service_type
|
84
|
+
xml.services {
|
85
|
+
xml.send(:"service-code", @service_type)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def success?(response)
|
91
|
+
response[:price_quotes]
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'canada_post/request/manifest'
|
2
|
+
module CanadaPost
|
3
|
+
module Request
|
4
|
+
class Shipping < Base
|
5
|
+
|
6
|
+
attr_accessor :sender, :destination, :package, :notification, :preferences, :settlement_info, :group_id, :mailing_date, :contract_id
|
7
|
+
|
8
|
+
def initialize(credentials, options={})
|
9
|
+
@credentials = credentials
|
10
|
+
if options.present?
|
11
|
+
@sender = options[:sender]
|
12
|
+
@destination = options[:destination]
|
13
|
+
@package = options[:package]
|
14
|
+
@notification = options[:notification]
|
15
|
+
@preferences = options[:preferences]
|
16
|
+
@settlement_info = options[:settlement_info]
|
17
|
+
@group_id = options[:group_id]
|
18
|
+
@mobo = options[:mobo]
|
19
|
+
if @mobo.present? && @mobo[:customer_number].present?
|
20
|
+
@mobo_customer = @mobo[:customer_number]
|
21
|
+
@shipping_auth = {username: @mobo[:username], password: @mobo[:password]}
|
22
|
+
else
|
23
|
+
@mobo_customer = @credentials.customer_number
|
24
|
+
@shipping_auth = {username: credentials.username, password: credentials.password}
|
25
|
+
end
|
26
|
+
@customer_number = @credentials.customer_number
|
27
|
+
@mailing_date = options[:mailing_date]
|
28
|
+
@contract_id = options[:contract_id]
|
29
|
+
@service_code = options[:service_code]
|
30
|
+
end
|
31
|
+
super(credentials)
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_request
|
35
|
+
shipment_response = Hash.new
|
36
|
+
api_response = self.class.post(
|
37
|
+
shipping_url,
|
38
|
+
body: build_xml,
|
39
|
+
headers: shipping_header,
|
40
|
+
basic_auth: @shipping_auth
|
41
|
+
)
|
42
|
+
shipping_response = process_response(api_response)
|
43
|
+
shipment_response[:create_shipping] = shipping_response
|
44
|
+
unless shipping_response[:errors].present?
|
45
|
+
manifest_params = {
|
46
|
+
destination: @destination,
|
47
|
+
phone: @sender[:phone],
|
48
|
+
group_id: @group_id
|
49
|
+
}
|
50
|
+
manifest_response = CanadaPost::Request::Manifest.new(@credentials, manifest_params).process_request
|
51
|
+
shipment_response[:transmit_shipping] = manifest_response
|
52
|
+
end
|
53
|
+
shipment_response
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_price(shipping_id, mobo = @credentials.customer_number)
|
57
|
+
price_url = api_url + "/rs/#{@credentials.customer_number}/#{mobo}/shipment/#{shipping_id}/price"
|
58
|
+
api_response = self.class.get(
|
59
|
+
price_url,
|
60
|
+
headers: shipping_header,
|
61
|
+
basic_auth: @authorization
|
62
|
+
)
|
63
|
+
process_response(api_response)
|
64
|
+
end
|
65
|
+
|
66
|
+
def details(shipping_id, mobo = @credentials.customer_number)
|
67
|
+
details_url = api_url + "/rs/#{@credentials.customer_number}/#{mobo}/shipment/#{shipping_id}/details"
|
68
|
+
api_response = self.class.get(
|
69
|
+
details_url,
|
70
|
+
headers: shipping_header,
|
71
|
+
basic_auth: @authorization
|
72
|
+
)
|
73
|
+
process_response(api_response)
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_label(label_url)
|
77
|
+
self.class.get(
|
78
|
+
label_url,
|
79
|
+
headers: {
|
80
|
+
'Content-type' => 'application/pdf',
|
81
|
+
'Accept' => 'application/pdf'
|
82
|
+
},
|
83
|
+
basic_auth: @authorization
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def void_shipping(shipping_id, mobo = @credentials.customer_number)
|
88
|
+
void_url = api_url + "/rs/#{@credentials.customer_number}/#{mobo}/shipment/#{shipping_id}"
|
89
|
+
api_response = self.class.delete(
|
90
|
+
void_url,
|
91
|
+
headers: shipping_header,
|
92
|
+
basic_auth: @authorization
|
93
|
+
)
|
94
|
+
puts api_response
|
95
|
+
process_response(api_response)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def api_url
|
101
|
+
@credentials.mode == "production" ? PRODUCTION_URL : TEST_URL
|
102
|
+
end
|
103
|
+
|
104
|
+
def shipping_url
|
105
|
+
base_url = api_url
|
106
|
+
if @mobo.present? && @mobo[:customer_number].present?
|
107
|
+
base_url += "/rs/#{@mobo_customer}-#{@customer_number}/#{@mobo_customer}/shipment"
|
108
|
+
else
|
109
|
+
base_url += "/rs/#{@customer_number}/#{@customer_number}/shipment"
|
110
|
+
end
|
111
|
+
base_url
|
112
|
+
end
|
113
|
+
|
114
|
+
def shipping_header
|
115
|
+
header = {
|
116
|
+
'Content-type' => 'application/vnd.cpc.shipment-v7+xml',
|
117
|
+
'Accept' => 'application/vnd.cpc.shipment-v7+xml'
|
118
|
+
}
|
119
|
+
if @mobo.present? && @mobo[:customer_number].present?
|
120
|
+
header['Platform-id'] = @customer_number
|
121
|
+
end
|
122
|
+
header
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_xml
|
126
|
+
ns = "http://www.canadapost.ca/ws/shipment-v7"
|
127
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
128
|
+
xml.send(:"shipment", xmlns: ns) {
|
129
|
+
add_shipment_params(xml)
|
130
|
+
}
|
131
|
+
end
|
132
|
+
builder.doc.root.to_xml
|
133
|
+
end
|
134
|
+
|
135
|
+
def add_shipment_params(xml)
|
136
|
+
if @group_id.present?
|
137
|
+
xml.send(:'group-id', @group_id)
|
138
|
+
end
|
139
|
+
if @mailing_date.present?
|
140
|
+
xml.send(:'expected-mailing-date', @mailing_date)
|
141
|
+
end
|
142
|
+
sender_zip = @sender[:address_details][:postal_code].present? ? @sender[:address_details][:postal_code].gsub(' ', '') : ''
|
143
|
+
rsp = @sender[:shipping_point].present? ? @sender[:shipping_point].gsub(' ', '') : sender_zip
|
144
|
+
xml.send(:'requested-shipping-point', rsp)
|
145
|
+
xml.send(:'delivery-spec') {
|
146
|
+
add_delivery_spec(xml)
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def add_delivery_spec(xml)
|
151
|
+
xml.send(:'service-code', @service_code) if @service_code.present?
|
152
|
+
xml.send(:'sender') {
|
153
|
+
add_sender(xml)
|
154
|
+
}
|
155
|
+
|
156
|
+
xml.send(:'destination') {
|
157
|
+
add_destination(xml)
|
158
|
+
}
|
159
|
+
|
160
|
+
add_package(xml)
|
161
|
+
xml.send(:'print-preferences') {
|
162
|
+
xml.send(:'output-format', '8.5x11')
|
163
|
+
}
|
164
|
+
|
165
|
+
xml.notification {
|
166
|
+
xml.send(:'email', @notification[:email])
|
167
|
+
xml.send(:'on-shipment', @notification[:on_shipment])
|
168
|
+
xml.send(:'on-exception', @notification[:on_exception])
|
169
|
+
xml.send(:'on-delivery', @notification[:on_delivery])
|
170
|
+
}
|
171
|
+
|
172
|
+
xml.preferences {
|
173
|
+
xml.send(:'show-packing-instructions', @preferences[:show_packing_instructions])
|
174
|
+
xml.send(:'show-postage-rate', @preferences[:show_postage_rate])
|
175
|
+
xml.send(:'show-insured-value', @preferences[:show_insured_value])
|
176
|
+
}
|
177
|
+
|
178
|
+
xml.send(:'settlement-info') {
|
179
|
+
contract_id = @credentials.mode == "production" ? @contract_id : TEST_CONTRACT_ID
|
180
|
+
xml.send(:'contract-id', contract_id)
|
181
|
+
if @mobo.present? && @mobo[:customer_number].present?
|
182
|
+
xml.send(:'paid-by-customer', @mobo_customer)
|
183
|
+
end
|
184
|
+
xml.send(:'intended-method-of-payment', 'Account')
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
def add_sender(xml)
|
189
|
+
xml.send(:'name', @sender[:name])
|
190
|
+
xml.send(:'company', @sender[:company])
|
191
|
+
xml.send(:'contact-phone', @sender[:phone])
|
192
|
+
xml.send(:'address-details') {
|
193
|
+
add_address(xml, @sender[:address_details])
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_destination(xml)
|
198
|
+
xml.send(:'name', @destination[:name])
|
199
|
+
xml.send(:'company', @destination[:company])
|
200
|
+
xml.send(:'address-details') {
|
201
|
+
add_address(xml, @destination[:address_details])
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
def add_address(xml, params)
|
206
|
+
xml.send(:'address-line-1', params[:address])
|
207
|
+
xml.send(:'city', params[:city])
|
208
|
+
xml.send(:'prov-state', params[:state])
|
209
|
+
xml.send(:'country-code', params[:country])
|
210
|
+
if params[:postal_code].present?
|
211
|
+
xml.send(:'postal-zip-code', params[:postal_code].gsub(' ', ''))
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def add_package(xml)
|
216
|
+
xml.send(:"parcel-characteristics") {
|
217
|
+
xml.unpackaged @package[:unpackaged].present? ? @package[:unpackaged] : false
|
218
|
+
xml.send(:"mailing-tube", @package[:mailing_tube].present? ? @package[:mailing_tube] : false)
|
219
|
+
xml.weight @package[:weight]
|
220
|
+
if @package[:dimensions]
|
221
|
+
xml.dimensions {
|
222
|
+
xml.height @package[:dimensions][:height].to_f.round(1)
|
223
|
+
xml.width @package[:dimensions][:width].to_f.round(1)
|
224
|
+
xml.length @package[:dimensions][:length].to_f.round(1)
|
225
|
+
}
|
226
|
+
end
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module CanadaPost
|
2
|
+
class Shipment
|
3
|
+
|
4
|
+
def initialize(options={})
|
5
|
+
@credentials = Credentials.new(options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def create(options = {})
|
9
|
+
Request::Shipping.new(@credentials, options).process_request
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_price(shipping_id)
|
13
|
+
Request::Shipping.new(@credentials).get_price(shipping_id)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_label(label_url)
|
17
|
+
Request::Shipping.new(@credentials).get_label(label_url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_manifest(url)
|
21
|
+
Request::Manifest.new(@credentials).get_manifest(url)
|
22
|
+
end
|
23
|
+
|
24
|
+
def registration_token
|
25
|
+
Request::Registration.new(@credentials).get_token
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_merchant_info(token)
|
29
|
+
Request::Registration.new(@credentials).merchant_info(token)
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_artifact(url)
|
33
|
+
manifest = Request::Manifest.new(@credentials).get_manifest(url)
|
34
|
+
if manifest[:errors].present?
|
35
|
+
return {
|
36
|
+
status: false,
|
37
|
+
error: manifest[:errors]
|
38
|
+
}
|
39
|
+
else
|
40
|
+
artifact_link = get_artifact_link(manifest[:manifest])
|
41
|
+
artifact = Request::Manifest.new(@credentials).get_artifact(artifact_link)
|
42
|
+
return {
|
43
|
+
status: true,
|
44
|
+
artifact: artifact
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_artifact_link(manifest)
|
50
|
+
links = manifest[:links]
|
51
|
+
if links.present?
|
52
|
+
links[:link].each do |link|
|
53
|
+
if link[:rel] == 'artifact'
|
54
|
+
return link[:href]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def details(shipping_id)
|
61
|
+
Request::Shipping.new(@credentials).details(shipping_id)
|
62
|
+
end
|
63
|
+
|
64
|
+
def rate(options={})
|
65
|
+
Request::Rate.new(@credentials, options).process_request
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'canada_post'
|
3
|
+
require 'canada_post/shipment'
|
4
|
+
require 'canada_post/client'
|
5
|
+
|
6
|
+
describe CanadaPost::Request::Manifest do
|
7
|
+
context 'missing required parameters' do
|
8
|
+
it 'does raise Rate exception' do
|
9
|
+
expect { CanadaPost::Client.new }.to raise_error(CanadaPost::RateError)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'required parameters present' do
|
14
|
+
it 'does create a valid instance' do
|
15
|
+
expect(CanadaPost::Client.new(canada_post_credentials)).to be_an_instance_of(CanadaPost::Client)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'Shipment shipping service' do
|
20
|
+
let(:canada_post_service) { CanadaPost::Client.new(canada_post_credentials) }
|
21
|
+
let(:destination) { {name: 'nazrul recp', company: 'your company', address_details: {address: 'test dest address', city: 'Ottawa', state: 'ON', country: 'CA', postal_code: 'K1P5Z9'}} }
|
22
|
+
let(:group_id) { {value: '5241556'} }
|
23
|
+
let(:phone) { {value: '34343435'} }
|
24
|
+
|
25
|
+
context 'shipping manifest', :vcr do
|
26
|
+
let(:manifest) {
|
27
|
+
canada_post_service.manifest(
|
28
|
+
destination: destination,
|
29
|
+
phone: phone[:value],
|
30
|
+
group_id: group_id[:value]
|
31
|
+
)
|
32
|
+
}
|
33
|
+
|
34
|
+
it 'Should return empty result' do
|
35
|
+
expect(manifest[:errors]).to eq 'All groups in the transmit request were empty or all shipments were excluded; there was nothing to transmit'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|