Aller au contenu

omnibus/canpar

Omnibus Canpar: rates, shipments and labels, tracking - the CanShip web services (SOAP).

Étage
Fournisseur · Omnibus
Version
2.x
Suite de conformité
branchée, CI non lue
Vérifié en réel
Son README ne le dit pas
Licence
MIT
composer require omnibus/canpar:2.x-dev

Canpar for omnisense/omnibus: rates, shipments with their labels, tracking and voids, through Canpar's CanShip web services (SOAP 1.1). The envelopes are written and read as XML over plain HTTP (ext-soap is not needed). The gateway calls:

Operation Service Operation name
rate CanparRatingService rateShipment, once per service
ship CanparAddonsService processShipment, then getLabels
track CanparTrackingService trackByBarcode
cancel CanparTrackingService, then CanparAddonsService trackByBarcode, then voidShipment

under https://canship.canpar.com/canshipws/services (live) or https://sandbox.canpar.com/canshipws/services (sandbox: true), each with SOAPAction: urn:<operation> and a 20-second timeout.

Installation

composer require omnibus/canpar

Requires PHP 8.2, omnisense/omnibus (2.x) and symfony/http-client (6.4, 7 or 8); given no HTTP client, the factory makes one with HttpClient::create(). In a Symfony application with Omnibus\Bridge\Symfony\OmnibusBundle, the factory is registered as soon as the package is installed.

Credentials

  • A Canpar account with CanShip web services access: the user id (an email address) and password, and the shipper number (the account number). The account manager gives the sandbox login.

The user id and password travel inside every request's body.

Options

Option Type Default Required
user_id string - yes the CanShip login
password string - yes its password
shipper_number string - yes the account number (shipper_num)
sandbox bool false no true: sandbox.canpar.com
rates list - no prices from configuration instead of rateShipment (the rates option): with it, rateShipment is not called

Shipment options (Shipment::$options):

Option Read by Default
services rate 1, 2, 3 to Canada; 4, 5 to the US; 6 elsewhere the service types to price
sender_province rate, ship '' the sender's two-letter province or state
recipient_province rate, ship '' the recipient's two-letter province or state
label_format ship PDF PDF, or ZPL for a thermal label

The address model has no province: it comes from the two options.

Capabilities

Request Supported
Rating yes (rateShipment), or configured rates with rates
Shipping yes
Tracking yes
Pickup no: Canpar delivers to the door
GetSlip yes: the label again, through the barcode's shipment id
Cancel yes (void)
Paperless no

Behaviour

Services. The service is Canpar's service type; labels come from Omnibus\Canpar\Mapping::SERVICES:

Type Label
1 Canpar Ground (the default to Canada)
2 Select
3 Overnight
4 USA
5 Select USA
6 International (the default elsewhere)
7 Select Letter
8 Overnight Letter
9 USA Letter

Another type is labelled Canpar <type>.

The shipment. Rating and shipping send the same shipment element: the shipper number, the shipping date and time (Shipment::$shippingDate, else now), the service type (Shipment::$service, else the default above), centimetres and kilograms, an email notification to the recipient when they have an email, the sender as pickup address and the recipient as delivery address (name: the company, else the person; attention: the person; two address lines, 40 characters each; city; province option; country; postcode in capitals without spaces; phone digits; email), one package per parcel (weight in kilograms with two decimals, at least 0.1; dimensions when all three are given; the declared value when set), and the reference (40 characters).

Rate. One rateShipment per service type (services, else the default for the destination, whatever Shipment::$service says), with the association, individual and invoice discounts applied. Each answer's shipment gives one rate: its total_charge in CAD, days its transit_time (none when 0). A service Canpar refuses is left out; when every service is refused, the last refusal is raised. Rates come cheapest first; toPickupPoint is false.

Ship. processShipment; the tracking number is the first package's barcode, and the shipment's id is kept for the label: getLabels with thermal true for ZPL. The label is the first one returned, base64-decoded, application/zpl or application/pdf. When getLabels fails, the booking still succeeds without content: slip() fetches the label again. One Label carries the first package's barcode for the whole shipment. trackingUrl is https://www.canpar.com/en/tracking/track.htm?barcode=<number>. A response without an id or a barcode is a CarrierException (Canpar booked no shipment.).

Track. trackByBarcode with the shipment's tracking; the locale is not used: the English description is taken (the French one when there is no English one). Events: local_date_time, city province as location, Canpar's code as code; sorted oldest first. The status is the latest event's, or DELIVERED when the answer has a delivered_date_time. An event's status, read from its code and its English description in this order:

Code Or the description contains TrackingStatus
DEL delivered DELIVERED
OFD out for delivery OUT_FOR_DELIVERY
available for pickup, held for pickup AVAILABLE_FOR_PICKUP
return RETURNED
attempt, exception, unable EXCEPTION
MAN manifest, shipment created PENDING
any other code IN_TRANSIT
no code UNKNOWN

Slip. trackByBarcode for the barcode's shipment id (none: a CarrierException, Canpar knows no shipment for <barcode>.), then getLabels for it, thermal for Label::ZPL: the first label, base64-decoded, application/pdf or application/zpl (none: Canpar gave no label for <barcode>.).

Cancel. Canpar voids by shipment id, not by barcode: trackByBarcode first finds the shipment's id (shipment_id, else id); none gives false. Then voidShipment with that id: true when it does not fail (its answer is not read further).

Example

<?php

require __DIR__.'/vendor/autoload.php';

use Omnibus\Canpar\CanparGatewayFactory;
use Omnibus\Model\Address;
use Omnibus\Model\Parcel;
use Omnibus\Model\Shipment;
use Symfony\Component\HttpClient\HttpClient;

$canpar = (new CanparGatewayFactory(HttpClient::create()))->create([
    'user_id' => getenv('CANPAR_USER'),
    'password' => getenv('CANPAR_PASSWORD'),
    'shipper_number' => getenv('CANPAR_SHIPPER'),
    'sandbox' => true,
]);

$shipment = new Shipment(
    new Address('Glitch Art', ['100 Queen St W'], 'M5H 2N2', 'Toronto', 'CA', company: 'Glitch Art', phone: '416-555-9876'),
    new Address('Ada Lovelace', ['1 Wellington St'], 'K1A 0A9', 'Ottawa', 'CA', email: 'ada@example.org'),
    [new Parcel(1500, 30, 20, 10, value: 4500)],
    service: '1',
    reference: 'CMD-1042',
    options: ['sender_province' => 'ON', 'recipient_province' => 'ON', 'label_format' => 'PDF'],
);

foreach ($canpar->rate($shipment) as $rate) {
    printf("%s %-14s %6.2f %s, %s days\n", $rate->service, $rate->label, $rate->amount / 100, $rate->currency, $rate->days ?? '?');
}

$label = $canpar->ship($shipment);
file_put_contents($label->trackingNumber.'.pdf', (string) $label->content);
echo $label->trackingNumber, ' ', $label->trackingUrl, "\n";

$tracking = $canpar->track($label->trackingNumber);
echo $tracking->status->value, ' ', $tracking->latest()?->location, "\n";

var_dump($canpar->cancel($label->trackingNumber));

In Symfony:

omnibus:
    gateways:
        canpar:
            factory: canpar
            options:
                user_id: '%env(CANPAR_USER)%'
                password: '%env(CANPAR_PASSWORD)%'
                shipper_number: '%env(CANPAR_SHIPPER)%'
                sandbox: '%env(bool:CANPAR_SANDBOX)%'

Errors

Every failure is an Omnibus\Exception\CarrierException whose carrier is canpar:

  • an error in Canpar's answer: its text (Canpar gives no code);
  • a SOAP fault: its faultstring;
  • an HTTP status of 400 or more, or an answer without its return element: HTTP <status>;
  • a body that is not XML: Canpar answered HTTP <status> with a body that is not SOAP.;
  • a network error: Canpar request failed: ...;
  • Canpar booked no shipment. when no id or barcode comes back.

A failed getLabels after the shipment is created is not raised (above). A missing user_id, password or shipper_number is an InvalidConfigException when the gateway is built; Pickup and Paperless raise RequestNotSupportedException before any call.

No failure carries the password: a network error's message has them masked as *** (the key parameters of a URL too), its previous is a copy of the HTTP client's exception (the same class and code), and no trace of the chain keeps its arguments, where the request's options stood (No secret in a failure).

Verified, and not

Tested offline: Tests/CanparGatewayTest.php (each service rated in its own call and the rates cheapest first with their labels and transit days, the login in the body, the postcode without its space, the weight in kilograms; a shipment processed and its label fetched with thermal false; tracking with its location and the delivered date; the void through the shipment id; no pickup points) and Tests/CanparContractTest.php, the family's conformance suite on recorded answers. The example above was run in the core's harness on those recorded answers.

Not verified: the README says the package is built from Canpar's published CanShip documentation and was not yet run against the sandbox; that needs the credentials above.

Responsibility

An independent, unofficial client, not affiliated with Canpar Express (TFI International). Whoever runs it complies with Canpar's terms (canpar.com terms of use): see the README's Responsibility section.

Proposer une modification docs/index.md