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

Add AustraliaPost Carrier #363

Merged
merged 2 commits into from
Jun 2, 2016
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Active Shipping is currently being used and improved in a production environment
* [Shipwire](http://www.shipwire.com)
* [Stamps](http://www.stamps.com)
* [Kunaki](http://www.kunaki.com)
* [Australia Post](http://auspost.com.au/)

## Installation

Expand Down
1 change: 1 addition & 0 deletions lib/active_shipping/carriers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ def find(name)
ActiveShipping::Carriers.register :CanadaPostPWS, 'active_shipping/carriers/canada_post_pws'
ActiveShipping::Carriers.register :Stamps, 'active_shipping/carriers/stamps'
ActiveShipping::Carriers.register :Correios, 'active_shipping/carriers/correios'
ActiveShipping::Carriers.register :AustraliaPost, 'active_shipping/carriers/australia_post'
250 changes: 250 additions & 0 deletions lib/active_shipping/carriers/australia_post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
require 'active_support/core_ext/object/to_query'

module ActiveShipping
class AustraliaPost < Carrier
cattr_reader :name
@@name = 'Australia Post'

HOST = 'digitalapi.auspost.com.au'

PARCEL_ENDPOINTS = {
service: {
domestic: '/postage/parcel/domestic/service.json',
international: '/postage/parcel/international/service.json'
},
calculate: {
domestic: '/postage/parcel/domestic/calculate.json',
international: '/postage/parcel/international/calculate.json'
}
}.freeze

def requirements
[:api_key]
end

def find_rates(origin, destination, packages, options = {})
packages = Array(packages)

service_requests = packages.map do |package|
service_request = ServiceRequest.new(origin, destination, package, options)

service_request.parse(commit(service_request.url))
service_request
end

combined_response = CombinedResponse.new(origin, destination, packages, service_requests)

RateResponse.new(true, 'success', combined_response.params, combined_response.options)
end

def calculate_rates(origin, destination, packages, service_code, options = {})
packages = Array(packages)

calculate_requests = packages.map do |package|
calculate_request = CalculateRequest.new(origin, destination, package, service_code, options)

calculate_request.parse(commit(calculate_request.url))
calculate_request
end

combined_response = CombinedResponse.new(origin, destination, packages, calculate_requests)

RateResponse.new(true, 'success', combined_response.params, combined_response.options)
end

private

def commit(request_url)
ssl_get(request_url, headers)

rescue ActiveUtils::ResponseError, ActiveShipping::ResponseError => e
data = JSON.parse(e.response.body)
error_message = data['error'] && data['error']['errorMessage'] ? data['error']['errorMessage'] : 'unknown'

RateResponse.new(false, error_message, data)
end

def headers
{
'Content-type' => 'application/json',
'auth-key' => @options[:api_key]
}
end

class CombinedResponse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


def initialize(origin, destination, packages, requests)
@requests = requests
@origin = origin
@destination = destination
@packages = packages
end

def options
{
rates: rates,
raw_responses: @requests.map(&:raw_response),
request: @requests.map(&:url)
}
end

def params
{
responses: @requests.map(&:response)
}
end

private

def rate_options(rates)
{
service_name: rates.first[:service_name],
service_code: rates.first[:service_code],
total_price: rates.sum { |rate| rate[:total_price] },
currency: 'AUD',
delivery_time_text: rates.first[:delivery_time_text]
}
end

def rates
rates = @requests.map(&:rates).flatten

rates.group_by { |rate| rate[:service_name] }.map do |service_name, service_rates|
next unless service_rates.size == @packages.size

AustraliaPostRateEstimate.new(@origin, @destination, AustraliaPost.name, service_name, rate_options(service_rates))
end.compact
end

end

class AustraliaPostRequest
attr_reader :raw_response
attr_reader :response
attr_reader :rates

def initialize(origin, destination, package, options)
@origin = Location.from(origin)
@destination = Location.from(destination)
@package = package
@rates = []
@options = options
end

def url
endpoint = domestic_destination? ? @endpoints[:domestic] : @endpoints[:international]
params = domestic_destination? ? domestic_params : international_params

URI::HTTPS.build(host: HOST, path: endpoint, query: params.to_query).to_s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Way better.

end

def parse(data)
@raw_response = data
@response = JSON.parse(data)
end

protected

def domestic_destination?
@destination.country_code == 'AU'
end

def domestic_params
{
length: @package.cm(:length),
width: @package.cm(:width),
height: @package.cm(:height),
weight: @package.weight.in_kg.to_f.round(2),
from_postcode: @origin.postal_code,
to_postcode: @destination.postal_code
}
end

def international_params
{
weight: @package.weight.in_kg.to_f.round(2),
country_code: @destination.country_code
}
end

end

class ServiceRequest < AustraliaPostRequest

def initialize(origin, destination, package, options)
super
@endpoints = PARCEL_ENDPOINTS[:service]
end

def parse(data)
super

@rates = response['services']['service'].map do |service|
{
service_name: service['name'],
service_code: service['code'],
total_price: service['price'].to_f,
currency: 'AUD'
}
end
end

end

class CalculateRequest < AustraliaPostRequest
attr_reader :service_code

def initialize(origin, destination, package, service_code, options)
super(origin, destination, package, options)

@service_code = service_code
@endpoints = PARCEL_ENDPOINTS[:calculate]
end

def parse(data)
super
postage_result = response['postage_result']

@rates = [{
service_name: postage_result['service'],
service_code: service_code,
total_price: postage_result['total_cost'].to_f,
currency: 'AUD',
delivery_time_text: postage_result['delivery_time']
}]
end

private

def calculate_params
{
service_code: @service_code,
option_code: @options[:option_code],
suboption_code: @options[:suboption_code],
extra_cover: @options[:extra_cover]
}.
# INFO: equivalent of .compact
select { |_, value| !value.nil? }
end

def domestic_params
super.merge(calculate_params)
end

def international_params
super.merge(calculate_params)
end

end

class AustraliaPostRateEstimate < RateEstimate
attr_reader :delivery_time_text

def initialize(origin, destination, carrier, service_name, options = {})
super
@delivery_time_text = options[:delivery_time_text]
end

end
end
end
3 changes: 3 additions & 0 deletions test/credentials.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ stamps:
username: <%= ENV['ACTIVESHIPPING_STAMPS_USERNAME'] %>
password: <%= ENV['ACTIVESHIPPING_STAMPS_PASSWORD'] %>

australia_post:
api_key: <%= ENV['ACTIVESHIPPING_AUSTRALIA_POST_API_KEY'] %>

# fedex_freight:
# account: FedExFreightAccountNumber
# shipping_address1: FedExShippingAddress1
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/json/australia_post/calculate_domestic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"postage_result": {
"service": "Express Post",
"delivery_time": "Guaranteed Next Business Day within the Express Post network (If posted on any business day Monday to Friday in accordance with the conditions set out on the item).",
"total_cost": "10.20",
"costs": {
"cost": {
"item": "Express Post",
"cost": "10.20"
}
}
}
}
19 changes: 19 additions & 0 deletions test/fixtures/json/australia_post/calculate_domestic_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"postage_result": {
"service": "Express Post",
"delivery_time": "Guaranteed Next Business Day within the Express Post network (If posted on any business day Monday to Friday in accordance with the conditions set out on the item).",
"total_cost": "14.70",
"costs": {
"cost": [{
"item": "Express Post",
"cost": "10.20"
}, {
"item": "Standard Service",
"cost": "0.00"
}, {
"item": "Extra Cover",
"cost": "4.50"
}]
}
}
}
12 changes: 12 additions & 0 deletions test/fixtures/json/australia_post/calculate_international.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"postage_result": {
"service": "Courier",
"total_cost": "87.36",
"costs": {
"cost": {
"item": "Courier",
"cost": "87.36"
}
}
}
}
15 changes: 15 additions & 0 deletions test/fixtures/json/australia_post/calculate_international_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"postage_result": {
"service": "Courier",
"total_cost": "87.36",
"costs": {
"cost": [{
"item": "Courier",
"cost": "87.36"
}, {
"item": "SMS track advice",
"cost": "0.00"
}]
}
}
}
5 changes: 5 additions & 0 deletions test/fixtures/json/australia_post/error_message.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"error": {
"errorMessage": "Please enter From postcode."
}
}
Loading