feat(50-allegro-shipment-waybill-push): push waybill to allegro checkout form

Phase 50 complete:

- add conditional waybill push for allegro orders only

- add retry on ALLEGRO_HTTP_401 and non-critical failure handling

- add unit tests and update architecture/changelog docs
This commit is contained in:
2026-03-28 15:32:34 +01:00
parent 2ab0d0e90e
commit 176d740578
9 changed files with 696 additions and 30 deletions

View File

@@ -3,13 +3,13 @@ declare(strict_types=1);
namespace App\Modules\Shipments;
use App\Core\Exceptions\IntegrationConfigException;
use App\Core\Exceptions\ShipmentException;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\AllegroApiClient;
use App\Modules\Settings\AllegroTokenManager;
use App\Modules\Settings\CompanySettingsRepository;
use RuntimeException;
use App\Core\Exceptions\IntegrationConfigException;
use App\Core\Exceptions\ShipmentException;
use Throwable;
final class AllegroShipmentService implements ShipmentProviderInterface
@@ -35,6 +35,7 @@ final class AllegroShipmentService implements ShipmentProviderInterface
{
[$accessToken, $env] = $this->tokenManager->resolveToken();
$response = $this->apiClient->getDeliveryServices($env, $accessToken);
return is_array($response['services'] ?? null) ? $response['services'] : [];
}
@@ -107,7 +108,7 @@ final class AllegroShipmentService implements ShipmentProviderInterface
$codAmount = (float) ($formData['cod_amount'] ?? 0);
if ($codAmount > 0) {
// Allegro WZA manages COD funds internally iban/ownerName are not accepted
// Allegro WZA manages COD funds internally - iban/ownerName are not accepted.
$apiPayload['input']['cashOnDelivery'] = [
'amount' => number_format($codAmount, 2, '.', ''),
'currency' => strtoupper(trim((string) ($formData['cod_currency'] ?? 'PLN'))),
@@ -196,14 +197,27 @@ final class AllegroShipmentService implements ShipmentProviderInterface
if ($status === 'SUCCESS' && $shipmentId !== '') {
$details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId);
$trackingNumber = trim((string) ($details['waybill'] ?? ''));
$carrierId = trim((string) ($package['carrier_id'] ?? ''));
if ($carrierId === '') {
$carrierId = trim((string) ($details['carrierId'] ?? ''));
}
$this->packages->update($packageId, [
'status' => 'created',
'shipment_id' => $shipmentId,
'tracking_number' => $trackingNumber !== '' ? $trackingNumber : null,
'carrier_id' => $carrierId !== '' ? $carrierId : ($package['carrier_id'] ?? null),
'payload_json' => $details,
]);
$this->pushWaybillToAllegroCheckoutForm(
(int) ($package['order_id'] ?? 0),
$trackingNumber,
$carrierId,
$accessToken,
$env
);
return [
'status' => 'created',
'shipment_id' => $shipmentId,
@@ -268,16 +282,32 @@ final class AllegroShipmentService implements ShipmentProviderInterface
'label_path' => 'labels/' . $filename,
];
// Refresh tracking number if not yet saved (may not have been available at creation time)
// Refresh tracking number if not yet saved (may not have been available at creation time).
if (trim((string) ($package['tracking_number'] ?? '')) === '') {
try {
$details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId);
$trackingNumber = trim((string) ($details['waybill'] ?? ''));
$carrierId = trim((string) ($package['carrier_id'] ?? ''));
if ($carrierId === '') {
$carrierId = trim((string) ($details['carrierId'] ?? ''));
}
if ($trackingNumber !== '') {
$updateFields['tracking_number'] = $trackingNumber;
if ($carrierId !== '') {
$updateFields['carrier_id'] = $carrierId;
}
$this->pushWaybillToAllegroCheckoutForm(
(int) ($package['order_id'] ?? 0),
$trackingNumber,
$carrierId,
$accessToken,
$env
);
}
} catch (Throwable) {
// non-critical label is still saved
// non-critical - label is still saved
}
}
@@ -359,11 +389,87 @@ final class AllegroShipmentService implements ShipmentProviderInterface
}
}
private function pushWaybillToAllegroCheckoutForm(
int $orderId,
string $trackingNumber,
string $carrierId,
string $accessToken,
string $environment
): void {
if ($orderId <= 0) {
return;
}
$waybill = trim($trackingNumber);
if ($waybill === '') {
return;
}
$orderDetails = $this->ordersRepository->findDetails($orderId);
if ($orderDetails === null) {
return;
}
$order = is_array($orderDetails['order'] ?? null) ? $orderDetails['order'] : [];
$source = strtolower(trim((string) ($order['source'] ?? '')));
if ($source !== 'allegro') {
return;
}
$checkoutFormId = trim((string) ($order['source_order_id'] ?? ''));
if ($checkoutFormId === '') {
return;
}
$normalizedCarrierId = trim($carrierId);
if ($normalizedCarrierId === '') {
return;
}
$carrierName = (string) ($this->packages->resolveCarrierName('allegro_wza', $normalizedCarrierId) ?? '');
$carrierName = trim($carrierName);
if ($carrierName === '') {
$carrierName = $normalizedCarrierId;
}
try {
$this->apiClient->addShipmentToOrder(
$environment,
$accessToken,
$checkoutFormId,
$waybill,
$normalizedCarrierId,
$carrierName
);
} catch (RuntimeException $exception) {
if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') {
return;
}
try {
[$refreshedToken, $refreshedEnvironment] = $this->tokenManager->resolveToken();
$this->apiClient->addShipmentToOrder(
$refreshedEnvironment,
$refreshedToken,
$checkoutFormId,
$waybill,
$normalizedCarrierId,
$carrierName
);
} catch (Throwable) {
// non-critical - local shipment remains created
}
} catch (Throwable) {
// non-critical - local shipment remains created
}
}
private function generateUuid(): string
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
}
}