Skip to content
This repository has been archived by the owner on Jun 13, 2018. It is now read-only.

Cp contract shipping #559

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ env:
- ACTIVESHIPPING_CANADA_POST_PWS_CUSTOMER_NUMBER=2004381
- ACTIVESHIPPING_CANADA_POST_PWS_API_KEY=6e93d53968881714
- ACTIVESHIPPING_CANADA_POST_PWS_SECRET=0bfa9fcb9853d1f51ee57a
- ACTIVESHIPPING_CANADA_POST_PWS_CONTRACT=42708517
- ACTIVESHIPPING_USPS_LOGIN=677JADED7283
116 changes: 113 additions & 3 deletions lib/active_shipping/carriers/canada_post_pws.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class CanadaPostPWS < Carrier
ENDPOINT = "https://soa-gw.canadapost.ca/" # production

SHIPMENT_MIMETYPE = "application/vnd.cpc.ncshipment+xml"
CONTRACT_MIMETYPE = "application/vnd.cpc.shipment-v8+xml"
RATE_MIMETYPE = "application/vnd.cpc.ship.rate+xml"
TRACK_MIMETYPE = "application/vnd.cpc.track+xml"
REGISTER_MIMETYPE = "application/vnd.cpc.registration+xml"
Expand Down Expand Up @@ -107,6 +108,16 @@ def create_shipment(origin, destination, package, line_items = [], options = {})
CPPWSShippingResponse.new(false, "Missing Customer Number", {}, :carrier => @@name)
end

def create_contract_shipment(origin, destination, package, line_items = [], options = {}, return_details = {})
request_body = build_contract_shipment_request(origin, destination, package, line_items, options, return_details)
response = ssl_post(create_contract_shipment_url(options), request_body, headers(options, CONTRACT_MIMETYPE, CONTRACT_MIMETYPE))
parse_contract_shipment_response(response)
rescue ActiveUtils::ResponseError, ActiveShipping::ResponseError => e
error_response(e.response.body, CPPWSShippingResponse)
rescue MissingCustomerNumberError
CPPWSShippingResponse.new(false, "Missing Customer Number", {}, :carrier => @@name)
end

def retrieve_shipment(shipping_id, options = {})
response = ssl_post(shipment_url(shipping_id, options), nil, headers(options, SHIPMENT_MIMETYPE, SHIPMENT_MIMETYPE))
parse_shipment_response(response)
Expand Down Expand Up @@ -358,6 +369,7 @@ def build_tracking_events(events)
# :show_postage_rate
# :cod, :cod_amount, :insurance, :insurance_amount, :signature_required, :pa18, :pa19, :hfp, :dns, :lad
#
#
def build_shipment_request(origin, destination, package, line_items = [], options = {})
builder = Nokogiri::XML::Builder.new do |xml|
xml.public_send('non-contract-shipment', :xmlns => "http://www.canadapost.ca/ws/ncshipment") do
Expand All @@ -378,10 +390,48 @@ def build_shipment_request(origin, destination, package, line_items = [], option
builder.to_xml
end

# return_details
# :service_code, :return_recipient {:name, :company, :address_details {:address_line_1...}}

def build_contract_shipment_request(origin, destination, package, line_items = [], options = {}, return_details = {})
builder = Nokogiri::XML::Builder.new do |xml|
xml.public_send('shipment', :xmlns => "http://www.canadapost.ca/ws/shipment-v8") do
shipment_node(xml, options)
xml.public_send('delivery-spec') do
shipment_service_code_node(xml, options)
shipment_sender_node(xml, origin, options)
shipment_destination_node(xml, destination, options)
shipment_options_node(xml, options)
shipment_parcel_node(xml, package)
shipment_notification_node(xml, options)
shipment_preferences_node(xml, options)
references_node(xml, options) # optional > user defined custom notes
shipment_customs_node(xml, destination, line_items, options)
settlement_node(xml, options)
# COD Remittance defaults to sender
end
shipment_return_node(xml, return_details, options)
end
end
builder.to_xml
end

def shipment_node(xml, options)
xml.public_send('transmit-shipment', options[:transmit] || true)
xml.public_send('requested-shipping-point', options[:shipping_point])
end

def shipment_service_code_node(xml, options)
xml.public_send('service-code', options[:service])
end

def settlement_node(xml, options)
xml.public_send('settlement-info') do
xml.public_send('contract-id', options[:contract_number])
xml.public_send('intended-method-of-payment', options[:payment_type] || 'Account' )
end
end

def shipment_sender_node(xml, sender, options)
location = location_from_hash(sender)
xml.public_send('sender') do
Expand All @@ -393,7 +443,7 @@ def shipment_sender_node(xml, sender, options)
xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
xml.public_send('city', location.city)
xml.public_send('prov-state', location.province)
# xml.public_send('country-code', location.country_code)
xml.public_send('country-code', location.country_code) unless location.country_code.blank?
xml.public_send('postal-zip-code', get_sanitized_postal_code(location))
end
end
Expand Down Expand Up @@ -473,6 +523,28 @@ def shipment_customs_node(xml, destination, line_items, options)
end
end

# return_details
# :service_code, :return_recipient {:name, :company, :address_details {:address_line_1...}}

def shipment_return_node(xml, return_details, options)
return if return_details.blank?
location = location_from_hash(return_details[:return_recipient][:address_details])
xml.public_send('return-spec') do
xml.public_send('service-code', return_details[:service_code] )
xml.public_send('return-recipient') do
xml.public_send('name', return_details[:name]) if return_details[:name]
xml.public_send('company', return_details[:company]) if return_details[:company]
xml.public_send('address-details') do
xml.public_send('address-line-1', location.address1)
xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
xml.public_send('city', location.city)
xml.public_send('prov-state', location.province) unless location.province.blank?
xml.public_send('postal-zip-code', get_sanitized_postal_code(location))
end
end
end
end

def shipment_parcel_node(xml, package, options = {})
weight = sanitize_weight_kg(package.kilograms.to_f)
xml.public_send('parcel-characteristics') do
Expand All @@ -484,7 +556,7 @@ def shipment_parcel_node(xml, package, options = {})
xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
end
xml.public_send('document', false)
#xml.public_send('document', false)
else
xml.public_send('document', true)
end
Expand All @@ -494,6 +566,23 @@ def shipment_parcel_node(xml, package, options = {})
end
end

def parse_contract_shipment_response(response)
doc = Nokogiri.XML(response)
doc.remove_namespaces!
raise ActiveShipping::ResponseError, "No Shipping" unless doc.at('shipment-info')
receipt_url = doc.root.at_xpath("links/link[@rel='receipt']")['href'] unless doc.root.at_xpath("links/link[@rel='receipt']").blank?
return_label_url = doc.root.at_xpath("links/link[@rel='returnLabel']")['href'] unless doc.root.at_xpath("links/link[@rel='returnLabel']").blank?
options = {
:shipping_id => doc.root.at('shipment-id').text,
:details_url => doc.root.at_xpath("links/link[@rel='details']")['href'],
:label_url => doc.root.at_xpath("links/link[@rel='label']")['href'],
:receipt_url => receipt_url,
:return_label_url => return_label_url
}
options[:tracking_number] = doc.root.at('tracking-pin').text if doc.root.at('tracking-pin')
CPPWSContractShippingResponse.new(true, "", {}, options)
end

def parse_shipment_response(response)
doc = Nokogiri.XML(response)
doc.remove_namespaces!
Expand All @@ -505,7 +594,6 @@ def parse_shipment_response(response)
:receipt_url => doc.root.at_xpath("links/link[@rel='receipt']")['href'],
}
options[:tracking_number] = doc.root.at('tracking-pin').text if doc.root.at('tracking-pin')

CPPWSShippingResponse.new(true, "", {}, options)
end

Expand Down Expand Up @@ -604,6 +692,15 @@ def create_shipment_url(options)
end
end

def create_contract_shipment_url(options)
raise MissingCustomerNumberError unless customer_number = options[:customer_number]
if @platform_id.present?
endpoint + "rs/#{customer_number}-#{@platform_id}/shipment"
else
endpoint + "rs/#{customer_number}/#{customer_number}/shipment"
end
end

def shipment_url(shipping_id, options = {})
raise MissingCustomerNumberError unless customer_number = options[:customer_number]
if @platform_id.present?
Expand Down Expand Up @@ -873,6 +970,19 @@ def initialize(success, message, params = {}, options = {})
end
end

class CPPWSContractShippingResponse < ShippingResponse
include CPPWSErrorResponse
attr_reader :label_url, :details_url, :receipt_url, :return_label_url
def initialize(success, message, params = {}, options = {})
handle_error(message, options)
super
@label_url = options[:label_url]
@details_url = options[:details_url]
@receipt_url = options[:receipt_url]
@return_label_url = options[:return_label_url]
end
end

class CPPWSRegisterResponse < Response
include CPPWSErrorResponse
attr_reader :token_id
Expand Down
1 change: 1 addition & 0 deletions test/credentials.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ canada_post_pws:
customer_number: <%= ENV['ACTIVESHIPPING_CANADA_POST_PWS_CUSTOMER_NUMBER'] %>
api_key: <%= ENV['ACTIVESHIPPING_CANADA_POST_PWS_API_KEY'] %>
secret: <%= ENV['ACTIVESHIPPING_CANADA_POST_PWS_SECRET'] %>
contract_number: <%= ENV['ACTIVESHIPPING_CANADA_POST_PWS_CONTRACT'] %>

canada_post_pws_platform:
platform_id: <%= ENV['ACTIVESHIPPING_CANADA_POST_PWS_PLATFORM_PLATFORM_ID'] %>
Expand Down
60 changes: 51 additions & 9 deletions test/remote/canada_post_pws_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

class RemoteCanadaPostPWSTest < ActiveSupport::TestCase
# All remote tests require Canada Post development environment credentials
# When using an account with a contract number you should add it to the opts hash

include ActiveShipping::Test::Credentials
include ActiveShipping::Test::Fixtures

Expand All @@ -14,7 +16,9 @@ def setup

@line_item1 = line_item_fixture

@shipping_opts1 = { :dc => true, :cov => true, :cov_amount => 100.00, :aban => true }
@shipping_opts1 = {:cod => true, :dc => true, :cov => true, :cov_amount => 100.00, :aban => true }

@shipping_opts2 = {so: true, cov: true, cov_amount: 999.99, transmit: true, shipping_point: 'R3W1S1'}

@home_params = {
:name => "John Smith",
Expand All @@ -23,11 +27,22 @@ def setup
:address1 => "123 Elm St.",
:city => 'Ottawa',
:province => 'ON',
:country => 'CA',
:postal_code => 'K1P 1J1'
}
@home = Location.new(@home_params)

@contract_home_params = {
:name => "John Smith",
:company => "test",
:phone => "613-555-1212",
:address1 => "123 Elm St.",
:city => 'Ottawa',
:province => 'ON',
:country => 'CA',
:postal_code => 'K1P 1J1'
}
@contract_home = Location.new(@contract_home_params)

@dom_params = {
:name => "John Smith Sr.",
:company => "",
Expand Down Expand Up @@ -77,6 +92,7 @@ def setup
@cp.logger = Logger.new(StringIO.new)

@customer_number = @login[:customer_number]
@contract_number = @login[:contract_number]

@DEFAULT_RESPONSE = {
:shipping_id => "406951321983787352",
Expand Down Expand Up @@ -121,25 +137,51 @@ def test_tracking_when_no_tracking_info_raises_exception
end

def test_create_shipment
skip "Failing with 'Contract Number is a required field' after API change, skipping because no clue how to fix, might need different creds"
skip "contract number in credentials" if @contract_number
opts = {:customer_number => @customer_number, :service => "DOM.XP"}
response = @cp.create_shipment(@home_params, @dom_params, @pkg1, @line_item1, opts)
assert_kind_of CPPWSShippingResponse, response
assert_match /\A\d{17}\z/, response.shipping_id
assert_match /\A\d{18}\z/, response.shipping_id
assert_equal "123456789012", response.tracking_number
assert_match "https://ct.soa-gw.canadapost.ca/ers/artifact/", response.label_url
assert_match "https://ct.soa-gw.canadapost.ca/rs/artifact/", response.label_url
assert_match @login[:api_key], response.label_url
end

def test_create_shipment_with_options
skip "Failing with 'Contract Number is a required field' after API change, skipping because no clue how to fix, might need different creds"
skip "contract number in credentials" if @contract_number
opts = {:customer_number => @customer_number, :service => "USA.EP"}.merge(@shipping_opts1)
response = @cp.create_shipment(@home_params, @dest_params, @pkg1, @line_item1, opts)

assert_kind_of CPPWSShippingResponse, response
assert_match /\A\d{17}\z/, response.shipping_id
assert_match /\A\d{18}\z/, response.shipping_id
assert_equal "123456789012", response.tracking_number
assert_match "https://ct.soa-gw.canadapost.ca/rs/artifact/", response.label_url
assert_match @login[:api_key], response.label_url
end

def test_create_contract_shipment_with_options
skip "no contract number in credentials" unless @contract_number
opts = {:customer_number => @customer_number, :service => "DOM.XP", contract_number: @contract_number}.merge(@shipping_opts2)
response = @cp.create_contract_shipment(@contract_home_params, @dom_params, @pkg1, @line_item1, opts)
assert_kind_of CPPWSContractShippingResponse, response
assert_match /\A\d{18}\z/, response.shipping_id
assert_equal "123456789012", response.tracking_number
assert_match "https://ct.soa-gw.canadapost.ca/rs/artifact/", response.label_url
assert_match @login[:api_key], response.label_url
end

def test_create_contract_shipment_with_return_label_and_options
skip "no contract number in credentials" unless @contract_number

opts = {:customer_number => @customer_number, :service => "DOM.XP", contract_number: @contract_number}.merge(@shipping_opts2)

return_details = {service_code: 'DOM.RP', return_recipient: { address_details: @contract_home_params } }

response = @cp.create_contract_shipment(@contract_home_params, @dom_params, @pkg1, @line_item1, opts, return_details)
assert_kind_of CPPWSContractShippingResponse, response
assert_match /\A\d{18}\z/, response.shipping_id
assert_equal "123456789012", response.tracking_number
assert_match "https://ct.soa-gw.canadapost.ca/ers/artifact/", response.label_url
assert_match "https://ct.soa-gw.canadapost.ca/rs/artifact/", response.label_url
assert_match "https://ct.soa-gw.canadapost.ca/rs/artifact/", response.return_label_url
assert_match @login[:api_key], response.label_url
end

Expand Down
25 changes: 25 additions & 0 deletions test/unit/carriers/canada_post_pws_shipping_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def setup
:so => true, :pa18 => true}

@default_options = {:customer_number => '123456'}
@default_contract_options = {:customer_number => '123456', :contract_number => '42708517'}

@DEFAULT_RESPONSE = {
:shipping_id => "406951321983787352",
Expand Down Expand Up @@ -93,6 +94,30 @@ def test_build_shipment_request_for_domestic
refute request.blank?
end

def test_build_contract_shipment_request_for_domestic
options = @default_contract_options.dup
request = @cp.build_contract_shipment_request(@home_params, @dom_params, @pkg1, @line_item1, options)
refute request.blank?
end

def test_build_contract_shipment_with_return_request_for_domestic
options = @default_contract_options.dup
return_details = {service_code: 'DOM.RP', return_recipient: { address_details: @home_params } }

request = @cp.build_contract_shipment_request(@home_params, @dom_params, @pkg1, @line_item1, options, return_details)
refute request.blank?

doc = Nokogiri.XML(request)
doc.remove_namespaces!

assert root_node = doc.at('shipment')
assert delivery_spec = root_node.at('delivery-spec')
assert destination = delivery_spec.at('destination')
assert address_details = destination.at('address-details')
assert_equal 'CA', address_details.at('country-code').text
assert return_spec = root_node.at('return-spec')
end

def test_build_shipment_request_for_US
options = @default_options.dup
request = @cp.build_shipment_request(@home_params, @us_params, @pkg1, @line_item1, options)
Expand Down