feat(06-sonarqube-quality): introduce typed exception hierarchy (S112 fix)

Replace 86+ generic RuntimeException throws with domain-specific exception
classes: AllegroApiException, AllegroOAuthException, ApaczkaApiException,
ShipmentException, IntegrationConfigException — all extending OrderProException
extends RuntimeException. Existing catch(RuntimeException) blocks unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 11:04:52 +01:00
parent 3a9cfcd4a2
commit 3c27c4e54a
21 changed files with 141 additions and 87 deletions

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Core\Exceptions;
class AllegroApiException extends OrderProException
{
}

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Core\Exceptions;
class AllegroOAuthException extends AllegroApiException
{
}

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Core\Exceptions;
class ApaczkaApiException extends OrderProException
{
}

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Core\Exceptions;
class IntegrationConfigException extends OrderProException
{
}

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Core\Exceptions;
class OrderProException extends \RuntimeException
{
}

View File

@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Core\Exceptions;
class ShipmentException extends OrderProException
{
}

View File

@@ -3,7 +3,7 @@ declare(strict_types=1);
namespace App\Modules\Settings; namespace App\Modules\Settings;
use RuntimeException; use App\Core\Exceptions\AllegroApiException;
final class AllegroApiClient final class AllegroApiClient
{ {
@@ -14,7 +14,7 @@ final class AllegroApiClient
{ {
$safeId = rawurlencode(trim($checkoutFormId)); $safeId = rawurlencode(trim($checkoutFormId));
if ($safeId === '') { if ($safeId === '') {
throw new RuntimeException('Brak ID zamowienia Allegro do pobrania.'); throw new AllegroApiException('Brak ID zamowienia Allegro do pobrania.');
} }
$url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId; $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId;
@@ -45,7 +45,7 @@ final class AllegroApiClient
{ {
$safeId = rawurlencode(trim($checkoutFormId)); $safeId = rawurlencode(trim($checkoutFormId));
if ($safeId === '') { if ($safeId === '') {
throw new RuntimeException('Brak ID zamowienia Allegro do pobrania przesylek.'); throw new AllegroApiException('Brak ID zamowienia Allegro do pobrania przesylek.');
} }
$url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId . '/shipments'; $url = rtrim($this->apiBaseUrl($environment), '/') . '/order/checkout-forms/' . $safeId . '/shipments';
@@ -61,7 +61,7 @@ final class AllegroApiClient
{ {
$safeId = rawurlencode(trim($offerId)); $safeId = rawurlencode(trim($offerId));
if ($safeId === '') { if ($safeId === '') {
throw new RuntimeException('Brak ID oferty Allegro do pobrania.'); throw new AllegroApiException('Brak ID oferty Allegro do pobrania.');
} }
$url = rtrim($this->apiBaseUrl($environment), '/') . '/sale/product-offers/' . $safeId; $url = rtrim($this->apiBaseUrl($environment), '/') . '/sale/product-offers/' . $safeId;
@@ -163,7 +163,7 @@ final class AllegroApiClient
$ch = curl_init($url); $ch = curl_init($url);
if ($ch === false) { if ($ch === false) {
throw new RuntimeException('Nie udalo sie zainicjowac polaczenia z API Allegro.'); throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
} }
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -185,16 +185,16 @@ final class AllegroApiClient
$ch = null; $ch = null;
if ($responseBody === false) { if ($responseBody === false) {
throw new RuntimeException('Blad polaczenia z API Allegro: ' . $curlError); throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
} }
$json = json_decode((string) $responseBody, true); $json = json_decode((string) $responseBody, true);
if (!is_array($json)) { if (!is_array($json)) {
throw new RuntimeException('Nieprawidlowy JSON odpowiedzi API Allegro.'); throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
} }
if ($httpCode === 401) { if ($httpCode === 401) {
throw new RuntimeException('ALLEGRO_HTTP_401'); throw new AllegroApiException('ALLEGRO_HTTP_401');
} }
if ($httpCode < 200 || $httpCode >= 300) { if ($httpCode < 200 || $httpCode >= 300) {
@@ -212,7 +212,7 @@ final class AllegroApiClient
if ($message === '') { if ($message === '') {
$message = 'Blad API Allegro.'; $message = 'Blad API Allegro.';
} }
throw new RuntimeException('API Allegro HTTP ' . $httpCode . ': ' . $message); throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
} }
return $json; return $json;
@@ -227,7 +227,7 @@ final class AllegroApiClient
$ch = curl_init($url); $ch = curl_init($url);
if ($ch === false) { if ($ch === false) {
throw new RuntimeException('Nie udalo sie zainicjowac polaczenia z API Allegro.'); throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
} }
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -249,21 +249,21 @@ final class AllegroApiClient
$ch = null; $ch = null;
if ($responseBody === false) { if ($responseBody === false) {
throw new RuntimeException('Blad polaczenia z API Allegro: ' . $curlError); throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
} }
if ($httpCode === 401) { if ($httpCode === 401) {
throw new RuntimeException('ALLEGRO_HTTP_401'); throw new AllegroApiException('ALLEGRO_HTTP_401');
} }
if ($httpCode === 204) { if ($httpCode === 204) {
throw new RuntimeException('Brak etykiety dla podanej przesylki.'); throw new AllegroApiException('Brak etykiety dla podanej przesylki.');
} }
if ($httpCode < 200 || $httpCode >= 300) { if ($httpCode < 200 || $httpCode >= 300) {
$json = json_decode((string) $responseBody, true); $json = json_decode((string) $responseBody, true);
$message = is_array($json) ? trim((string) ($json['message'] ?? 'Blad API Allegro.')) : 'Blad API Allegro.'; $message = is_array($json) ? trim((string) ($json['message'] ?? 'Blad API Allegro.')) : 'Blad API Allegro.';
throw new RuntimeException('API Allegro HTTP ' . $httpCode . ': ' . $message); throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
} }
return (string) $responseBody; return (string) $responseBody;
@@ -276,7 +276,7 @@ final class AllegroApiClient
{ {
$ch = curl_init($url); $ch = curl_init($url);
if ($ch === false) { if ($ch === false) {
throw new RuntimeException('Nie udalo sie zainicjowac polaczenia z API Allegro.'); throw new AllegroApiException('Nie udalo sie zainicjowac polaczenia z API Allegro.');
} }
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -296,21 +296,21 @@ final class AllegroApiClient
$ch = null; $ch = null;
if ($responseBody === false) { if ($responseBody === false) {
throw new RuntimeException('Blad polaczenia z API Allegro: ' . $curlError); throw new AllegroApiException('Blad polaczenia z API Allegro: ' . $curlError);
} }
$json = json_decode((string) $responseBody, true); $json = json_decode((string) $responseBody, true);
if (!is_array($json)) { if (!is_array($json)) {
throw new RuntimeException('Nieprawidlowy JSON odpowiedzi API Allegro.'); throw new AllegroApiException('Nieprawidlowy JSON odpowiedzi API Allegro.');
} }
if ($httpCode === 401) { if ($httpCode === 401) {
throw new RuntimeException('ALLEGRO_HTTP_401'); throw new AllegroApiException('ALLEGRO_HTTP_401');
} }
if ($httpCode < 200 || $httpCode >= 300) { if ($httpCode < 200 || $httpCode >= 300) {
$message = trim((string) ($json['message'] ?? 'Blad API Allegro.')); $message = trim((string) ($json['message'] ?? 'Blad API Allegro.'));
throw new RuntimeException('API Allegro HTTP ' . $httpCode . ': ' . $message); throw new AllegroApiException('API Allegro HTTP ' . $httpCode . ': ' . $message);
} }
return $json; return $json;

View File

@@ -14,6 +14,7 @@ use App\Modules\Cron\CronRepository;
use DateInterval; use DateInterval;
use DateTimeImmutable; use DateTimeImmutable;
use RuntimeException; use RuntimeException;
use AppCoreExceptionsIntegrationConfigException;
use Throwable; use Throwable;
final class AllegroIntegrationController final class AllegroIntegrationController
@@ -701,7 +702,7 @@ final class AllegroIntegrationController
{ {
$credentials = $this->repository->getOAuthCredentials(); $credentials = $this->repository->getOAuthCredentials();
if ($credentials === null) { if ($credentials === null) {
throw new RuntimeException($this->translator->get('settings.allegro.flash.credentials_missing')); throw new IntegrationConfigException($this->translator->get('settings.allegro.flash.credentials_missing'));
} }
return $credentials; return $credentials;

View File

@@ -5,7 +5,7 @@ namespace App\Modules\Settings;
use App\Core\Support\StringHelper; use App\Core\Support\StringHelper;
use PDO; use PDO;
use RuntimeException; use AppCorexceptionsIntegrationConfigException;
use Throwable; use Throwable;
final class AllegroIntegrationRepository final class AllegroIntegrationRepository
@@ -104,7 +104,7 @@ final class AllegroIntegrationRepository
$this->ensureRow($env); $this->ensureRow($env);
$current = $this->fetchRowByEnv($env); $current = $this->fetchRowByEnv($env);
if ($current === null) { if ($current === null) {
throw new RuntimeException('Brak rekordu konfiguracji Allegro.'); throw new IntegrationConfigException('Brak rekordu konfiguracji Allegro.');
} }
$clientSecret = trim((string) ($payload['client_secret'] ?? '')); $clientSecret = trim((string) ($payload['client_secret'] ?? ''));

View File

@@ -3,7 +3,7 @@ declare(strict_types=1);
namespace App\Modules\Settings; namespace App\Modules\Settings;
use RuntimeException; use App\Core\Exceptions\AllegroOAuthException;
final class AllegroOAuthClient final class AllegroOAuthClient
{ {
@@ -62,7 +62,7 @@ final class AllegroOAuthClient
$accessToken = trim((string) ($payload['access_token'] ?? '')); $accessToken = trim((string) ($payload['access_token'] ?? ''));
$refreshToken = trim((string) ($payload['refresh_token'] ?? '')); $refreshToken = trim((string) ($payload['refresh_token'] ?? ''));
if ($accessToken === '' || $refreshToken === '') { if ($accessToken === '' || $refreshToken === '') {
throw new RuntimeException('Allegro nie zwrocilo kompletu tokenow OAuth.'); throw new AllegroOAuthException('Allegro nie zwrocilo kompletu tokenow OAuth.');
} }
return [ return [
@@ -95,7 +95,7 @@ final class AllegroOAuthClient
$accessToken = trim((string) ($payload['access_token'] ?? '')); $accessToken = trim((string) ($payload['access_token'] ?? ''));
if ($accessToken === '') { if ($accessToken === '') {
throw new RuntimeException('Allegro nie zwrocilo access_token po odswiezeniu.'); throw new AllegroOAuthException('Allegro nie zwrocilo access_token po odswiezeniu.');
} }
return [ return [
@@ -138,7 +138,7 @@ final class AllegroOAuthClient
): array { ): array {
$ch = curl_init($url); $ch = curl_init($url);
if ($ch === false) { if ($ch === false) {
throw new RuntimeException('Nie udalo sie zainicjowac polaczenia OAuth z Allegro.'); throw new AllegroOAuthException('Nie udalo sie zainicjowac polaczenia OAuth z Allegro.');
} }
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -160,18 +160,18 @@ final class AllegroOAuthClient
$ch = null; $ch = null;
if ($responseBody === false) { if ($responseBody === false) {
throw new RuntimeException('Blad polaczenia OAuth z Allegro: ' . $curlError); throw new AllegroOAuthException('Blad polaczenia OAuth z Allegro: ' . $curlError);
} }
$json = json_decode((string) $responseBody, true); $json = json_decode((string) $responseBody, true);
if (!is_array($json)) { if (!is_array($json)) {
throw new RuntimeException('Nieprawidlowy JSON odpowiedzi OAuth Allegro.'); throw new AllegroOAuthException('Nieprawidlowy JSON odpowiedzi OAuth Allegro.');
} }
if ($httpCode < 200 || $httpCode >= 300) { if ($httpCode < 200 || $httpCode >= 300) {
$error = trim((string) ($json['error'] ?? 'oauth_error')); $error = trim((string) ($json['error'] ?? 'oauth_error'));
$description = trim((string) ($json['error_description'] ?? 'Brak szczegolow bledu OAuth.')); $description = trim((string) ($json['error_description'] ?? 'Brak szczegolow bledu OAuth.'));
throw new RuntimeException('OAuth Allegro [' . $error . ']: ' . $description); throw new AllegroOAuthException('OAuth Allegro [' . $error . ']: ' . $description);
} }
return $json; return $json;

View File

@@ -7,6 +7,7 @@ use App\Core\Support\StringHelper;
use App\Modules\Orders\OrderImportRepository; use App\Modules\Orders\OrderImportRepository;
use App\Modules\Orders\OrdersRepository; use App\Modules\Orders\OrdersRepository;
use RuntimeException; use RuntimeException;
use AppCoreExceptionsAllegroApiException;
use Throwable; use Throwable;
final class AllegroOrderImportService final class AllegroOrderImportService
@@ -28,7 +29,7 @@ final class AllegroOrderImportService
{ {
$orderId = trim($checkoutFormId); $orderId = trim($checkoutFormId);
if ($orderId === '') { if ($orderId === '') {
throw new RuntimeException('Podaj ID zamowienia Allegro.'); throw new AllegroApiException('Podaj ID zamowienia Allegro.');
} }
[$accessToken, $env] = $this->tokenManager->resolveToken(); [$accessToken, $env] = $this->tokenManager->resolveToken();
@@ -101,7 +102,7 @@ final class AllegroOrderImportService
{ {
$checkoutFormId = trim((string) ($payload['id'] ?? '')); $checkoutFormId = trim((string) ($payload['id'] ?? ''));
if ($checkoutFormId === '') { if ($checkoutFormId === '') {
throw new RuntimeException('Odpowiedz Allegro nie zawiera ID zamowienia.'); throw new AllegroApiException('Odpowiedz Allegro nie zawiera ID zamowienia.');
} }
$status = trim((string) ($payload['status'] ?? '')); $status = trim((string) ($payload['status'] ?? ''));

View File

@@ -6,6 +6,7 @@ namespace App\Modules\Settings;
use App\Core\Support\StringHelper; use App\Core\Support\StringHelper;
use DateTimeImmutable; use DateTimeImmutable;
use RuntimeException; use RuntimeException;
use AppCoreExceptionsIntegrationConfigException;
use Throwable; use Throwable;
final class AllegroOrdersSyncService final class AllegroOrdersSyncService
@@ -42,7 +43,7 @@ final class AllegroOrdersSyncService
$integrationId = $this->integrationRepository->getActiveIntegrationId(); $integrationId = $this->integrationRepository->getActiveIntegrationId();
if ($integrationId <= 0) { if ($integrationId <= 0) {
throw new RuntimeException('Brak aktywnej integracji bazowej Allegro.'); throw new IntegrationConfigException('Brak aktywnej integracji bazowej Allegro.');
} }
$now = new DateTimeImmutable('now'); $now = new DateTimeImmutable('now');

View File

@@ -5,7 +5,7 @@ namespace App\Modules\Settings;
use DateInterval; use DateInterval;
use DateTimeImmutable; use DateTimeImmutable;
use RuntimeException; use AppCorexceptionsAllegroOAuthException;
use Throwable; use Throwable;
final class AllegroTokenManager final class AllegroTokenManager
@@ -23,7 +23,7 @@ final class AllegroTokenManager
{ {
$oauth = $this->repository->getTokenCredentials(); $oauth = $this->repository->getTokenCredentials();
if ($oauth === null) { if ($oauth === null) {
throw new RuntimeException('Brak polaczenia OAuth Allegro. Polacz konto w Ustawieniach.'); throw new AllegroOAuthException('Brak polaczenia OAuth Allegro. Polacz konto w Ustawieniach.');
} }
$env = (string) ($oauth['environment'] ?? 'sandbox'); $env = (string) ($oauth['environment'] ?? 'sandbox');
@@ -55,7 +55,7 @@ final class AllegroTokenManager
{ {
$oauth = $this->repository->getTokenCredentials(); $oauth = $this->repository->getTokenCredentials();
if ($oauth === null) { if ($oauth === null) {
throw new RuntimeException('Brak danych OAuth Allegro.'); throw new AllegroOAuthException('Brak danych OAuth Allegro.');
} }
$token = $this->oauthClient->refreshAccessToken( $token = $this->oauthClient->refreshAccessToken(
@@ -89,7 +89,7 @@ final class AllegroTokenManager
$updated = $this->repository->getTokenCredentials(); $updated = $this->repository->getTokenCredentials();
$newToken = trim((string) ($updated['access_token'] ?? '')); $newToken = trim((string) ($updated['access_token'] ?? ''));
if ($newToken === '') { if ($newToken === '') {
throw new RuntimeException('Nie udalo sie odswiezyc tokenu Allegro.'); throw new AllegroOAuthException('Nie udalo sie odswiezyc tokenu Allegro.');
} }
return [$newToken, (string) ($updated['environment'] ?? 'sandbox')]; return [$newToken, (string) ($updated['environment'] ?? 'sandbox')];

View File

@@ -3,7 +3,7 @@ declare(strict_types=1);
namespace App\Modules\Settings; namespace App\Modules\Settings;
use RuntimeException; use AppCorexceptionsApaczkaApiException;
final class ApaczkaApiClient final class ApaczkaApiClient
{ {
@@ -68,14 +68,14 @@ final class ApaczkaApiClient
{ {
$normalizedRoute = trim($route, " \t\n\r\0\x0B/"); $normalizedRoute = trim($route, " \t\n\r\0\x0B/");
if ($normalizedRoute === '') { if ($normalizedRoute === '') {
throw new RuntimeException('Nie podano endpointu API Apaczka.'); throw new ApaczkaApiException('Nie podano endpointu API Apaczka.');
} }
$routeWithTrailingSlash = $normalizedRoute . '/'; $routeWithTrailingSlash = $normalizedRoute . '/';
$expires = (string) (time() + 60); $expires = (string) (time() + 60);
$requestJson = json_encode($request); $requestJson = json_encode($request);
if (!is_string($requestJson)) { if (!is_string($requestJson)) {
throw new RuntimeException('Nie mozna zakodowac payloadu Apaczka.'); throw new ApaczkaApiException('Nie mozna zakodowac payloadu Apaczka.');
} }
$basePayload = [ $basePayload = [
@@ -107,11 +107,11 @@ final class ApaczkaApiClient
} }
if ($httpCode < 200 || $httpCode >= 300) { if ($httpCode < 200 || $httpCode >= 300) {
throw new RuntimeException('API Apaczka HTTP ' . $httpCode . ': ' . $message); throw new ApaczkaApiException('API Apaczka HTTP ' . $httpCode . ': ' . $message);
} }
if ($status !== 200) { if ($status !== 200) {
$responsePreview = substr(json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '', 0, 240); $responsePreview = substr(json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '', 0, 240);
throw new RuntimeException( throw new ApaczkaApiException(
'Blad API Apaczka (status ' . $status . '): ' . $message . '. Odpowiedz: ' . $responsePreview 'Blad API Apaczka (status ' . $status . '): ' . $message . '. Odpowiedz: ' . $responsePreview
); );
} }
@@ -122,12 +122,12 @@ final class ApaczkaApiClient
if ($lastSignatureError !== null) { if ($lastSignatureError !== null) {
[$decoded, $status, $message] = $lastSignatureError; [$decoded, $status, $message] = $lastSignatureError;
$responsePreview = substr(json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '', 0, 240); $responsePreview = substr(json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '', 0, 240);
throw new RuntimeException( throw new ApaczkaApiException(
'Blad API Apaczka (status ' . $status . '): ' . $message . '. Odpowiedz: ' . $responsePreview 'Blad API Apaczka (status ' . $status . '): ' . $message . '. Odpowiedz: ' . $responsePreview
); );
} }
throw new RuntimeException('Blad API Apaczka.'); throw new ApaczkaApiException('Blad API Apaczka.');
} }
private function buildSignature( private function buildSignature(
@@ -177,7 +177,7 @@ final class ApaczkaApiClient
$url = rtrim(self::API_BASE_URL, '/') . '/' . ltrim($endpointPath, '/'); $url = rtrim(self::API_BASE_URL, '/') . '/' . ltrim($endpointPath, '/');
$ch = curl_init($url); $ch = curl_init($url);
if ($ch === false) { if ($ch === false) {
throw new RuntimeException('Nie udalo sie zainicjowac polaczenia z API Apaczka.'); throw new ApaczkaApiException('Nie udalo sie zainicjowac polaczenia z API Apaczka.');
} }
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -199,14 +199,14 @@ final class ApaczkaApiClient
unset($ch); unset($ch);
if ($rawBody === false) { if ($rawBody === false) {
throw new RuntimeException('Blad polaczenia z API Apaczka: ' . $curlError); throw new ApaczkaApiException('Blad polaczenia z API Apaczka: ' . $curlError);
} }
$normalizedBody = ltrim((string) $rawBody, "\xEF\xBB\xBF \t\n\r\0\x0B"); $normalizedBody = ltrim((string) $rawBody, "\xEF\xBB\xBF \t\n\r\0\x0B");
$decoded = json_decode($normalizedBody, true); $decoded = json_decode($normalizedBody, true);
if (!is_array($decoded)) { if (!is_array($decoded)) {
$snippet = substr(trim(strip_tags($normalizedBody)), 0, 180); $snippet = substr(trim(strip_tags($normalizedBody)), 0, 180);
throw new RuntimeException( throw new ApaczkaApiException(
'Nieprawidlowa odpowiedz API Apaczka (brak JSON, HTTP ' . $httpCode . '). Fragment: ' . $snippet 'Nieprawidlowa odpowiedz API Apaczka (brak JSON, HTTP ' . $httpCode . '). Fragment: ' . $snippet
); );
} }

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Modules\Settings; namespace App\Modules\Settings;
use PDO; use PDO;
use RuntimeException; use AppCorexceptionsIntegrationConfigException;
use Throwable; use Throwable;
final class ApaczkaIntegrationRepository final class ApaczkaIntegrationRepository
@@ -56,12 +56,12 @@ final class ApaczkaIntegrationRepository
$row = $this->fetchRow(); $row = $this->fetchRow();
if ($row === null) { if ($row === null) {
throw new RuntimeException('Brak rekordu konfiguracji Apaczka.'); throw new IntegrationConfigException('Brak rekordu konfiguracji Apaczka.');
} }
$appId = trim((string) ($payload['app_id'] ?? '')); $appId = trim((string) ($payload['app_id'] ?? ''));
if ($appId === '') { if ($appId === '') {
throw new RuntimeException('Podaj App ID Apaczka.'); throw new IntegrationConfigException('Podaj App ID Apaczka.');
} }
$currentEncrypted = $this->resolveSecretEncrypted($row, $this->integrations->findById($integrationId)); $currentEncrypted = $this->resolveSecretEncrypted($row, $this->integrations->findById($integrationId));
@@ -72,7 +72,7 @@ final class ApaczkaIntegrationRepository
} }
if ($nextEncrypted === null || $nextEncrypted === '') { if ($nextEncrypted === null || $nextEncrypted === '') {
throw new RuntimeException('Podaj App Secret Apaczka.'); throw new IntegrationConfigException('Podaj App Secret Apaczka.');
} }
$stmt = $this->pdo->prepare( $stmt = $this->pdo->prepare(
@@ -129,7 +129,7 @@ final class ApaczkaIntegrationRepository
{ {
$credentials = $this->getApiCredentials(); $credentials = $this->getApiCredentials();
if ($credentials === null) { if ($credentials === null) {
throw new RuntimeException('Brak konfiguracji App ID/App Secret Apaczka.'); throw new IntegrationConfigException('Brak konfiguracji App ID/App Secret Apaczka.');
} }
return $apiClient->getServiceStructure( return $apiClient->getServiceStructure(

View File

@@ -5,7 +5,7 @@ namespace App\Modules\Settings;
use App\Core\Support\StringHelper; use App\Core\Support\StringHelper;
use PDO; use PDO;
use RuntimeException; use AppCorexceptionsIntegrationConfigException;
use Throwable; use Throwable;
final class InpostIntegrationRepository final class InpostIntegrationRepository
@@ -65,7 +65,7 @@ final class InpostIntegrationRepository
$this->ensureRow(); $this->ensureRow();
$current = $this->fetchRow(); $current = $this->fetchRow();
if ($current === null) { if ($current === null) {
throw new RuntimeException('Brak rekordu konfiguracji InPost.'); throw new IntegrationConfigException('Brak rekordu konfiguracji InPost.');
} }
$integrationId = $this->ensureBaseIntegration(); $integrationId = $this->ensureBaseIntegration();

View File

@@ -3,7 +3,7 @@ declare(strict_types=1);
namespace App\Modules\Settings; namespace App\Modules\Settings;
use RuntimeException; use AppCorexceptionsIntegrationConfigException;
final class IntegrationSecretCipher final class IntegrationSecretCipher
{ {
@@ -18,7 +18,7 @@ final class IntegrationSecretCipher
return null; return null;
} }
if ($this->secret === '') { if ($this->secret === '') {
throw new RuntimeException('Brak INTEGRATIONS_SECRET do szyfrowania danych integracji.'); throw new IntegrationConfigException('Brak INTEGRATIONS_SECRET do szyfrowania danych integracji.');
} }
$encryptionKey = hash('sha256', 'enc|' . $this->secret, true); $encryptionKey = hash('sha256', 'enc|' . $this->secret, true);
@@ -26,7 +26,7 @@ final class IntegrationSecretCipher
$iv = random_bytes(16); $iv = random_bytes(16);
$cipherRaw = openssl_encrypt($value, 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv); $cipherRaw = openssl_encrypt($value, 'AES-256-CBC', $encryptionKey, OPENSSL_RAW_DATA, $iv);
if ($cipherRaw === false) { if ($cipherRaw === false) {
throw new RuntimeException('Nie udalo sie zaszyfrowac danych integracji.'); throw new IntegrationConfigException('Nie udalo sie zaszyfrowac danych integracji.');
} }
$mac = hash_hmac('sha256', $iv . $cipherRaw, $hmacKey, true); $mac = hash_hmac('sha256', $iv . $cipherRaw, $hmacKey, true);
@@ -36,7 +36,7 @@ final class IntegrationSecretCipher
public function decrypt(string $encrypted): ?string public function decrypt(string $encrypted): ?string
{ {
if ($this->secret === '') { if ($this->secret === '') {
throw new RuntimeException('Brak INTEGRATIONS_SECRET do odszyfrowania danych integracji.'); throw new IntegrationConfigException('Brak INTEGRATIONS_SECRET do odszyfrowania danych integracji.');
} }
if (!str_starts_with($encrypted, 'v1:')) { if (!str_starts_with($encrypted, 'v1:')) {

View File

@@ -5,7 +5,7 @@ namespace App\Modules\Settings;
use App\Core\Support\StringHelper; use App\Core\Support\StringHelper;
use PDO; use PDO;
use RuntimeException; use AppCorexceptionsIntegrationConfigException;
use Throwable; use Throwable;
final class ShopproIntegrationsRepository final class ShopproIntegrationsRepository
@@ -154,7 +154,7 @@ final class ShopproIntegrationsRepository
if ($integrationId > 0) { if ($integrationId > 0) {
$existing = $this->findIntegration($integrationId); $existing = $this->findIntegration($integrationId);
if ($existing === null) { if ($existing === null) {
throw new RuntimeException('INTEGRATION_NOT_FOUND'); throw new IntegrationConfigException('INTEGRATION_NOT_FOUND');
} }
$encryptedApiKey = trim((string) ($existing['api_key_encrypted'] ?? '')); $encryptedApiKey = trim((string) ($existing['api_key_encrypted'] ?? ''));

View File

@@ -8,6 +8,8 @@ use App\Modules\Settings\AllegroApiClient;
use App\Modules\Settings\AllegroTokenManager; use App\Modules\Settings\AllegroTokenManager;
use App\Modules\Settings\CompanySettingsRepository; use App\Modules\Settings\CompanySettingsRepository;
use RuntimeException; use RuntimeException;
use AppCoreExceptionsIntegrationConfigException;
use AppCoreExceptionsShipmentException;
use Throwable; use Throwable;
final class AllegroShipmentService implements ShipmentProviderInterface final class AllegroShipmentService implements ShipmentProviderInterface
@@ -44,7 +46,7 @@ final class AllegroShipmentService implements ShipmentProviderInterface
{ {
$order = $this->ordersRepository->findDetails($orderId); $order = $this->ordersRepository->findDetails($orderId);
if ($order === null) { if ($order === null) {
throw new RuntimeException('Zamowienie nie znalezione.'); throw new ShipmentException('Zamowienie nie znalezione.');
} }
$company = $this->companySettings->getSettings(); $company = $this->companySettings->getSettings();
@@ -53,7 +55,7 @@ final class AllegroShipmentService implements ShipmentProviderInterface
$deliveryMethodId = trim((string) ($formData['delivery_method_id'] ?? '')); $deliveryMethodId = trim((string) ($formData['delivery_method_id'] ?? ''));
if ($deliveryMethodId === '') { if ($deliveryMethodId === '') {
throw new RuntimeException('Nie podano metody dostawy.'); throw new ShipmentException('Nie podano metody dostawy.');
} }
$receiverAddress = $this->buildReceiverAddress($order, $formData); $receiverAddress = $this->buildReceiverAddress($order, $formData);
@@ -177,12 +179,12 @@ final class AllegroShipmentService implements ShipmentProviderInterface
{ {
$package = $this->packages->findById($packageId); $package = $this->packages->findById($packageId);
if ($package === null) { if ($package === null) {
throw new RuntimeException('Paczka nie znaleziona.'); throw new ShipmentException('Paczka nie znaleziona.');
} }
$commandId = trim((string) ($package['command_id'] ?? '')); $commandId = trim((string) ($package['command_id'] ?? ''));
if ($commandId === '') { if ($commandId === '') {
throw new RuntimeException('Brak command_id dla tej paczki.'); throw new ShipmentException('Brak command_id dla tej paczki.');
} }
[$accessToken, $env] = $this->tokenManager->resolveToken(); [$accessToken, $env] = $this->tokenManager->resolveToken();
@@ -238,12 +240,12 @@ final class AllegroShipmentService implements ShipmentProviderInterface
{ {
$package = $this->packages->findById($packageId); $package = $this->packages->findById($packageId);
if ($package === null) { if ($package === null) {
throw new RuntimeException('Paczka nie znaleziona.'); throw new ShipmentException('Paczka nie znaleziona.');
} }
$shipmentId = trim((string) ($package['shipment_id'] ?? '')); $shipmentId = trim((string) ($package['shipment_id'] ?? ''));
if ($shipmentId === '') { if ($shipmentId === '') {
throw new RuntimeException('Przesylka nie zostala jeszcze utworzona.'); throw new ShipmentException('Przesylka nie zostala jeszcze utworzona.');
} }
[$accessToken, $env] = $this->tokenManager->resolveToken(); [$accessToken, $env] = $this->tokenManager->resolveToken();
@@ -346,14 +348,14 @@ final class AllegroShipmentService implements ShipmentProviderInterface
$required = ['street', 'city', 'postalCode', 'phone', 'email']; $required = ['street', 'city', 'postalCode', 'phone', 'email'];
foreach ($required as $field) { foreach ($required as $field) {
if (trim((string) ($sender[$field] ?? '')) === '') { if (trim((string) ($sender[$field] ?? '')) === '') {
throw new RuntimeException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak: ' . $field . ').'); throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak: ' . $field . ').');
} }
} }
$name = trim((string) ($sender['name'] ?? '')); $name = trim((string) ($sender['name'] ?? ''));
$company = trim((string) ($sender['company'] ?? '')); $company = trim((string) ($sender['company'] ?? ''));
if ($name === '' && $company === '') { if ($name === '' && $company === '') {
throw new RuntimeException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak nazwy/firmy).'); throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak nazwy/firmy).');
} }
} }

View File

@@ -7,7 +7,8 @@ use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\ApaczkaApiClient; use App\Modules\Settings\ApaczkaApiClient;
use App\Modules\Settings\ApaczkaIntegrationRepository; use App\Modules\Settings\ApaczkaIntegrationRepository;
use App\Modules\Settings\CompanySettingsRepository; use App\Modules\Settings\CompanySettingsRepository;
use RuntimeException; use AppCorexceptionsIntegrationConfigException;
use AppCorexceptionsShipmentException;
use Throwable; use Throwable;
final class ApaczkaShipmentService implements ShipmentProviderInterface final class ApaczkaShipmentService implements ShipmentProviderInterface
@@ -48,7 +49,7 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
{ {
$order = $this->ordersRepository->findDetails($orderId); $order = $this->ordersRepository->findDetails($orderId);
if ($order === null) { if ($order === null) {
throw new RuntimeException('Zamowienie nie znalezione.'); throw new ShipmentException('Zamowienie nie znalezione.');
} }
[$appId, $appSecret] = $this->requireCredentials(); [$appId, $appSecret] = $this->requireCredentials();
@@ -61,7 +62,7 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
$deliveryMethodId = trim((string) ($formData['delivery_method_id'] ?? '')); $deliveryMethodId = trim((string) ($formData['delivery_method_id'] ?? ''));
if ($deliveryMethodId === '') { if ($deliveryMethodId === '') {
throw new RuntimeException('Nie podano uslugi Apaczka.'); throw new ShipmentException('Nie podano uslugi Apaczka.');
} }
$serviceDefinition = $this->resolveServiceDefinition($appId, $appSecret, $deliveryMethodId); $serviceDefinition = $this->resolveServiceDefinition($appId, $appSecret, $deliveryMethodId);
@@ -153,7 +154,7 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
'status' => 'error', 'status' => 'error',
'error_message' => $errorMessage, 'error_message' => $errorMessage,
]); ]);
throw new RuntimeException($errorMessage, 0, $exception); throw new ShipmentException($errorMessage, 0, $exception);
} }
$orderResponse = is_array($response['response']['order'] ?? null) ? $response['response']['order'] : []; $orderResponse = is_array($response['response']['order'] ?? null) ? $response['response']['order'] : [];
@@ -180,7 +181,7 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
{ {
$package = $this->packages->findById($packageId); $package = $this->packages->findById($packageId);
if ($package === null) { if ($package === null) {
throw new RuntimeException('Paczka nie znaleziona.'); throw new ShipmentException('Paczka nie znaleziona.');
} }
$shipmentId = max(0, (int) ($package['shipment_id'] ?? 0)); $shipmentId = max(0, (int) ($package['shipment_id'] ?? 0));
@@ -213,12 +214,12 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
{ {
$package = $this->packages->findById($packageId); $package = $this->packages->findById($packageId);
if ($package === null) { if ($package === null) {
throw new RuntimeException('Paczka nie znaleziona.'); throw new ShipmentException('Paczka nie znaleziona.');
} }
$shipmentId = max(0, (int) ($package['shipment_id'] ?? 0)); $shipmentId = max(0, (int) ($package['shipment_id'] ?? 0));
if ($shipmentId <= 0) { if ($shipmentId <= 0) {
throw new RuntimeException('Przesylka nie zostala jeszcze utworzona.'); throw new ShipmentException('Przesylka nie zostala jeszcze utworzona.');
} }
[$appId, $appSecret] = $this->requireCredentials(); [$appId, $appSecret] = $this->requireCredentials();
@@ -243,12 +244,12 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
$base64 = trim((string) ($waybillRaw['data'] ?? '')); $base64 = trim((string) ($waybillRaw['data'] ?? ''));
} }
if ($base64 === '') { if ($base64 === '') {
throw new RuntimeException('Apaczka nie zwrocila danych etykiety.'); throw new ShipmentException('Apaczka nie zwrocila danych etykiety.');
} }
$binary = base64_decode($base64, true); $binary = base64_decode($base64, true);
if (!is_string($binary) || $binary === '') { if (!is_string($binary) || $binary === '') {
throw new RuntimeException('Nie mozna odczytac etykiety Apaczka.'); throw new ShipmentException('Nie mozna odczytac etykiety Apaczka.');
} }
$dir = rtrim($storagePath, '/\\') . '/labels'; $dir = rtrim($storagePath, '/\\') . '/labels';
@@ -292,7 +293,7 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
$appSecret = trim((string) ($credentials['app_secret'] ?? '')); $appSecret = trim((string) ($credentials['app_secret'] ?? ''));
if ($appId === '' || $appSecret === '') { if ($appId === '' || $appSecret === '') {
throw new RuntimeException('Brak konfiguracji Apaczka (app_id/app_secret).'); throw new IntegrationConfigException('Brak konfiguracji Apaczka (app_id/app_secret).');
} }
return [$appId, $appSecret]; return [$appId, $appSecret];
@@ -409,7 +410,7 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
} }
if ($name === '' || $street === '' || $city === '' || $postalCode === '' || $countryCode === '') { if ($name === '' || $street === '' || $city === '' || $postalCode === '' || $countryCode === '') {
throw new RuntimeException('Brak wymaganych danych adresowych odbiorcy.'); throw new ShipmentException('Brak wymaganych danych adresowych odbiorcy.');
} }
$receiver = [ $receiver = [
@@ -663,13 +664,13 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
$serviceName = trim((string) ($serviceDefinition['name'] ?? '')); $serviceName = trim((string) ($serviceDefinition['name'] ?? ''));
if ($requiresReceiverPoint && $receiverPoint === '') { if ($requiresReceiverPoint && $receiverPoint === '') {
throw new RuntimeException( throw new ShipmentException(
'Wybrana usluga Apaczka (' . ($serviceName !== '' ? $serviceName : 'ID') 'Wybrana usluga Apaczka (' . ($serviceName !== '' ? $serviceName : 'ID')
. ') wymaga punktu odbioru (`receiver_point_id`).' . ') wymaga punktu odbioru (`receiver_point_id`).'
); );
} }
if ($requiresSenderPoint && $senderPoint === '') { if ($requiresSenderPoint && $senderPoint === '') {
throw new RuntimeException( throw new ShipmentException(
'Wybrana usluga Apaczka (' . ($serviceName !== '' ? $serviceName : 'ID') 'Wybrana usluga Apaczka (' . ($serviceName !== '' ? $serviceName : 'ID')
. ') wymaga punktu nadania (`sender_point_id`).' . ') wymaga punktu nadania (`sender_point_id`).'
); );
@@ -865,18 +866,18 @@ final class ApaczkaShipmentService implements ShipmentProviderInterface
$required = ['street', 'city', 'postalCode', 'phone', 'email']; $required = ['street', 'city', 'postalCode', 'phone', 'email'];
foreach ($required as $field) { foreach ($required as $field) {
if (trim((string) ($sender[$field] ?? '')) === '') { if (trim((string) ($sender[$field] ?? '')) === '') {
throw new RuntimeException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak: ' . $field . ').'); throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak: ' . $field . ').');
} }
} }
$name = trim((string) ($sender['name'] ?? '')); $name = trim((string) ($sender['name'] ?? ''));
$company = trim((string) ($sender['company'] ?? '')); $company = trim((string) ($sender['company'] ?? ''));
if ($name === '' && $company === '') { if ($name === '' && $company === '') {
throw new RuntimeException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak nazwy/firmy).'); throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak nazwy/firmy).');
} }
$contactPerson = trim((string) ($sender['contactPerson'] ?? $name ?? '')); $contactPerson = trim((string) ($sender['contactPerson'] ?? $name ?? ''));
if ($contactPerson === '') { if ($contactPerson === '') {
throw new RuntimeException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak osoby kontaktowej).'); throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak osoby kontaktowej).');
} }
} }
} }

View File

@@ -13,7 +13,7 @@ use App\Modules\Auth\AuthService;
use App\Modules\Orders\OrdersRepository; use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\CarrierDeliveryMethodMappingRepository; use App\Modules\Settings\CarrierDeliveryMethodMappingRepository;
use App\Modules\Settings\CompanySettingsRepository; use App\Modules\Settings\CompanySettingsRepository;
use RuntimeException; use AppCorexceptionsShipmentException;
use Throwable; use Throwable;
final class ShipmentController final class ShipmentController
@@ -167,7 +167,7 @@ final class ShipmentController
} }
$provider = $this->providerRegistry->get($providerCode); $provider = $this->providerRegistry->get($providerCode);
if ($provider === null) { if ($provider === null) {
throw new RuntimeException('Nieznany provider przesylek: ' . $providerCode); throw new ShipmentException('Nieznany provider przesylek: ' . $providerCode);
} }
$result = $provider->createShipment($orderId, [ $result = $provider->createShipment($orderId, [
@@ -280,13 +280,13 @@ final class ShipmentController
try { try {
$package = $this->packageRepository->findById($packageId); $package = $this->packageRepository->findById($packageId);
if ($package === null) { if ($package === null) {
throw new RuntimeException('Paczka nie znaleziona.'); throw new ShipmentException('Paczka nie znaleziona.');
} }
$providerCode = strtolower(trim((string) ($package['provider'] ?? 'allegro_wza'))); $providerCode = strtolower(trim((string) ($package['provider'] ?? 'allegro_wza')));
$provider = $this->providerRegistry->get($providerCode); $provider = $this->providerRegistry->get($providerCode);
if ($provider === null) { if ($provider === null) {
throw new RuntimeException('Brak providera: ' . $providerCode); throw new ShipmentException('Brak providera: ' . $providerCode);
} }
$result = $provider->downloadLabel($packageId, $this->storagePath); $result = $provider->downloadLabel($packageId, $this->storagePath);