Go API implementation

This commit is contained in:
Andreas Palm 2025-01-03 21:07:07 +01:00
parent 28aacdf8fa
commit afab111c75
14 changed files with 488 additions and 0 deletions

View File

@ -0,0 +1,24 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class NeutralAddress {
public string $name1 = '';
public string $name2 = '';
public string $name3 = '';
public string $street = '';
public string $houseNumber = '';
public string $zipCode = '';
public string $city = '';
public string $country = '';
}
class Address extends NeutralAddress {
public string $phoneNumber = '';
public string $remarks = '';
public string $email = '';
}

View File

@ -0,0 +1,31 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
use Xentral\Carrier\SendCloud\Data\Label;
class CreateOrderRequest {
public string $responsibleStation;
public string $customerId;
public Shipment $shipment;
public Address $consignorAddress;
public NeutralAddress $neutralAddress;
public Address $consigneeAddress;
public LabelType $label;
/***
* @var Package[]
*/
public array $packages;
public function __construct() {
$this->shipment = new Shipment();
$this->consignorAddress = new Address();
$this->consigneeAddress = new Address();
$this->neutralAddress = new NeutralAddress();
$this->packages = [new Package()];
}
}

View File

@ -0,0 +1,17 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class CreateOrderResponse {
public string $hwbNumber;
public OrderStatus $orderStatus;
public \DateTime $pickupDate;
public \DateTime $deliveryDate;
public string $hwbOrPackageLabel;
public array $barcodes;
}

View File

@ -0,0 +1,27 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class Delivery implements \JsonSerializable {
public \DateTime $dateFrom;
public \DateTime $dateTill;
public bool $dateIsAvis = false;
public bool $deliveryOnSaturday = false;
public bool $deliveryOnHoliday = false;
public function jsonSerialize()
{
return [
'date' => isset($this->dateFrom) ? $this->dateFrom->format('d.m.Y') : '',
'timeFrom' => !$this->dateIsAvis && isset($this->dateFrom) ? $this->dateFrom->format('H:i') : '',
'timeTill' => !$this->dateIsAvis && isset($this->dateTill) ? $this->dateTill->format('H:i') : '',
'avisFrom' => $this->dateIsAvis && isset($this->dateFrom) ? $this->dateFrom->format('H:i') : '',
'avisTill' => $this->dateIsAvis && isset($this->dateTill) ? $this->dateTill->format('H:i') : '',
'weekendOrHolidayIndicator' => $this->deliveryOnHoliday ? 'H' : ($this->deliveryOnSaturday ? 'S' : '')
];
}
}

View File

@ -0,0 +1,14 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class ErrorResponse {
public string $message;
public int $errorCode;
public int $status;
public \DateTime $timeStamp;
}

View File

@ -0,0 +1,14 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
enum LabelType: string {
case ZPL = '1';
case PDF_A6 = '2';
case PDF_A4 = '4';
case TPCL = '5';
}

View File

@ -0,0 +1,20 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class MoneyValue implements \JsonSerializable {
public float $amount = 0;
public string $currency = 'EUR';
public function jsonSerialize()
{
return [
'amount' => $this->amount > 0 ? (string)$this->amount : '',
'currency' => $this->amount > 0 ? $this->currency : '',
];
}
}

View File

@ -0,0 +1,13 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
enum OrderStatus : string {
case New = 'New';
case Released = 'Released';
case Cancelled = 'Cancelled';
}

View File

@ -0,0 +1,22 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class Package implements \JsonSerializable {
public int $length = 0;
public int $width = 0;
public int $height = 0;
public function jsonSerialize()
{
return [
'length' => $this->length ? (string) $this->length : '',
'width' => $this->width ? (string) $this->width : '',
'height' => $this->height ? (string) $this->height : '',
];
}
}

View File

@ -0,0 +1,21 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class Pickup implements \JsonSerializable {
public \DateTime $dateFrom;
public \DateTime $dateTill;
public function jsonSerialize()
{
return [
'date' => $this->dateFrom->format('d.m.Y'),
'timeFrom' => $this->dateFrom->format('H:i'),
'timeTill' => $this->dateTill->format('H:i'),
];
}
}

View File

@ -0,0 +1,57 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
class Shipment implements \JsonSerializable {
public string $hwbNumber = '';
public OrderStatus $orderStatus;
public bool $validation = true;
public ShipmentService $service;
public float $weight;
public string $content = '';
public string $customerReference = '';
public ?string $costCenter;
public bool $selfPickup = false;
public bool $selfDelivery = false;
public int $width;
public int $length;
public int $height;
public int $packageCount = 1;
public bool $freightCollect = false;
public bool $identCheck = false;
public bool $receiptNotice = false;
public bool $isNeutralPickup = false;
public Pickup $pickup;
public Delivery $delivery;
public ?MoneyValue $insurance;
public ?MoneyValue $valueOfGoods;
public ?MoneyValue $cashOnDelivery;
public function __construct()
{
$this->pickup = new Pickup();
$this->delivery = new Delivery();
$this->insurance = new MoneyValue();
$this->valueOfGoods = new MoneyValue();
$this->cashOnDelivery = new MoneyValue();
}
public function SetService(string $service) {
$this->service = ShipmentService::from($service);
}
public function jsonSerialize()
{
$array = (array) $this;
$array['dimensions'] = '';
return array_map(function ($value) {
if (is_bool($value))
return $value ? 'Yes' : 'No';
return $value;
}, $array);
}
}

View File

@ -0,0 +1,17 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go\Data;
enum ShipmentService: string {
case Overnight = 'ON';
case Worldwide = 'INT';
case OvernightLetter = 'LET';
case WorldwideLetter = 'INL';
case OvernightCodedDelivery = 'ONC';
case OvernightCodedLetter = 'LEC';
case DirectShipment = 'DI';
}

View File

@ -0,0 +1,54 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: AGPL-3.0-only
namespace Xentral\Carrier\Go;
use Xentral\Carrier\Go\Data\CreateOrderRequest;
use Xentral\Carrier\Go\Data\CreateOrderResponse;
use Xentral\Carrier\Go\Data\OrderStatus;
class GoApi {
const BASE_URL_PROD = 'https://ws.api.general-overnight.com/external/ci/order/api/v1/';
const BASE_URL_TEST = 'https://ws-tst.api.general-overnight.com/external/ci/order/api/v1/';
protected string $baseUrl;
public function __construct(protected string $username, protected string $password, bool $testMode) {
if ($testMode)
$this->baseUrl = self::BASE_URL_TEST;
else
$this->baseUrl = self::BASE_URL_PROD;
}
public function createOrder(CreateOrderRequest $request): CreateOrderResponse|string {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->baseUrl.'createOrder',
CURLOPT_POST => 1,
CURLOPT_USERNAME => $this->username,
CURLOPT_PASSWORD => $this->password,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($request, JSON_THROW_ON_ERROR),
]);
$response = json_decode(curl_exec($curl));
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($code == 200) {
$ret = new CreateOrderResponse();
$ret->hwbNumber = $response->hwbNumber;
$ret->orderStatus = OrderStatus::from($response->orderStatus);
$ret->pickupDate = new \DateTime($response->pickupDate);
$ret->deliveryDate = new \DateTime($response->deliveryDate);
$ret->hwbOrPackageLabel = $response->hwbOrPackageLabel;
$ret->barcodes = array_map(function ($item) { return $item->barcode; }, $response->package);
return $ret;
}
return $response->message;
}
}

157
www/lib/versandarten/go.php Normal file
View File

@ -0,0 +1,157 @@
<?php
// SPDX-FileCopyrightText: 2025 Andreas Palm
//
// SPDX-License-Identifier: LicenseRef-EGPL-3.1
use Xentral\Carrier\Go\Data\MoneyValue;
use Xentral\Carrier\Go\Data\ShipmentService;
use Xentral\Carrier\Go\Data\CreateOrderRequest;
use Xentral\Carrier\Go\Data\CreateOrderResponse;
use Xentral\Carrier\Go\Data\LabelType;
use Xentral\Carrier\Go\Data\OrderStatus;
use Xentral\Carrier\Go\GoApi;
use Xentral\Modules\ShippingMethod\Model\CreateShipmentResult;
use Xentral\Modules\ShippingMethod\Model\Product;
use Xentral\Modules\ShippingMethod\Model\ShipmentStatus;
require_once dirname(__DIR__) . '/class.versanddienstleister.php';
class Versandart_go extends Versanddienstleister
{
protected GoApi $api;
protected array $options;
public function __construct(ApplicationCore $app, ?int $id)
{
parent::__construct($app, $id);
if (!isset($this->id) || !isset($this->settings->username) || !isset($this->settings->password))
return;
$this->api = new GoApi($this->settings->username, $this->settings->password, $this->settings->useTestEndpoint ?? true);
}
public function GetName(): string
{
return "GO!";
}
public function AdditionalSettings(): array
{
return [
'username' => ['typ' => 'text', 'bezeichnung' => 'API Username:'],
'password' => ['typ' => 'text', 'bezeichnung' => 'API Password:'],
'responsibleStation' => ['typ' => 'text', 'bezeichnung' => 'Responsible Station:'],
'customerId' => ['typ' => 'text', 'bezeichnung' => 'Customer ID:'],
'useTestEndpoint' => ['typ' => 'checkbox', 'bezeichnung' => 'Testsystem verwenden:', 'default' => true],
'orderAsDraft' => ['typ' => 'checkbox', 'bezeichnung' => 'Order As Draft:'],
'labelType' => ['typ' => 'select', 'bezeichnung' => 'Label Type:', 'optionen' => [
LabelType::PDF_A6->value => 'PDF A6',
LabelType::PDF_A4->value => 'PDF A4',
]],
'consignorName1' => ['typ' => 'text', 'bezeichnung' => 'Absender Name1:'],
'consignorName2' => ['typ' => 'text', 'bezeichnung' => 'Absender Name2:'],
'consignorName3' => ['typ' => 'text', 'bezeichnung' => 'Absender Name3:'],
'consignorStreet' => ['typ' => 'text', 'bezeichnung' => 'Absender Straße:'],
'consignorHouseNumber' => ['typ' => 'text', 'bezeichnung' => 'Absender Hausnummer:'],
'consignorZipCode' => ['typ' => 'text', 'bezeichnung' => 'Absender PLZ:'],
'consignorCity' => ['typ' => 'text', 'bezeichnung' => 'Absender Stadt:'],
'consignorCountry' => ['typ' => 'text', 'bezeichnung' => 'Absender Land:'],
'consignorPhoneNumber' => ['typ' => 'text', 'bezeichnung' => 'Absender Telefon:'],
'consignorRemarks' => ['typ' => 'text', 'bezeichnung' => 'Absender Bemerkungen:'],
'consignorEmail' => ['typ' => 'text', 'bezeichnung' => 'Absender Email:'],
'defaultPickupFrom' => ['typ' => 'text', 'bezeichnung' => 'Standard Abholzeit von:'],
'defaultPickupTill' => ['typ' => 'text', 'bezeichnung' => 'Standard Abholzeit bis:'],
];
}
public function CreateShipment(object $json, array $address): CreateShipmentResult
{
$order = new CreateOrderRequest();
$order->responsibleStation = $this->settings->responsibleStation;
$order->customerId = $this->settings->customerId;
$order->label = LabelType::from($this->settings->labelType);
$order->shipment->orderStatus = $this->settings->orderAsDraft ? OrderStatus::New : OrderStatus::Released;
$order->shipment->SetService($json->product);
$order->shipment->customerReference = $json->order_number;
$order->shipment->weight = floatval($json->weight);
$order->shipment->pickup->dateFrom = new DateTime($this->settings->defaultPickupFrom);
if ($order->shipment->pickup->dateFrom < new DateTime('now'))
$order->shipment->pickup->dateFrom = $order->shipment->pickup->dateFrom->add(new DateInterval('P1D'));
$order->shipment->pickup->dateTill = new DateTime($this->settings->defaultPickupTill);
if ($json->total_insured_value > 0)
$order->shipment->insurance->amount = $json->total_insured_value;
$order->consignorAddress->name1 = $this->settings->consignorName1;
$order->consignorAddress->name2 = $this->settings->consignorName2;
$order->consignorAddress->name3 = $this->settings->consignorName3;
$order->consignorAddress->street = $this->settings->consignorStreet;
$order->consignorAddress->houseNumber = $this->settings->consignorHouseNumber;
$order->consignorAddress->zipCode = $this->settings->consignorZipCode;
$order->consignorAddress->city = $this->settings->consignorCity;
$order->consignorAddress->country = $this->settings->consignorCountry;
$order->consignorAddress->phoneNumber = $this->settings->consignorPhoneNumber;
$order->consignorAddress->remarks = $this->settings->consignorRemarks;
$order->consignorAddress->email = $this->settings->consignorEmail;
switch ($json->addresstype) {
case 0:
$order->consigneeAddress->name1 = $json->company_name;
$order->consigneeAddress->name2 = join(
';',
array_filter(
[
$json->contact_name,
$json->company_division
],
fn(string $item) => !empty(trim($item))
)
);
break;
case 3:
$order->consigneeAddress->name1 = $json->name;
$order->consigneeAddress->name2 = $json->contact_name;
break;
}
$order->consigneeAddress->name3 = $json->address2;
$order->consigneeAddress->street = $json->street;
$order->consigneeAddress->houseNumber = $json->streetnumber;
$order->consigneeAddress->country = $json->country;
$order->consigneeAddress->zipCode = $json->zip;
$order->consigneeAddress->city = $json->city;
$order->consigneeAddress->email = $json->email;
$order->consigneeAddress->phoneNumber = $json->phone;
$ret = new CreateShipmentResult();
$result = $this->api->createOrder($order);
if ($result instanceof CreateOrderResponse) {
$ret->Success = true;
$ret->TrackingNumber = $result->hwbNumber;
$ret->TrackingUrl = '';
$ret->Label = base64_decode($result->hwbOrPackageLabel);
} else {
$ret->Errors[] = $result;
}
return $ret;
}
public function GetShippingProducts(): array
{
$result = [];
$result[] = Product::Create(ShipmentService::Overnight->value, 'GO! Overnight');
$result[] = Product::Create(ShipmentService::Worldwide->value, 'GO! Worldwide');
$result[] = Product::Create(ShipmentService::OvernightLetter->value, 'GO! Overnight - Letter')
->WithWeight(0, 0.25);
$result[] = Product::Create(ShipmentService::WorldwideLetter->value, 'GO! Worldwide - Letter')
->WithWeight(0, 0.25);
$result[] = Product::Create(ShipmentService::OvernightCodedDelivery->value, 'GO! Overnight Coded Delivery');
$result[] = Product::Create(ShipmentService::OvernightCodedLetter->value, 'GO! Overnight Letter Coded Delivery')
->WithWeight(0, 0.25);
return $result;
}
public function GetShipmentStatus(string $tracking): ShipmentStatus|null
{
// TODO: Implement GetShipmentStatus() method.
return null;
}
}