mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 09:48:40 +00:00
788 lines
28 KiB
PHP
788 lines
28 KiB
PHP
<?php
|
|
/**
|
|
* Jingga
|
|
*
|
|
* PHP Version 8.1
|
|
*
|
|
* @package phpOMS\Api\Shipping\UPS
|
|
* @copyright Dennis Eichhorn
|
|
* @license OMS License 2.0
|
|
* @version 1.0.0
|
|
* @link https://jingga.app
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
namespace phpOMS\Api\Shipping\UPS;
|
|
|
|
use phpOMS\Api\Shipping\AuthStatus;
|
|
use phpOMS\Api\Shipping\AuthType;
|
|
use phpOMS\Api\Shipping\ShippingInterface;
|
|
use phpOMS\Message\Http\HttpRequest;
|
|
use phpOMS\Message\Http\RequestMethod;
|
|
use phpOMS\Message\Http\Rest;
|
|
use phpOMS\System\MimeType;
|
|
use phpOMS\Uri\HttpUri;
|
|
|
|
/**
|
|
* Shipment API.
|
|
*
|
|
* @package phpOMS\Api\Shipping\UPS
|
|
* @license OMS License 2.0
|
|
* @link https://jingga.app
|
|
* @see https://developer.ups.com/api/reference/oauth/authorization-code?loc=en_US
|
|
* @see https://developer.ups.com/api/reference?loc=en_US
|
|
* @since 1.0.0
|
|
*/
|
|
final class UPSShipping implements ShippingInterface
|
|
{
|
|
/**
|
|
* Api version
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public const API_VERSION = 'v1';
|
|
|
|
/**
|
|
* API environment.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public static string $ENV = 'live';
|
|
|
|
/**
|
|
* API link to live/production version.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public const LIVE_URL = 'https://onlinetools.ups.com';
|
|
|
|
/**
|
|
* API link to test/sandbox version.
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public const SANDBOX_URL = 'https://wwwcie.ups.com';
|
|
|
|
/**
|
|
* The type of authentication that is supported.
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public const AUTH_TYPE = AuthType::AUTOMATIC_LOGIN | AuthType::MANUAL_LOGIN;
|
|
|
|
/**
|
|
* Minimum auth expiration time until re-auth.
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public const TIME_DELTA = 10;
|
|
|
|
/**
|
|
* Client id
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $client = '';
|
|
|
|
/**
|
|
* Login id
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $login = '';
|
|
|
|
/**
|
|
* Password
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $password = '';
|
|
|
|
/**
|
|
* Current auth token
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $token = '';
|
|
|
|
/**
|
|
* Current auth refresh token in case the token expires
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $refreshToken = '';
|
|
|
|
/**
|
|
* Api Key
|
|
*
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
public string $apiKey = '';
|
|
|
|
/**
|
|
* Token expiration.
|
|
*
|
|
* @var \DateTime
|
|
* @since 1.0.0
|
|
*/
|
|
public \DateTime $expire;
|
|
|
|
/**
|
|
* Refresh token expiration.
|
|
*
|
|
* @var \DateTime
|
|
* @since 1.0.0
|
|
*/
|
|
public \DateTime $refreshExpire;
|
|
|
|
/**
|
|
* Refresh token expiration.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->expire = new \DateTime('now');
|
|
$this->refreshExpire = new \DateTime('now');
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function authLogin(
|
|
string $login, string $password,
|
|
?string $client = null,
|
|
?string $payload = null
|
|
) : int
|
|
{
|
|
$this->client = $client ?? $this->client;
|
|
$this->login = $login;
|
|
$this->password = $password;
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/security/' . self::API_VERSION . '/oauth/token';
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
$request->setMethod(RequestMethod::POST);
|
|
$request->setData('grant_type', 'client_credentials');
|
|
$request->header->set('Content-Type', MimeType::M_POST);
|
|
|
|
if ($client !== null) {
|
|
$request->header->set('x-merchant-id', $client);
|
|
}
|
|
|
|
$request->header->set('Authorization', 'Basic ' . \base64_encode($login . ':' . $password));
|
|
|
|
$this->expire = new \DateTime('now');
|
|
|
|
$response = Rest::request($request);
|
|
|
|
switch ($response->header->status) {
|
|
case 400:
|
|
case 401:
|
|
$status = AuthStatus::FAILED;
|
|
break;
|
|
case 403:
|
|
$status = AuthStatus::BLOCKED;
|
|
break;
|
|
case 429:
|
|
$status = AuthStatus::LIMIT_EXCEEDED;
|
|
break;
|
|
case 200:
|
|
$this->token = $response->getDataString('access_token') ?? '';
|
|
$this->expire->setTimestamp($this->expire->getTimestamp() + ((int) $response->getData('expires_in')));
|
|
|
|
$status = AuthStatus::OK;
|
|
break;
|
|
default:
|
|
$status = AuthStatus::FAILED;
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function authRedirectLogin(
|
|
string $client,
|
|
?string $redirect = null,
|
|
array $payload = []
|
|
) : HttpRequest
|
|
{
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/security/' . self::API_VERSION . '/oauth/authorize';
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
|
|
$request->setMethod(RequestMethod::GET);
|
|
$request->setData('client_id', $client);
|
|
$request->setData('redirect_uri', $redirect);
|
|
$request->setData('response_type', 'code');
|
|
|
|
if (isset($payload['id'])) {
|
|
$request->setData('scope', $payload['id']);
|
|
}
|
|
|
|
return $request;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function tokenFromRedirect(
|
|
string $login, string $password,
|
|
HttpRequest $redirect
|
|
) : int
|
|
{
|
|
$code = $redirect->getData('code') ?? '';
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/security/' . self::API_VERSION . '/oauth/token';
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
$request->setMethod(RequestMethod::POST);
|
|
|
|
// @remark: One api documentation part says redirect_uri is required another says it's not required
|
|
// Personally I don't see why a redirect is required or even helpful. Will try without it!
|
|
$request->setData('grant_type', 'authorization_code');
|
|
$request->setData('code', $code);
|
|
$request->header->set('Content-Type', MimeType::M_POST);
|
|
$request->header->set('Authorization', 'Basic ' . \base64_encode($login . ':' . $password));
|
|
|
|
$this->expire = new \DateTime('now');
|
|
$this->refreshExpire = new \DateTime('now');
|
|
|
|
$response = Rest::request($request);
|
|
|
|
switch ($response->header->status) {
|
|
case 400:
|
|
case 401:
|
|
$status = AuthStatus::FAILED;
|
|
break;
|
|
case 403:
|
|
$status = AuthStatus::BLOCKED;
|
|
break;
|
|
case 429:
|
|
$status = AuthStatus::LIMIT_EXCEEDED;
|
|
break;
|
|
case 200:
|
|
$this->token = $response->getDataString('access_token') ?? '';
|
|
$this->refreshToken = $response->getDataString('refresh_token') ?? '';
|
|
|
|
$this->expire->setTimestamp($this->expire->getTimestamp() + ((int) $response->getData('expires_in')));
|
|
$this->refreshExpire->setTimestamp($this->refreshExpire->getTimestamp() + ((int) $response->getData('refresh_token_expires_in')));
|
|
|
|
$status = AuthStatus::OK;
|
|
break;
|
|
default:
|
|
$status = AuthStatus::FAILED;
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function refreshToken() : int
|
|
{
|
|
$now = new \DateTime('now');
|
|
if ($this->refreshExpire->getTimestamp() < $now->getTimestamp() - self::TIME_DELTA) {
|
|
return AuthStatus::FAILED;
|
|
}
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/security/' . self::API_VERSION . '/oauth/refresh';
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
|
|
$request->setMethod(RequestMethod::POST);
|
|
$request->header->set('Content-Type', MimeType::M_POST);
|
|
$request->header->set('Authorization', 'Basic ' . \base64_encode($this->login . ':' . $this->password));
|
|
|
|
$request->setData('grant_type', 'refresh_token');
|
|
$request->setData('refresh_token', $this->refreshToken);
|
|
|
|
$this->expire = clone $now;
|
|
$this->refreshExpire = clone $now;
|
|
|
|
$response = Rest::request($request);
|
|
|
|
switch ($response->header->status) {
|
|
case 400:
|
|
case 401:
|
|
$status = AuthStatus::FAILED;
|
|
break;
|
|
case 403:
|
|
$status = AuthStatus::BLOCKED;
|
|
break;
|
|
case 429:
|
|
$status = AuthStatus::LIMIT_EXCEEDED;
|
|
break;
|
|
case 200:
|
|
$this->token = $response->getDataString('access_token') ?? '';
|
|
$this->refreshToken = $response->getDataString('refresh_token') ?? '';
|
|
|
|
$this->expire->setTimestamp($this->expire->getTimestamp() + ((int) $response->getData('expires_in')));
|
|
$this->refreshExpire->setTimestamp($this->refreshExpire->getTimestamp() + ((int) $response->getData('refresh_token_expires_in')));
|
|
|
|
$status = AuthStatus::OK;
|
|
break;
|
|
default:
|
|
$status = AuthStatus::FAILED;
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function authApiKey(string $key) : int
|
|
{
|
|
return AuthStatus::FAILED;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function timeInTransit(array $shipFrom, array $receiver, array $package, \DateTime $shipDate) : array
|
|
{
|
|
if (!$this->validateOrReconnectAuth()) {
|
|
return [];
|
|
}
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/api/shipments/' . self::API_VERSION . '/transittimes';
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
|
|
$request->setMethod(RequestMethod::POST);
|
|
$request->header->set('Content-Type', MimeType::M_JSON);
|
|
$request->header->set('Authorization', 'Bearer ' . $this->token);
|
|
$request->header->set('transId', ((string) \microtime(true)) . '-' . \bin2hex(\random_bytes(6)));
|
|
$request->header->set('transactionSrc', 'jingga');
|
|
|
|
$request->setData('originCountryCode', $shipFrom['country_code']);
|
|
$request->setData('originStateProvince', \substr($shipFrom['state'], 0, 50));
|
|
$request->setData('originCityName', \substr($shipFrom['city'], 0, 50));
|
|
$request->setData('originPostalCode', \substr($shipFrom['zip'], 0, 10));
|
|
|
|
$request->setData('destinationCountryCode', $receiver['country_code']);
|
|
$request->setData('destinationStateProvince', \substr($receiver['state'], 0, 50));
|
|
$request->setData('destinationCityName', \substr($receiver['city'], 0, 50));
|
|
$request->setData('destinationPostalCode', \substr($receiver['zip'], 0, 10));
|
|
$request->setData('avvFlag', true);
|
|
|
|
$request->setData('billType', $package['type']);
|
|
$request->setData('weight', $package['weight']);
|
|
$request->setData('weightUnitOfMeasure', $package['weight_unit']); // LBS or KGS
|
|
$request->setData('shipmentContentsValue', $package['value']);
|
|
$request->setData('shipmentContentsCurrencyCode', $package['currency']); // 3 char ISO code
|
|
$request->setData('numberOfPackages', $package['count']);
|
|
|
|
$request->setData('shipDate', $shipDate->format('Y-m-d'));
|
|
|
|
$response = Rest::request($request);
|
|
if ($response->header->status !== 200) {
|
|
return [];
|
|
}
|
|
|
|
$services = $response->getDataArray('services') ?? [];
|
|
$transits = [];
|
|
|
|
foreach ($services as $service) {
|
|
$transits[] = [
|
|
'serviceLevel' => $service['serviceLevel'],
|
|
'deliveryDate' => new \DateTime($service['deliveryDaye']),
|
|
'deliveryDateFrom' => null,
|
|
'deliveryDateTo' => null,
|
|
];
|
|
}
|
|
|
|
return $transits;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function ship(array $sender, array $shipFrom, array $receiver, array $package, array $data) : array
|
|
{
|
|
if (!$this->validateOrReconnectAuth()) {
|
|
return [];
|
|
}
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/api/shipments/' . self::API_VERSION . '/ship';
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
|
|
$request->setMethod(RequestMethod::POST);
|
|
$request->header->set('Authorization', 'Bearer ' . $this->token);
|
|
$request->header->set('transId', ((string) \microtime(true)) . '-' . \bin2hex(\random_bytes(6)));
|
|
$request->header->set('transactionSrc', 'jingga');
|
|
|
|
// @todo dangerous goods
|
|
// @todo implement printing standard (pdf-zpl/format and size)
|
|
|
|
$body = [
|
|
'Request' => [
|
|
'RequestOption' => 'validate',
|
|
'SubVersion' => '2205',
|
|
],
|
|
'Shipment' => [
|
|
'Description' => $package['description'],
|
|
'DocumentsOnlyIndicator' => '0',
|
|
'Shipper' => [
|
|
'Name' => \substr($sender['name'], 0, 35),
|
|
'AttentionName' => \substr($sender['fao'], 0, 35),
|
|
'CompanyDisplayableName' => \substr($sender['name'], 0, 35),
|
|
'TaxIdentificationNumber' => \substr($sender['taxid'], 0, 15),
|
|
'Phone' => [
|
|
'Number' => \substr($sender['phone'], 0, 15),
|
|
],
|
|
'ShipperNumber' => $sender['number'],
|
|
'EMailAddress' => \substr($sender['email'], 0, 50),
|
|
'Address' => [
|
|
'AddressLine' => \substr($sender['address'], 0, 35),
|
|
'City' => \substr($sender['city'], 0, 30),
|
|
'StateProvinceCode' => \substr($sender['state'], 0, 5),
|
|
'PostalCode' => \substr($sender['zip'], 0, 9),
|
|
'CountryCode' => $sender['country_code'],
|
|
],
|
|
],
|
|
'ShipTo' => [
|
|
'Name' => \substr($receiver['name'], 0, 35),
|
|
'AttentionName' => \substr($receiver['fao'], 0, 35),
|
|
'CompanyDisplayableName' => \substr($receiver['name'], 0, 35),
|
|
'TaxIdentificationNumber' => \substr($receiver['taxid'], 0, 15),
|
|
'Phone' => [
|
|
'Number' => \substr($receiver['phone'], 0, 15),
|
|
],
|
|
'ShipperNumber' => $receiver['number'],
|
|
'EMailAddress' => \substr($receiver['email'], 0, 50),
|
|
'Address' => [
|
|
'AddressLine' => \substr($receiver['address'], 0, 35),
|
|
'City' => \substr($receiver['city'], 0, 30),
|
|
'StateProvinceCode' => \substr($receiver['state'], 0, 5),
|
|
'PostalCode' => \substr($receiver['zip'], 0, 9),
|
|
'CountryCode' => $receiver['country_code'],
|
|
],
|
|
],
|
|
/* @todo only allowed for US -> US and PR -> PR shipments?
|
|
'ReferenceNumber' => [
|
|
'BarCodeIndicator' => '1',
|
|
'Code' => '',
|
|
'Value' => '',
|
|
],
|
|
*/
|
|
'Service' => [
|
|
'Code' => $data['service_code'],
|
|
'Description' => \substr($data['service_description'], 0, 35),
|
|
],
|
|
'InvoiceLineTotal' => [
|
|
'CurrencyCode' => $package['currency'],
|
|
'MonetaryValue' => $package['value'],
|
|
],
|
|
'NumOfPiecesInShipment' => $package['count'],
|
|
'CostCenter' => \substr($package['costcenter'], 0, 30),
|
|
'PackageID' => \substr($package['id'], 0, 30),
|
|
'PackageIDBarcodeIndicator' => '1',
|
|
'Package' => [],
|
|
],
|
|
'LabelSpecification' => [
|
|
'LabelImageFormat' => [
|
|
'Code' => $data['label_code'],
|
|
'Description' => \substr($data['label_description'], 0, 35),
|
|
],
|
|
'LabelStockSize' => [
|
|
'Height' => $data['label_height'],
|
|
'Width' => $data['label_width'],
|
|
],
|
|
],
|
|
'ReceiptSpecification' => [
|
|
'ImageFormat' => [
|
|
'Code' => $data['receipt_code'],
|
|
'Description' => \substr($data['receipt_description'], 0, 35),
|
|
],
|
|
],
|
|
];
|
|
|
|
$packages = [];
|
|
foreach ($package['packages'] as $p) {
|
|
$packages[] = [
|
|
'Description' => \substr($p['description'], 0, 35),
|
|
'Packaging' => [
|
|
'Code' => $p['package_code'],
|
|
'Description' => $p['package_description'],
|
|
],
|
|
'Dimensions' => [
|
|
'UnitOfMeasurement' => [
|
|
'Code' => $p['package_dim_unit'], // IN or CM or 00 or 01
|
|
'Description' => \substr($p['package_dim_unit_description'], 0, 35),
|
|
],
|
|
'Length' => $p['length'],
|
|
'Width' => $p['width'],
|
|
'Height' => $p['height'],
|
|
],
|
|
'DimWeight' => [
|
|
'UnitOfMeasurement' => [
|
|
'Code' => $p['package_dimweight_unit'],
|
|
'Description' => \substr($p['package_dimweight_unit_description'], 0, 35),
|
|
],
|
|
'Weight' => $p['weight'],
|
|
],
|
|
'PackageWeight' => [
|
|
'UnitOfMeasurement' => [
|
|
'Code' => $p['package_weight_unit'],
|
|
'Description' => \substr($p['package_weight_unit_description'], 0, 35),
|
|
],
|
|
'Weight' => $p['weight'],
|
|
],
|
|
|
|
];
|
|
}
|
|
|
|
$body['Shipment']['Package'] = $packages;
|
|
|
|
// Only required if shipper != shipFrom (e.g. pickup location != shipper)
|
|
if (!empty($shipFrom)) {
|
|
$body['Shipment']['ShipFrom'] = [
|
|
'Name' => \substr($shipFrom['name'], 0, 35),
|
|
'AttentionName' => \substr($shipFrom['fao'], 0, 35),
|
|
'CompanyDisplayableName' => \substr($shipFrom['name'], 0, 35),
|
|
'TaxIdentificationNumber' => \substr($shipFrom['taxid'], 0, 15),
|
|
'Phone' => [
|
|
'Number' => \substr($shipFrom['phone'], 0, 15),
|
|
],
|
|
'ShipperNumber' => $shipFrom['number'],
|
|
'EMailAddress' => \substr($shipFrom['email'], 0, 50),
|
|
'Address' => [
|
|
'AddressLine' => \substr($shipFrom['address'], 0, 35),
|
|
'City' => \substr($shipFrom['city'], 0, 30),
|
|
'StateProvinceCode' => \substr($shipFrom['state'], 0, 5),
|
|
'PostalCode' => \substr($shipFrom['zip'], 0, 9),
|
|
'CountryCode' => $shipFrom['country_code'],
|
|
],
|
|
];
|
|
}
|
|
|
|
$request->setData('ShipmentRequest', $body);
|
|
|
|
$response = Rest::request($request);
|
|
if ($response->header->status !== 200) {
|
|
return [];
|
|
}
|
|
|
|
$result = $response->getDataArray('ShipmentResponse') ?? [];
|
|
|
|
$shipment = [
|
|
'id' => $result['ShipmentResults']['ShipmentIdentificationNumber'] ?? '',
|
|
'costs' => [
|
|
'service' => $result['ShipmentResults']['ShipmentCharges']['BaseServiceCharge']['MonetaryValue'] ?? null,
|
|
'transportation' => $result['ShipmentResults']['ShipmentCharges']['TransportationCharges']['MonetaryValue'] ?? null,
|
|
'options' => $result['ShipmentResults']['ShipmentCharges']['ServiceOptionsCharges']['MonetaryValue'] ?? null,
|
|
'subtotal' => $result['ShipmentResults']['ShipmentCharges']['TotalCharges']['MonetaryValue'] ?? null,
|
|
'taxes' => $result['ShipmentResults']['ShipmentCharges']['TaxCharges']['MonetaryValue'] ?? null,
|
|
'taxes_type' => $result['ShipmentResults']['ShipmentCharges']['TaxCharges']['Type'] ?? null,
|
|
'total' => $result['ShipmentResults']['ShipmentCharges']['TotalChargesWithTaxes']['MonetaryValue'] ?? null,
|
|
'currency' => $result['ShipmentResults']['ShipmentCharges']['TotalCharges']['CurrencyCode'] ?? null,
|
|
],
|
|
'packages' => [],
|
|
'label' => [
|
|
'code' => '',
|
|
'url' => $result['ShipmentResults']['LabelURL'] ?? '',
|
|
'barcode' => $result['ShipmentResults']['BarCodeImage'] ?? '',
|
|
'local' => $result['ShipmentResults']['LocalLanguageLabelURL'] ?? '',
|
|
'data' => '',
|
|
],
|
|
'receipt' => [
|
|
'code' => '',
|
|
'url' => $result['ShipmentResults']['ReceiptURL'] ?? '',
|
|
'local' => $result['ShipmentResults']['LocalLanguageReceiptURL'] ?? '',
|
|
'data' => '',
|
|
],
|
|
// @todo dangerous goods paper image
|
|
];
|
|
|
|
$packages = [];
|
|
foreach ($result['ShipmentResults']['Packages'] as $package) {
|
|
$packages[] = [
|
|
'id' => $package['TrackingNumber'],
|
|
'label' => [
|
|
'code' => $package['ShippingLabel']['ImageFormat']['Code'],
|
|
'url' => '',
|
|
'barcode' => $package['PDF417'],
|
|
'image' => $package['ShippingLabel']['GraphicImage'],
|
|
'browser' => $package['HTMLImage'],
|
|
'data' => '',
|
|
],
|
|
'receipt' => [
|
|
'code' => $package['ShippingReceipt']['ImageFormat']['Code'],
|
|
'image' => $package['ShippingReceipt']['ImageFormat']['GraphicImage'],
|
|
],
|
|
];
|
|
}
|
|
|
|
$shipment['packages'] = $packages;
|
|
|
|
return $shipment;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function cancel(string $shipment, array $packages = []) : bool
|
|
{
|
|
if (!$this->validateOrReconnectAuth()) {
|
|
return false;
|
|
}
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/api/shipments/' . self::API_VERSION . '/void/cancel/' . $shipment;
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
|
|
$request->setMethod(RequestMethod::DELETE);
|
|
$request->header->set('Authorization', 'Bearer ' . $this->token);
|
|
$request->header->set('transId', ((string) \microtime(true)) . '-' . \bin2hex(\random_bytes(6)));
|
|
$request->header->set('transactionSrc', 'jingga');
|
|
|
|
$request->setData('trackingnumber', empty($shipment) ? $shipment : \implode(',', $packages));
|
|
|
|
$response = Rest::request($request);
|
|
if ($response->header->status !== 200) {
|
|
return false;
|
|
}
|
|
|
|
return ($response->getDataArray('VoidShipmentResponse')['Response']['ResponseStatus']['Code'] ?? '0') === '1';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function track(string $shipment) : array
|
|
{
|
|
if (!$this->validateOrReconnectAuth()) {
|
|
return [];
|
|
}
|
|
|
|
$base = self::$ENV === 'live' ? self::LIVE_URL : self::SANDBOX_URL;
|
|
$uri = $base . '/api/track/v1/details/' . $shipment;
|
|
|
|
$request = new HttpRequest(new HttpUri($uri));
|
|
|
|
$request->setMethod(RequestMethod::GET);
|
|
$request->header->set('Authorization', 'Bearer ' . $this->token);
|
|
$request->header->set('transId', ((string) \microtime(true)) . '-' . \bin2hex(\random_bytes(6)));
|
|
$request->header->set('transactionSrc', 'jingga');
|
|
|
|
$request->setData('locale', 'en_US');
|
|
$request->setData('returnSignature', 'false');
|
|
|
|
$response = Rest::request($request);
|
|
if ($response->header->status !== 200) {
|
|
return [];
|
|
}
|
|
|
|
$shipments = $response->getDataArray('trackResponse') ?? [];
|
|
$shipments = $shipments['shipment'] ?? [];
|
|
|
|
$tracking = [];
|
|
|
|
// @todo add general shipment status (not just for individual packages)
|
|
|
|
foreach ($shipments as $shipment) {
|
|
$packages = [];
|
|
foreach ($shipment['package'] as $package) {
|
|
$activities = [];
|
|
foreach ($package['activity'] as $activity) {
|
|
$activities[] = [
|
|
'date' => new \DateTime($activity['date'] . ' ' . $activity['time']),
|
|
'description' => '',
|
|
'location' => [
|
|
'address' => [
|
|
$activity['location']['address']['addressLine1'],
|
|
$activity['location']['address']['addressLine2'],
|
|
$activity['location']['address']['addressLine3'],
|
|
],
|
|
'city' => $activity['location']['address']['city'],
|
|
'country' => $activity['location']['address']['country'],
|
|
'country_code' => $activity['location']['address']['country_code'],
|
|
'zip' => $activity['location']['address']['postalCode'],
|
|
'state' => $activity['location']['address']['stateProvice'],
|
|
],
|
|
'status' => [
|
|
'code' => $activity['status']['code'],
|
|
'statusCode' => $activity['status']['statusCode'],
|
|
'description' => $activity['status']['description'],
|
|
],
|
|
];
|
|
}
|
|
|
|
$packages[] = [
|
|
'status' => [
|
|
'code' => $package['status']['code'],
|
|
'statusCode' => $package['status']['statusCode'],
|
|
'description' => $package['status']['description'],
|
|
],
|
|
'deliveryDate' => new \DateTime($package['deliveryDate'] . ' ' . $package['deliveryTime']['endTime']),
|
|
'count' => $package['packageCount'],
|
|
'weight' => $package['weight']['weight'],
|
|
'weight_unit' => $package['weight']['unitOfMeasurement'],
|
|
'activities' => $activities,
|
|
'received' => [
|
|
'by' => $package['deliveryInformation']['receivedBy'],
|
|
'signature' => $package['deliveryInformation']['signature'],
|
|
'location' => $package['deliveryInformation']['location'],
|
|
'date' => '',
|
|
],
|
|
];
|
|
}
|
|
|
|
$tracking[] = $packages;
|
|
}
|
|
|
|
return $tracking;
|
|
}
|
|
|
|
/**
|
|
* Validates the current authentication and tries to reconnect if the connection timed out
|
|
*
|
|
* @return bool
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
private function validateOrReconnectAuth() : bool
|
|
{
|
|
$status = AuthStatus::OK;
|
|
$now = new \DateTime('now');
|
|
|
|
if ($this->expire->getTimestamp() < $now->getTimestamp() - self::TIME_DELTA) {
|
|
$status = AuthStatus::FAILED;
|
|
|
|
if ($this->refreshToken !== '') {
|
|
$status = $this->refreshToken();
|
|
} elseif ($this->login !== '' && $this->password !== '') {
|
|
$status = $this->authLogin($this->login, $this->password, $this->client);
|
|
}
|
|
}
|
|
|
|
return $status === AuthStatus::OK
|
|
&& $this->expire->getTimestamp() > $now->getTimestamp() - self::TIME_DELTA;
|
|
}
|
|
}
|