update 20.10.2025
This commit is contained in:
parent
8c11130b5d
commit
a939cd51ef
616 changed files with 84821 additions and 4121 deletions
|
|
@ -1,3 +1,6 @@
|
|||
#DHL Dokuments
|
||||
https://developer.dhl.com/api-reference/parcel-de-shipping-post-parcel-germany-v2?language_content_entity=en#get-started-section/
|
||||
|
||||
# DHL Laravel Package
|
||||
|
||||
A comprehensive Laravel package for DHL shipping, tracking, and returns functionality with direct API integration.
|
||||
|
|
@ -20,7 +23,7 @@ A comprehensive Laravel package for DHL shipping, tracking, and returns function
|
|||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"type": "path",
|
||||
"url": "./packages/acme-laravel-dhl"
|
||||
}
|
||||
],
|
||||
|
|
@ -83,7 +86,7 @@ $orderData = [
|
|||
],
|
||||
'consignee' => [
|
||||
'name' => 'Customer Name',
|
||||
'street' => 'Kundenstraße 456', // House number will be auto-parsed
|
||||
'street' => 'Kundenstraße 456', // House number will be auto-parsed
|
||||
'postalCode' => '54321',
|
||||
'city' => 'Munich',
|
||||
'country' => 'DE'
|
||||
|
|
@ -104,7 +107,7 @@ The package automatically handles German address formats by extracting house num
|
|||
|
||||
// Supported formats:
|
||||
"Musterstraße 123" -> street: "Musterstraße", number: "123"
|
||||
"Am Markt 7" -> street: "Am Markt", number: "7"
|
||||
"Am Markt 7" -> street: "Am Markt", number: "7"
|
||||
"Karl-Marx-Straße 156" -> street: "Karl-Marx-Straße", number: "156"
|
||||
"Lindenstraße 1-3" -> street: "Lindenstraße", number: "1-3"
|
||||
"Muster Str. 99" -> street: "Muster Str.", number: "99"
|
||||
|
|
@ -171,7 +174,7 @@ When enabled, label creation and tracking updates will be processed asynchronous
|
|||
The package includes comprehensive error handling:
|
||||
|
||||
- `DhlApiException` - General API errors
|
||||
- `DhlAuthenticationException` - Authentication failures
|
||||
- `DhlAuthenticationException` - Authentication failures
|
||||
- `DhlValidationException` - Data validation errors
|
||||
|
||||
## Testing
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
return [
|
||||
'base_url' => env('DHL_BASE_URL', 'https://api-eu.dhl.com'),
|
||||
'sandbox_url' => env('DHL_SANDBOX_URL', 'https://api-sandbox.dhl.com'),
|
||||
'api_key' => env('DHL_API_KEY'),
|
||||
'username' => env('DHL_USERNAME'),
|
||||
'password' => env('DHL_PASSWORD'),
|
||||
|
|
@ -10,4 +11,15 @@ return [
|
|||
'profile' => env('DHL_PROFILE', 'STANDARD_GRUPPENPROFIL'),
|
||||
'webhook' => ['enabled' => env('DHL_WEBHOOK_ENABLED', false), 'secret' => env('DHL_WEBHOOK_SECRET'), 'route' => env('DHL_WEBHOOK_ROUTE', 'dhl/webhooks/tracking')],
|
||||
'use_queue' => env('DHL_USE_QUEUE', false), // Set to true to enable queue jobs for async processing
|
||||
'legacy' => [
|
||||
'test_mode' => env('DHL_TEST_MODE', true),
|
||||
'sandbox' => env('DHL_SANDBOX', true),
|
||||
],
|
||||
'ssl' => [
|
||||
'verify_peer' => env('DHL_SSL_VERIFY_PEER', true),
|
||||
'verify_host' => env('DHL_SSL_VERIFY_HOST', true),
|
||||
'ssl_version' => env('DHL_SSL_VERSION', 'TLSv1_2'),
|
||||
'timeout' => env('DHL_TIMEOUT', 30),
|
||||
'connect_timeout' => env('DHL_CONNECT_TIMEOUT', 10),
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ return new class extends Migration
|
|||
$table->string('dhl_shipment_no')->nullable()->index();
|
||||
$table->enum('type', ['outbound', 'return'])->default('outbound');
|
||||
$table->unsignedBigInteger('related_shipment_id')->nullable()->index(); // For returns
|
||||
$table->string('routing_code')->nullable();
|
||||
|
||||
|
||||
// Product and billing
|
||||
$table->string('product_code')->default('V01PAK');
|
||||
|
|
@ -32,6 +34,12 @@ return new class extends Migration
|
|||
$table->string('tracking_status')->nullable(); // Last DHL status text
|
||||
$table->timestamp('last_tracked_at')->nullable();
|
||||
|
||||
// Recipient information
|
||||
$table->string('firstname')->nullable();
|
||||
$table->string('lastname')->nullable();
|
||||
$table->string('company')->nullable();
|
||||
$table->json('recipient')->nullable();
|
||||
|
||||
// API response data
|
||||
$table->json('api_response_data')->nullable();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,12 @@ class DhlServiceProvider extends ServiceProvider
|
|||
|
||||
// Bind DhlClient as a singleton
|
||||
$this->app->singleton(Support\DhlClient::class, function ($app) {
|
||||
// Check if we're in test/sandbox mode
|
||||
$isTestMode = config('dhl.legacy.test_mode', false) || config('dhl.legacy.sandbox', false);
|
||||
$baseUrl = $isTestMode ? config('dhl.sandbox_url') : config('dhl.base_url');
|
||||
|
||||
return new Support\DhlClient(
|
||||
config('dhl.base_url'),
|
||||
$baseUrl,
|
||||
config('dhl.api_key'),
|
||||
config('dhl.username'),
|
||||
config('dhl.password')
|
||||
|
|
|
|||
|
|
@ -26,12 +26,17 @@ class DhlShipment extends Model
|
|||
'label_format',
|
||||
'label_path',
|
||||
'status',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'company',
|
||||
'recipient',
|
||||
'tracking_status',
|
||||
'last_tracked_at',
|
||||
'api_response_data'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'recipient' => 'array',
|
||||
'api_response_data' => 'array',
|
||||
'last_tracked_at' => 'datetime',
|
||||
'weight_kg' => 'decimal:3'
|
||||
|
|
@ -48,6 +53,7 @@ class DhlShipment extends Model
|
|||
'unknown' => 'unknown'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get the tracking events for this shipment
|
||||
*/
|
||||
|
|
@ -146,4 +152,36 @@ class DhlShipment extends Model
|
|||
{
|
||||
return $this->status === 'delivered';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translated status for current locale
|
||||
*/
|
||||
public function getStatusTranslation(): string
|
||||
{
|
||||
return __('dhl.status.' . $this->status, [], $this->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translated status for any status
|
||||
*/
|
||||
public static function getStatusTranslationFor(string $status): string
|
||||
{
|
||||
return __('dhl.status.' . $status, [], $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translated type for current locale
|
||||
*/
|
||||
public function getTypeTranslation(): string
|
||||
{
|
||||
return __('dhl.type.' . $this->type, [], $this->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translated product code for current locale
|
||||
*/
|
||||
public function getProductCodeTranslation(): string
|
||||
{
|
||||
return __('dhl.product_codes.' . $this->product_code, [], $this->product_code);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,18 +365,29 @@ class ShippingService
|
|||
*/
|
||||
private function getBillingNumberForProduct(string $productCode): string
|
||||
{
|
||||
// Try to get account number from config by product code
|
||||
$accountNumber = config("dhl.account_numbers.{$productCode}");
|
||||
// Check if we're in test/sandbox mode
|
||||
$isTestMode = config('dhl.legacy.test_mode', false) || config('dhl.legacy.sandbox', false);
|
||||
|
||||
if ($accountNumber) {
|
||||
return $accountNumber;
|
||||
if ($isTestMode) {
|
||||
// Use test billing number for sandbox mode
|
||||
$testBillingNumber = '33333333330102';
|
||||
Log::info('Using DHL test billing number (sandbox mode)', [
|
||||
'product_code' => $productCode,
|
||||
'billing_number' => $testBillingNumber,
|
||||
'test_mode' => true
|
||||
]);
|
||||
return $testBillingNumber;
|
||||
}
|
||||
|
||||
// Try to get from admin settings via Setting model
|
||||
// Try to get from admin settings via Setting model first (database settings override config)
|
||||
try {
|
||||
$settingKey = 'dhl_account_' . strtolower($productCode);
|
||||
$accountNumber = \App\Models\Setting::getContentBySlug($settingKey);
|
||||
if ($accountNumber) {
|
||||
Log::info('Using DHL account number from database settings', [
|
||||
'product_code' => $productCode,
|
||||
'account_number' => $accountNumber
|
||||
]);
|
||||
return $accountNumber;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
|
|
@ -387,6 +398,16 @@ class ShippingService
|
|||
]);
|
||||
}
|
||||
|
||||
// Try to get account number from config by product code
|
||||
$accountNumber = config("dhl.account_numbers.{$productCode}");
|
||||
if ($accountNumber) {
|
||||
Log::info('Using DHL account number from config file', [
|
||||
'product_code' => $productCode,
|
||||
'account_number' => $accountNumber
|
||||
]);
|
||||
return $accountNumber;
|
||||
}
|
||||
|
||||
// Fallback to default billing number
|
||||
$defaultBillingNumber = config('dhl.billing_number') ?: config('dhl.account_numbers.default');
|
||||
|
||||
|
|
@ -435,6 +456,35 @@ class ShippingService
|
|||
*/
|
||||
private function createShipmentRecord(array $orderData, array $payload, array $response, ?string $shipmentNumber): DhlShipment
|
||||
{
|
||||
// Extract recipient data from orderData (can be modified in modal)
|
||||
$consignee = $orderData['consignee'] ?? [];
|
||||
|
||||
// Parse name from consignee data
|
||||
$fullName = trim($consignee['name'] ?? '');
|
||||
$nameParts = explode(' ', $fullName, 2);
|
||||
$firstname = $nameParts[0] ?? '';
|
||||
$lastname = $nameParts[1] ?? '';
|
||||
|
||||
// If name is empty, try to get from separate fields
|
||||
if (empty($firstname) && empty($lastname)) {
|
||||
$firstname = $consignee['firstname'] ?? '';
|
||||
$lastname = $consignee['lastname'] ?? '';
|
||||
}
|
||||
|
||||
// Prepare complete recipient address as JSON
|
||||
$recipientData = [
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'company' => $consignee['name2'] ?? '',
|
||||
'street' => $consignee['street'] ?? '',
|
||||
'houseNumber' => $consignee['houseNumber'] ?? '',
|
||||
'postalCode' => $consignee['postalCode'] ?? '',
|
||||
'city' => $consignee['city'] ?? '',
|
||||
'country' => $consignee['country'] ?? '',
|
||||
'email' => $consignee['email'] ?? '',
|
||||
'phone' => $consignee['phone'] ?? '',
|
||||
];
|
||||
|
||||
return DhlShipment::create([
|
||||
'order_id' => $orderData['order_id'] ?? null,
|
||||
'dhl_shipment_no' => $shipmentNumber,
|
||||
|
|
@ -446,7 +496,13 @@ class ShippingService
|
|||
'status' => 'created',
|
||||
'label_format' => $payload['shipments'][0]['print']['format'],
|
||||
'label_path' => null,
|
||||
'api_response_data' => $response
|
||||
'api_response_data' => $response,
|
||||
|
||||
// Recipient data (can be modified in modal)
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'company' => $consignee['name2'] ?? '',
|
||||
'recipient' => $recipientData
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
namespace Acme\Dhl\Services;
|
||||
|
||||
use Acme\Dhl\Support\DhlClient;
|
||||
use Acme\Dhl\Jobs\SyncTrackingJob;
|
||||
use Acme\Dhl\Models\DhlShipment;
|
||||
use Acme\Dhl\Models\DhlTrackingEvent;
|
||||
use InvalidArgumentException;
|
||||
use Acme\Dhl\Support\DhlClient;
|
||||
use Exception;
|
||||
use Acme\Dhl\Jobs\SyncTrackingJob;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* DHL Tracking Service for fetching and managing tracking information
|
||||
|
|
@ -19,10 +19,11 @@ class TrackingService
|
|||
public function __construct(protected DhlClient $client) {}
|
||||
|
||||
/**
|
||||
* Fetch tracking status for a shipment
|
||||
* Fetch tracking status for a shipment using proper DHL APIs
|
||||
*
|
||||
* @param string $trackingNumber DHL tracking number
|
||||
* @param string $trackingNumber DHL tracking number
|
||||
* @return array Tracking data from DHL API
|
||||
*
|
||||
* @throws InvalidArgumentException When tracking number is empty
|
||||
* @throws Exception When API request fails
|
||||
*/
|
||||
|
|
@ -31,28 +32,47 @@ class TrackingService
|
|||
if (empty($trackingNumber)) {
|
||||
throw new InvalidArgumentException('Tracking number is required');
|
||||
}
|
||||
|
||||
if (config('dhl.use_queue')) {
|
||||
SyncTrackingJob::dispatch($trackingNumber);
|
||||
|
||||
return ['queued' => true];
|
||||
}
|
||||
|
||||
$response = $this->client->request('get', '/post-tracking/api/shipments', [], [
|
||||
'trackingNumber' => $trackingNumber
|
||||
]);
|
||||
Log::info('Fetched tracking status', ['trackingNumber' => $trackingNumber]);
|
||||
// Use the app's DhlTrackingService for actual API calls
|
||||
$trackingService = app(\App\Services\DhlTrackingService::class);
|
||||
$result = $trackingService->trackShipment($trackingNumber);
|
||||
|
||||
$events = data_get($response, 'shipments.0.events', []);
|
||||
$shipment = $this->findOrCreateShipment($trackingNumber);
|
||||
if ($result['success']) {
|
||||
Log::info('Fetched tracking status', [
|
||||
'trackingNumber' => $trackingNumber,
|
||||
'api_used' => $result['api_used'] ?? 'unknown',
|
||||
]);
|
||||
|
||||
$this->updateTrackingEvents($shipment, $events);
|
||||
// Update local database
|
||||
$shipment = $this->findOrCreateShipment($trackingNumber);
|
||||
|
||||
return $response;
|
||||
if (isset($result['events'])) {
|
||||
$this->updateTrackingEvents($shipment, $result['events']);
|
||||
}
|
||||
|
||||
// Update shipment status
|
||||
$shipment->update([
|
||||
'status' => $this->mapDhlStatusToInternal($result['status']),
|
||||
'tracking_status' => $result['status_text'],
|
||||
'last_tracked_at' => now(),
|
||||
]);
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
throw new Exception($result['message'] ?? 'Failed to fetch tracking status');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tracking status for multiple shipments
|
||||
*
|
||||
* @param array $trackingNumbers Array of tracking numbers to update
|
||||
* @param array $trackingNumbers Array of tracking numbers to update
|
||||
* @return array Results for each tracking number
|
||||
*/
|
||||
public function updateMultipleStatus(array $trackingNumbers): array
|
||||
|
|
@ -74,14 +94,14 @@ class TrackingService
|
|||
/**
|
||||
* Get latest tracking status for a shipment
|
||||
*
|
||||
* @param string $trackingNumber DHL tracking number
|
||||
* @param string $trackingNumber DHL tracking number
|
||||
* @return array|null Latest tracking event or null if not found
|
||||
*/
|
||||
public function getLatestStatus(string $trackingNumber): ?array
|
||||
{
|
||||
$shipment = DhlShipment::where('dhl_shipment_no', $trackingNumber)->first();
|
||||
|
||||
if (!$shipment) {
|
||||
if (! $shipment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +122,7 @@ class TrackingService
|
|||
['dhl_shipment_no' => $trackingNumber],
|
||||
[
|
||||
'type' => 'outbound',
|
||||
'status' => 'unknown'
|
||||
'status' => 'unknown',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
@ -130,12 +150,12 @@ class TrackingService
|
|||
[
|
||||
'shipment_id' => $shipment->id,
|
||||
'status_code' => $eventData['statusCode'] ?? null,
|
||||
'event_time' => $eventData['timestamp'] ?? null
|
||||
'event_time' => $eventData['timestamp'] ?? null,
|
||||
],
|
||||
[
|
||||
'status_text' => $eventData['status'] ?? null,
|
||||
'location' => $this->extractLocation($eventData),
|
||||
'raw' => $eventData
|
||||
'raw' => $eventData,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -173,8 +193,26 @@ class TrackingService
|
|||
$shipment->update([
|
||||
'status' => $status,
|
||||
'tracking_status' => $latestEvent['status'] ?? null,
|
||||
'last_tracked_at' => now()
|
||||
'last_tracked_at' => now(),
|
||||
]);
|
||||
Log::info('Updated shipment status', ['shipmentId' => $shipment->id, 'newStatus' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map DHL status codes to internal status
|
||||
*/
|
||||
private function mapDhlStatusToInternal(string $dhlStatus): string
|
||||
{
|
||||
$statusMap = [
|
||||
'pre-transit' => 'created',
|
||||
'transit' => 'in_transit',
|
||||
'out-for-delivery' => 'out_for_delivery',
|
||||
'delivered' => 'delivered',
|
||||
'failure' => 'failed',
|
||||
'returned' => 'returned',
|
||||
'exception' => 'exception',
|
||||
];
|
||||
|
||||
return $statusMap[$dhlStatus] ?? 'unknown';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ class DhlClient
|
|||
protected ?string $password
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get DHL-specific logger
|
||||
*/
|
||||
private function getDhlLogger()
|
||||
{
|
||||
return Log::channel('dhl');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP request to DHL API
|
||||
*
|
||||
|
|
@ -39,6 +47,20 @@ class DhlClient
|
|||
$request = Http::baseUrl($this->baseUrl)
|
||||
->withHeaders($this->buildHeaders())
|
||||
->timeout(30)
|
||||
->withOptions([
|
||||
'verify' => config('dhl.ssl.verify_peer', true), // SSL certificate verification
|
||||
'http_errors' => false, // Don't throw exceptions on HTTP error codes
|
||||
'curl' => [
|
||||
CURLOPT_SSL_VERIFYPEER => config('dhl.ssl.verify_peer', true),
|
||||
CURLOPT_SSL_VERIFYHOST => config('dhl.ssl.verify_host', true) ? 2 : 0,
|
||||
CURLOPT_SSLVERSION => $this->getSslVersion(),
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 5,
|
||||
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
|
||||
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
|
||||
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
|
||||
]
|
||||
])
|
||||
->retry(3, 300, function ($exception, $attempt) {
|
||||
if ($exception instanceof RequestException && $exception->response->status() === 429) {
|
||||
$delay = min(1000000 * $attempt, 10000000); // Max 10 seconds
|
||||
|
|
@ -67,7 +89,7 @@ class DhlClient
|
|||
if ($response->failed()) {
|
||||
// Log additional debug info for 400 errors
|
||||
if ($response->status() === 400) {
|
||||
Log::error('[DHL API] HTTP 400 Bad Request Details', [
|
||||
$this->getDhlLogger()->error('[DHL API] HTTP 400 Bad Request Details', [
|
||||
'method' => $method,
|
||||
'uri' => $uri,
|
||||
'request_payload' => $payload,
|
||||
|
|
@ -83,10 +105,10 @@ class DhlClient
|
|||
|
||||
return $response->json() ?? [];
|
||||
} catch (RequestException $e) {
|
||||
Log::error('DHL API request failed', ['error' => $e->getMessage(), 'method' => $method, 'uri' => $uri]);
|
||||
$this->getDhlLogger()->error('DHL API request failed', ['error' => $e->getMessage(), 'method' => $method, 'uri' => $uri]);
|
||||
throw new DhlApiException("DHL API request failed: {$e->getMessage()}", $e->getCode(), $e);
|
||||
} catch (Exception $e) {
|
||||
Log::error('DHL API error', ['error' => $e->getMessage()]);
|
||||
$this->getDhlLogger()->error('DHL API error', ['error' => $e->getMessage()]);
|
||||
// Re-throw our own exceptions
|
||||
if ($e instanceof DhlApiException) {
|
||||
throw $e;
|
||||
|
|
@ -152,6 +174,53 @@ class DhlClient
|
|||
?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SSL version constant based on configuration
|
||||
*/
|
||||
private function getSslVersion(): int
|
||||
{
|
||||
$sslVersion = config('dhl.ssl.ssl_version', 'TLSv1_2');
|
||||
|
||||
return match ($sslVersion) {
|
||||
'TLSv1_0' => CURL_SSLVERSION_TLSv1_0,
|
||||
'TLSv1_1' => CURL_SSLVERSION_TLSv1_1,
|
||||
'TLSv1_2' => CURL_SSLVERSION_TLSv1_2,
|
||||
'TLSv1_3' => defined('CURL_SSLVERSION_TLSv1_3') ? CURL_SSLVERSION_TLSv1_3 : CURL_SSLVERSION_TLSv1_2,
|
||||
default => CURL_SSLVERSION_TLSv1_2,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Log detailed server environment information for debugging
|
||||
*/
|
||||
public function logServerEnvironment(): void
|
||||
{
|
||||
$info = [
|
||||
'php_version' => PHP_VERSION,
|
||||
'curl_version' => curl_version(),
|
||||
'openssl_version' => OPENSSL_VERSION_TEXT ?? 'Unknown',
|
||||
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
|
||||
'dhl_config' => [
|
||||
'base_url' => $this->baseUrl,
|
||||
'has_api_key' => !empty($this->apiKey),
|
||||
'has_username' => !empty($this->username),
|
||||
'has_password' => !empty($this->password),
|
||||
'ssl_verify_peer' => config('dhl.ssl.verify_peer', true),
|
||||
'ssl_verify_host' => config('dhl.ssl.verify_host', true),
|
||||
'ssl_version' => config('dhl.ssl.ssl_version', 'TLSv1_2'),
|
||||
'timeout' => config('dhl.ssl.timeout', 30),
|
||||
'connect_timeout' => config('dhl.ssl.connect_timeout', 10),
|
||||
],
|
||||
'environment' => [
|
||||
'APP_ENV' => config('app.env'),
|
||||
'APP_DEBUG' => config('app.debug'),
|
||||
'APP_URL' => config('app.url'),
|
||||
]
|
||||
];
|
||||
|
||||
$this->getDhlLogger()->info('DHL Server Environment Debug Info', $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test connection to DHL API
|
||||
*
|
||||
|
|
@ -159,40 +228,370 @@ class DhlClient
|
|||
*/
|
||||
public function testConnection(): bool
|
||||
{
|
||||
try {
|
||||
// Check basic connectivity and authentication
|
||||
// Use the simplest possible endpoint to minimize permission issues
|
||||
$response = Http::baseUrl($this->baseUrl)
|
||||
->withHeaders($this->buildHeaders())
|
||||
->withBasicAuth($this->username, $this->password)
|
||||
->timeout(10)
|
||||
->get('/');
|
||||
// Log server environment for debugging
|
||||
$this->logServerEnvironment();
|
||||
|
||||
// If we get any response (even 404), the connection and auth are working
|
||||
if ($response->status() === 401) {
|
||||
Log::error('DHL API authentication failed: Invalid username/password');
|
||||
return false;
|
||||
// Try multiple connection methods for better compatibility
|
||||
$methods = [
|
||||
'method1' => 'Laravel HTTP with enhanced SSL',
|
||||
'method2' => 'Laravel HTTP with relaxed SSL',
|
||||
'method3' => 'Direct cURL fallback'
|
||||
];
|
||||
|
||||
foreach ($methods as $methodKey => $methodName) {
|
||||
try {
|
||||
$this->getDhlLogger()->info("DHL API connection test - trying {$methodName}", [
|
||||
'method' => $methodKey,
|
||||
'base_url' => $this->baseUrl
|
||||
]);
|
||||
|
||||
$success = $this->testConnectionWithMethod($methodKey);
|
||||
|
||||
if ($success) {
|
||||
$this->getDhlLogger()->info("DHL API connection test successful with {$methodName}", [
|
||||
'method' => $methodKey,
|
||||
'base_url' => $this->baseUrl
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->getDhlLogger()->warning("DHL API connection test failed with {$methodName}", [
|
||||
'method' => $methodKey,
|
||||
'error' => $e->getMessage(),
|
||||
'base_url' => $this->baseUrl
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->status() === 403 && str_contains($response->body(), 'api-key')) {
|
||||
Log::error('DHL API authentication failed: Invalid API key');
|
||||
$this->getDhlLogger()->error('DHL API connection test failed with all methods', [
|
||||
'base_url' => $this->baseUrl,
|
||||
'tried_methods' => array_keys($methods)
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test connection with specific method
|
||||
*/
|
||||
private function testConnectionWithMethod(string $method): bool
|
||||
{
|
||||
switch ($method) {
|
||||
case 'method1':
|
||||
return $this->testConnectionEnhanced();
|
||||
case 'method2':
|
||||
return $this->testConnectionRelaxed();
|
||||
case 'method3':
|
||||
return $this->testConnectionCurl();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any other response code (including 404, 403 for endpoint access) means connection works
|
||||
Log::info('DHL API connection test successful', [
|
||||
'status' => $response->status(),
|
||||
'has_api_key' => !empty($this->apiKey),
|
||||
'has_credentials' => !empty($this->username) && !empty($this->password)
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
Log::error('DHL API connection test failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'base_url' => $this->baseUrl
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced SSL connection test
|
||||
*/
|
||||
private function testConnectionEnhanced(): bool
|
||||
{
|
||||
// Detect cURL version for compatibility
|
||||
$curlVersion = curl_version();
|
||||
$isOldCurl = version_compare($curlVersion['version'], '8.0.0', '<');
|
||||
|
||||
$curlOptions = [
|
||||
CURLOPT_SSL_VERIFYPEER => config('dhl.ssl.verify_peer', true),
|
||||
CURLOPT_SSL_VERIFYHOST => config('dhl.ssl.verify_host', true) ? 2 : 0,
|
||||
CURLOPT_SSLVERSION => $this->getSslVersion(),
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 5,
|
||||
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
|
||||
CURLOPT_TIMEOUT => config('dhl.ssl.connect_timeout', 10),
|
||||
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
|
||||
CURLOPT_VERBOSE => true, // Enable verbose output
|
||||
CURLOPT_STDERR => fopen('php://temp', 'w+'), // Capture verbose output
|
||||
];
|
||||
|
||||
// Only use HTTP/2 for newer cURL versions
|
||||
if (!$isOldCurl && defined('CURL_HTTP_VERSION_2_0')) {
|
||||
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
|
||||
} else {
|
||||
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
|
||||
}
|
||||
|
||||
// Additional options for older cURL versions
|
||||
if ($isOldCurl) {
|
||||
$curlOptions[CURLOPT_TCP_NODELAY] = true;
|
||||
$curlOptions[CURLOPT_TCP_KEEPALIVE] = 1;
|
||||
$curlOptions[CURLOPT_TCP_KEEPIDLE] = 10;
|
||||
$curlOptions[CURLOPT_TCP_KEEPINTVL] = 1;
|
||||
}
|
||||
|
||||
// Log complete cURL request details
|
||||
$this->getDhlLogger()->info('DHL Enhanced Connection Test - Complete Request Details', [
|
||||
'method' => 'Enhanced SSL',
|
||||
'url' => $this->baseUrl . '/',
|
||||
'headers' => $this->buildHeaders(),
|
||||
'auth' => [
|
||||
'username' => $this->username,
|
||||
'password' => '***hidden***'
|
||||
],
|
||||
'curl_options' => $this->formatCurlOptions($curlOptions),
|
||||
'timeout' => 10,
|
||||
'ssl_config' => [
|
||||
'verify_peer' => config('dhl.ssl.verify_peer', true),
|
||||
'verify_host' => config('dhl.ssl.verify_host', true),
|
||||
'ssl_version' => config('dhl.ssl.ssl_version', 'TLSv1_2'),
|
||||
]
|
||||
]);
|
||||
|
||||
$response = Http::baseUrl($this->baseUrl)
|
||||
->withHeaders($this->buildHeaders())
|
||||
->withBasicAuth($this->username, $this->password)
|
||||
->timeout(10)
|
||||
->withOptions([
|
||||
'verify' => config('dhl.ssl.verify_peer', true),
|
||||
'http_errors' => false,
|
||||
'curl' => $curlOptions
|
||||
])
|
||||
->get('/');
|
||||
|
||||
// Log response details
|
||||
$this->getDhlLogger()->info('DHL Enhanced Connection Test - Response Details', [
|
||||
'status_code' => $response->status(),
|
||||
'headers' => $response->headers(),
|
||||
'body' => $response->body(),
|
||||
'success' => $this->validateResponse($response)
|
||||
]);
|
||||
|
||||
return $this->validateResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relaxed SSL connection test (fallback)
|
||||
*/
|
||||
private function testConnectionRelaxed(): bool
|
||||
{
|
||||
// Detect cURL version for compatibility
|
||||
$curlVersion = curl_version();
|
||||
$isOldCurl = version_compare($curlVersion['version'], '8.0.0', '<');
|
||||
|
||||
$curlOptions = [
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_SSLVERSION => CURL_SSLVERSION_DEFAULT,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 5,
|
||||
CURLOPT_CONNECTTIMEOUT => 15,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, // Force HTTP/1.1
|
||||
CURLOPT_VERBOSE => true, // Enable verbose output
|
||||
CURLOPT_STDERR => fopen('php://temp', 'w+'), // Capture verbose output
|
||||
];
|
||||
|
||||
// Additional options for older cURL versions
|
||||
if ($isOldCurl) {
|
||||
$curlOptions[CURLOPT_TCP_NODELAY] = true;
|
||||
$curlOptions[CURLOPT_TCP_KEEPALIVE] = 1;
|
||||
$curlOptions[CURLOPT_TCP_KEEPIDLE] = 10;
|
||||
$curlOptions[CURLOPT_TCP_KEEPINTVL] = 1;
|
||||
$curlOptions[CURLOPT_FRESH_CONNECT] = true;
|
||||
$curlOptions[CURLOPT_FORBID_REUSE] = true;
|
||||
}
|
||||
|
||||
// Log complete cURL request details
|
||||
$this->getDhlLogger()->info('DHL Relaxed Connection Test - Complete Request Details', [
|
||||
'method' => 'Relaxed SSL',
|
||||
'url' => $this->baseUrl . '/',
|
||||
'headers' => $this->buildHeaders(),
|
||||
'auth' => [
|
||||
'username' => $this->username,
|
||||
'password' => '***hidden***'
|
||||
],
|
||||
'curl_options' => $this->formatCurlOptions($curlOptions),
|
||||
'timeout' => 15,
|
||||
'ssl_config' => [
|
||||
'verify_peer' => false,
|
||||
'verify_host' => false,
|
||||
'ssl_version' => 'DEFAULT',
|
||||
]
|
||||
]);
|
||||
|
||||
$response = Http::baseUrl($this->baseUrl)
|
||||
->withHeaders($this->buildHeaders())
|
||||
->withBasicAuth($this->username, $this->password)
|
||||
->timeout(15)
|
||||
->withOptions([
|
||||
'verify' => false, // Disable SSL verification as fallback
|
||||
'http_errors' => false,
|
||||
'curl' => $curlOptions
|
||||
])
|
||||
->get('/');
|
||||
|
||||
// Log response details
|
||||
$this->getDhlLogger()->info('DHL Relaxed Connection Test - Response Details', [
|
||||
'status_code' => $response->status(),
|
||||
'headers' => $response->headers(),
|
||||
'body' => $response->body(),
|
||||
'success' => $this->validateResponse($response)
|
||||
]);
|
||||
|
||||
return $this->validateResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct cURL connection test (last resort)
|
||||
*/
|
||||
private function testConnectionCurl(): bool
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
$curlOptions = [
|
||||
CURLOPT_URL => $this->baseUrl . '/',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 15,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'User-Agent: acme-laravel-dhl/1.0'
|
||||
],
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
||||
CURLOPT_USERPWD => $this->username . ':' . $this->password,
|
||||
CURLOPT_SSL_VERIFYPEER => config('dhl.ssl.verify_peer', true),
|
||||
CURLOPT_SSL_VERIFYHOST => config('dhl.ssl.verify_host', true) ? 2 : 0,
|
||||
CURLOPT_SSLVERSION => $this->getSslVersion(),
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 5,
|
||||
CURLOPT_VERBOSE => true,
|
||||
CURLOPT_STDERR => fopen('php://temp', 'w+'),
|
||||
];
|
||||
|
||||
// Log complete cURL request details
|
||||
$this->getDhlLogger()->info('DHL Direct cURL Connection Test - Complete Request Details', [
|
||||
'method' => 'Direct cURL',
|
||||
'url' => $this->baseUrl . '/',
|
||||
'headers' => [
|
||||
'Accept: application/json',
|
||||
'User-Agent: acme-laravel-dhl/1.0'
|
||||
],
|
||||
'auth' => [
|
||||
'username' => $this->username,
|
||||
'password' => '***hidden***',
|
||||
'auth_type' => 'CURLAUTH_BASIC'
|
||||
],
|
||||
'curl_options' => $this->formatCurlOptions($curlOptions),
|
||||
'timeout' => 15,
|
||||
'ssl_config' => [
|
||||
'verify_peer' => config('dhl.ssl.verify_peer', true),
|
||||
'verify_host' => config('dhl.ssl.verify_host', true),
|
||||
'ssl_version' => config('dhl.ssl.ssl_version', 'TLSv1_2'),
|
||||
]
|
||||
]);
|
||||
|
||||
curl_setopt_array($ch, $curlOptions);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
$curlInfo = curl_getinfo($ch);
|
||||
|
||||
// Get verbose output
|
||||
rewind($curlOptions[CURLOPT_STDERR]);
|
||||
$verboseOutput = stream_get_contents($curlOptions[CURLOPT_STDERR]);
|
||||
fclose($curlOptions[CURLOPT_STDERR]);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Log response details
|
||||
$this->getDhlLogger()->info('DHL Direct cURL Connection Test - Response Details', [
|
||||
'status_code' => $httpCode,
|
||||
'response_body' => $response,
|
||||
'curl_info' => $curlInfo,
|
||||
'verbose_output' => $verboseOutput,
|
||||
'curl_error' => $error,
|
||||
'success' => $httpCode >= 200 && $httpCode < 500
|
||||
]);
|
||||
|
||||
if ($error) {
|
||||
throw new Exception("cURL error: {$error}");
|
||||
}
|
||||
|
||||
// For cURL, we check HTTP status codes
|
||||
return $httpCode >= 200 && $httpCode < 500; // Accept 2xx, 3xx, 4xx (but not 5xx)
|
||||
}
|
||||
|
||||
/**
|
||||
* Format cURL options for logging
|
||||
*/
|
||||
private function formatCurlOptions(array $curlOptions): array
|
||||
{
|
||||
$formatted = [];
|
||||
|
||||
foreach ($curlOptions as $option => $value) {
|
||||
$optionName = $this->getCurlOptionName($option);
|
||||
|
||||
// Hide sensitive values
|
||||
if (in_array($option, [CURLOPT_USERPWD, CURLOPT_STDERR])) {
|
||||
$formatted[$optionName] = '***hidden***';
|
||||
} elseif (is_resource($value)) {
|
||||
$formatted[$optionName] = 'resource';
|
||||
} elseif (is_array($value)) {
|
||||
$formatted[$optionName] = $value;
|
||||
} else {
|
||||
$formatted[$optionName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable cURL option name
|
||||
*/
|
||||
private function getCurlOptionName(int $option): string
|
||||
{
|
||||
$optionNames = [
|
||||
CURLOPT_URL => 'CURLOPT_URL',
|
||||
CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER',
|
||||
CURLOPT_TIMEOUT => 'CURLOPT_TIMEOUT',
|
||||
CURLOPT_CONNECTTIMEOUT => 'CURLOPT_CONNECTTIMEOUT',
|
||||
CURLOPT_HTTPHEADER => 'CURLOPT_HTTPHEADER',
|
||||
CURLOPT_HTTPAUTH => 'CURLOPT_HTTPAUTH',
|
||||
CURLOPT_USERPWD => 'CURLOPT_USERPWD',
|
||||
CURLOPT_SSL_VERIFYPEER => 'CURLOPT_SSL_VERIFYPEER',
|
||||
CURLOPT_SSL_VERIFYHOST => 'CURLOPT_SSL_VERIFYHOST',
|
||||
CURLOPT_SSLVERSION => 'CURLOPT_SSLVERSION',
|
||||
CURLOPT_FOLLOWLOCATION => 'CURLOPT_FOLLOWLOCATION',
|
||||
CURLOPT_MAXREDIRS => 'CURLOPT_MAXREDIRS',
|
||||
CURLOPT_VERBOSE => 'CURLOPT_VERBOSE',
|
||||
CURLOPT_STDERR => 'CURLOPT_STDERR',
|
||||
CURLOPT_USERAGENT => 'CURLOPT_USERAGENT',
|
||||
CURLOPT_HTTP_VERSION => 'CURLOPT_HTTP_VERSION',
|
||||
CURLOPT_TCP_NODELAY => 'CURLOPT_TCP_NODELAY',
|
||||
CURLOPT_TCP_KEEPALIVE => 'CURLOPT_TCP_KEEPALIVE',
|
||||
CURLOPT_TCP_KEEPIDLE => 'CURLOPT_TCP_KEEPIDLE',
|
||||
CURLOPT_TCP_KEEPINTVL => 'CURLOPT_TCP_KEEPINTVL',
|
||||
CURLOPT_FRESH_CONNECT => 'CURLOPT_FRESH_CONNECT',
|
||||
CURLOPT_FORBID_REUSE => 'CURLOPT_FORBID_REUSE',
|
||||
];
|
||||
|
||||
return $optionNames[$option] ?? "CURLOPT_UNKNOWN_{$option}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate HTTP response for connection test
|
||||
*/
|
||||
private function validateResponse($response): bool
|
||||
{
|
||||
if ($response->status() === 401) {
|
||||
$this->getDhlLogger()->error('DHL API authentication failed: Invalid username/password');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($response->status() === 403 && str_contains($response->body(), 'api-key')) {
|
||||
$this->getDhlLogger()->error('DHL API authentication failed: Invalid API key');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any other response code (including 404, 403 for endpoint access) means connection works
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue