Compare Courier Services or Send a Parcel with Packlink. This is the unofficial Crystal shard for Packlink.
Add packlink as a depencency to your application's shard.yml
:
dependencies:
packlink:
github: wout/packlink.cr
Then run shards install
.
To configure it globally, create an initializer and add the following line:
Packlink.configure do |config|
config.api_key = "<your-api-key>"
config.environment = "sandbox" # or "production"
end
You can also include a client instance in each request you make:
client = Packlink::Client.new("<your-api-key>")
begin
shipments = Packlink::Shipment
.from("GB", "BN2 1JJ")
.to("BE", 2000)
.package({width: 40, height: 30, length: 25, weight: 5})
.all(client: client)
rescue e : Packlink::RequestException
puts e.message
end
If you need to do multiple calls with the same API key, use the following helper:
Packlink::Client.with_api_key("<your-api-key>") do |packlink|
begin
from_gb_to_be = packlink.shipment
.from("GB", "BN2 1JJ").to("BE", 2000)
.package({width: 40, height: 30, length: 25, weight: 5}).all
from_de_to_fr = packlink.shipment
.package({width: 40, height: 30, length: 25, weight: 5})
.package({width: 10, height: 10, length: 10, weight: 1})
.from("DE", 10587).to("FR", 75013).all
rescue e : Packlink::RequestException
puts e.message
end
end
Available scoped calls for a given API key are:
Packlink::Client.with_api_key("<your-api-key>") do |packlink|
packlink.callback.register(*args)
packlink.customs.pdf(*args)
packlink.draft.create(*args)
packlink.dropoff.all(*args)
packlink.label.all(*args)
packlink.order.create(*args)
packlink.service.find(*args)
packlink.service.from(*args)
packlink.service.to(*args)
packlink.service.package(*args)
packlink.shipment.find(*args)
packlink.tracking.history(*args)
end
User management is only required if you are creating accounts for other users. For example, in a situation where other users can create an account for Packlink through your platform or plugin.
temporary_token = Packlink::Register.user({
email: "myaccount@packlink.com",
estimated_delivery_volume: "1 - 10",
ip: "123.123.123.123",
password: "myPassword",
phone: "+447987654321",
platform: "PRO",
platform_country: "GB",
policies: {
terms_and_conditions: true,
data_processing: true,
marketing_emails: true,
marketing_calls: true,
},
referral: {
onboarding_product: "dummy",
onboarding_sub_product: "sub_dummy",
},
source: "https://urlwhereregistrationoffered",
})
puts temporary_token # => e0f90eacfa678e20051c3a5bc2bcc05a...
Using the temporary token obtained at registration, check if the user is activated.
active = Packlink::User.active?("e0f90eac...")
If the user is not active, the permanent api token can be obtained as follows:
unless Packlink::User.active?("e0f90eac...")
permanent_token = Packlink::User.activate("e0f90eac...")
puts permanent_token # => fa678e20...
end
If a given user already has a Packlink Pro account, a new API key can be generated:
token = Packlink::Auth.generate({
email: "myaccount@packlink.com",
password: "myPassword",
platform: "pro",
platform_country: "gb",
})
puts token # => fa678e20...
If a given user has an account but forgot their password, a password reset link can be requested:
Packlink::Auth.reset_password({
email: "myaccount@packlink.com",
platform: "pro",
platform_country: "gb",
})
Note: This will never fail and there is no response. If the email address exists, an email will arrive. If not, nothing will happen.
You need a source (from
), destination (to
) and at least one package
:
services = Packlink::Service
.from("GB", "BN2 1JJ")
.to("BE", 9000)
.package({width: 15, height: 15, length: 15, weight: 1.5})
.all
service = services.first
service.id # => 20154
service.carrier_name # => "DPD"
service.name # => "Classic Kleinpaket"
service.price.total_price # => 3.94
service.price.currency # => "EUR"
service.transit_hours # => "24"
service.available_dates["2020/03/30"].from # => "08:00"
service.available_dates["2020/03/30"].till # => "18:00"
service.available_dates["2020/03/30"].to_s # => "08:00-18:00"
...
Note: For a full list if available fields, check the services spec fixture.
You can also add multiple packages and the order of the method chain is not important:
services = Packlink::Service
.package({width: 40, height: 30, length: 25, weight: 5})
.from("GB", "BN2 1JJ")
.package({width: 15, height: 15, length: 15, weight: 1.5})
.to("BE", 9000)
.package({width: 20, height: 15, length: 10, weight: 3})
.all
With an instance of a Packlink::Package
object:
package = Packlink::Package.build({
width: 40,
height: 30,
length: 25,
weight: 5
})
services = Packlink::Service
.package(package)
.from("GB", "BN2 1JJ")
.to("BE", 9000)
.all
For more clarity, or a different order, use named arguments:
services = Packlink::Service
.package(package)
.from(country: "GB", zip: "BN2 1JJ")
.to(country: "BE", zip: 9000)
.all
Finally, you can also avoid the method chain and use a NamedTuple
or Hash
:
services = Packlink::Service.all(query: {
from: {country: "DE", zip: 56457},
to: {country: "BE", zip: 9000},
packages: {
"0": {width: 10, height: 10, length: 10, weight: 1},
},
})
Or even:
services = Packlink::Service.all(query: {
from: {country: "DE", zip: 56457},
to: {country: "BE", zip: 9000},
packages: {
"0": Packlink::Package.build({width: 40, height: 30, length: 25, weight: 5}),
},
})
If you know the id of a service, its details can be fetched as follows:
service = Packlink::Service.find(20154)
Gives you a list of the ten closest dropoff points.
dropoffs = Packlink::Dropoff.all({
service_id: 21369,
country: "GB",
zip: "BN2 1JJ",
})
dropoff = dropoffs.first
dropoff.address # => "52 St. George's Road"
dropoff.city # => "Brighton"
dropoff.commerce_name # => "St. Georges News"
dropoff.id # => "S16271"
dropoff.lat # => 50.817563999999997
dropoff.long # => -0.118057
dropoff.opening_times.monday # => "06:00-21:00"
dropoff.phone # => "07461451073"
dropoff.zip # => "BN2 1EF"
Creates a new order. Each order can include several shipments.
An order consists of multiple parts, and that is how one should be built. It is
also possible to use one large Hash
or NamedTuple
. But using the method
below ensures type safety and completeness of the posted data.
# 1. Build one or more packages
package = Packlink::Package.build({
width: 15,
height: 15,
length: 10,
weight: 1
})
# 2. Build a source address
from_address = Packlink::Address.build({
city: "Cannes",
country: "FR",
email: "test@packlink.com",
name: "TestName",
phone: "0666559988",
state: "FR",
street1: "Suffren 3",
surname: "TestLastName",
zip_code: "06400",
})
# 3. Build a destination address
to_address = Packlink::Address.build({
city: "Paris",
country: "FR",
email: "test@packlink.com",
name: "TestName",
phone: "630465777",
state: "FR",
street1: "Avenue Marchal 1",
surname: "TestLastName",
zip_code: "75001",
})
# 3b. Build a customs object (only required for shipments outside the EU)
customs = Packlink::Shipment::Customs.build({
eori_number: "GB123456789000",
sender_personalid: "EX123456",
sender_type: "private",
shipment_type: "gift",
vat_number: "GB123456789",
items: [{
description_english: "Hairdryer",
quantity: 2,
weight: 1.3,
value: 33.5,
country_of_origin: "GB",
}],
})
# 4. Build shipment (can be multiple within one order)
shipment = Packlink::Order::Shipment.build({
from: from_address,
to: to_address,
packages: [package],
customs: customs,
content: "Test content",
contentvalue: 160,
dropoff_point_id: "062049",
service_id: 20149,
shipment_custom_reference: "69a280b2-f7db-11e6-915e-5c54c4398ed2",
source: "source_inbound",
})
# 5. Create order
order = Packlink::Order.create({
order_custom_reference: "Beautiful leggins from eBay",
shipments: [shipment]
})
# If everything went well, you will receive an order summary:
order.order_reference # => "DE00019732CF"
order.total_amount # => 4.9
line = order.shipments.first # => Packlink::Order::ShipmentLine
line.shipment_custom_reference # => "eBay_11993382332"
line.shipment_reference # => "DE000193392AB"
line.insurance_coverage_amount # => 750.0
line.total_price # => 4.9
line.receipt # => "http://url/to/receipt"
Creates a new draft shipment. Building a draft shipment is exactly the same as building a shipment for an order, except that in a draft, all fields are optional.
draft = Packlink::Draft.create({
from: from_address,
to: to_address,
packages: [package],
content: "Test content",
contentvalue: 160,
dropoff_point_id: "062049",
service_id: 20149,
shipment_custom_reference: "69a280b2-f7db-11e6-915e-5c54c4398ed2",
source: "source_inbound",
})
draft.shipment_reference # => "DE00019732CF"
Returns the shipping labels in PDF format (A4) for the given shipment reference:
labels = Packlink::Label.all("ES00019388AB")
puts labels.first # => "http://packlink.de/de/purchase/PostVenta/getLabelsByRef?ref=52cf..."
Note: In many cases, there will only be one shipping label.
Returns the shipping customs PDF url.
pdf = Packlink::Customs.pdf("DE2015API0000003515")
puts pdf # => "http://static.packitos.com/prodev-pro/customs/c24a19d1bf25df8..."
Returns the shipping details.
shipment = Packlink::Shipment.find("DE2015API0000003515")
shipment.base_price # => 15.85
shipment.carrier # => "Mondial Relay"
shipment.collection.city # => "Paris"
shipment.collection.name # => "Daniel Werner"
shipment.collection_date # => "2015-04-16"
shipment.collection_hour # => "00:00-24:00"
...
Note: For a full list if available fields, check the shipment spec fixture.
Returns the tracking history of your shipment.
history = Packlink::Tracking.history("ES00019388AB")
event = history.first
event.city # => "MIAMI"
event.created_at # => 2020-02-18 04:03:20.0 UTC : Time
event.description # => "DELIVERED"
event.timestamp # => 14242322
Store a url to notifiy shipment events. This call should be done only once per Packlink client since the configured url will be used to send status updates for all shipments of the client.
success = Packlink::Callback.register("https://www.urlexample.com/packlink")
puts success # => true
On the configured endpoints, you could process the events as follows:
# grab the posted JSON
json = post.body
# parse the event
event = Packlink::Callback::Event.from_json(json)
event.name # => "shipment.carrier.success"
event.created_at # => 2020-01-01 15:55:23.0 UTC : Time
event.data.shipment_custom_reference # => "eBay_11993382332589"
event.data.shipment_reference # => "DE567YH981230AA"
shipment.carrier.success
: Shipment registered successfully in carrier's system.shipment.carrier.fail
: Shipment failed to register in carrier's system.shipment.label.ready
: Labels ready to print.shipment.label.fail
: Labels have failed.shipment.tracking.update
: Shipment is in transit.shipment.delivered
: Shipment has been delivered.
- Fork it at https://github.com/wout/packlink.cr/fork
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
- wout - creator and maintainer