\n"); exit(1); } $basePath = dirname(__DIR__); registerAutoloader($basePath); $env = parse_ini_file($basePath . '/.env'); if (!is_array($env)) { fwrite(STDERR, "Cannot read .env\n"); exit(1); } [$pdo, $hostUsed] = openDatabase($env, !empty($options['use_remote'])); $order = fetchOrder($pdo, (int) $options['order_id']); if ($order === null) { fwrite(STDERR, "Order not found: " . (int) $options['order_id'] . "\n"); exit(1); } $addresses = fetchAddresses($pdo, (int) $order['id']); $sender = fetchSenderAddress($pdo); $mapping = fetchDeliveryMapping($pdo, $order); [$appId, $appSecret] = fetchApaczkaCredentials($pdo, $env); $client = new App\Modules\Settings\ApaczkaApiClient(); $services = $client->getServiceStructure($appId, $appSecret); $serviceIndex = buildServiceIndex($services); $basePayload = buildBasePayload($order, $addresses, $sender, $mapping); $candidates = buildCandidates($basePayload, $mapping, $serviceIndex, $options); echo "DB host: {$hostUsed}\n"; echo "Order: #" . (int) $order['id'] . " source=" . (string) ($order['source'] ?? '') . " source_order_id=" . (string) ($order['source_order_id'] ?? '') . "\n"; echo "Mapped service: " . (string) ($mapping['provider_service_id'] ?? '(none)') . " (" . (string) ($mapping['provider_service_name'] ?? '') . ")\n"; echo "Receiver point: " . (string) ($basePayload['receiver_point_id'] ?? '(none)') . "\n"; echo "Candidates: " . count($candidates) . "\n\n"; $attempt = 0; $success = false; foreach ($candidates as $candidate) { $attempt++; $label = buildCandidateLabel($candidate, $serviceIndex); echo "[{$attempt}] {$label}\n"; $apiPayload = buildApiPayloadFromCandidate($basePayload, $candidate); try { $response = $client->sendOrder($appId, $appSecret, $apiPayload); $orderResponse = is_array($response['response']['order'] ?? null) ? $response['response']['order'] : []; $apaczkaOrderId = (string) ($orderResponse['id'] ?? ''); $waybill = (string) ($orderResponse['waybill_number'] ?? ''); echo " OK: order_id=" . ($apaczkaOrderId !== '' ? $apaczkaOrderId : '-') . " waybill=" . ($waybill !== '' ? $waybill : '-') . "\n"; $success = true; if (empty($options['continue_on_success'])) { break; } } catch (Throwable $exception) { echo " ERR: " . trim($exception->getMessage()) . "\n"; } } echo "\nResult: " . ($success ? "SUCCESS" : "NO SUCCESS") . "\n"; exit($success ? 0 : 2); /** * @return array */ function parseOptions(array $argv): array { $options = [ 'order_id' => DEFAULT_ORDER_ID, 'service_id' => '', 'use_remote' => false, 'continue_on_success' => false, ]; foreach (array_slice($argv, 1) as $arg) { if (preg_match('/^--order-id=(\d+)$/', (string) $arg, $m) === 1) { $options['order_id'] = (int) $m[1]; continue; } if (preg_match('/^--service-id=([A-Za-z0-9_-]+)$/', (string) $arg, $m) === 1) { $options['service_id'] = (string) $m[1]; continue; } if ((string) $arg === '--use-remote') { $options['use_remote'] = true; continue; } if ((string) $arg === '--continue-on-success') { $options['continue_on_success'] = true; continue; } } return $options; } function registerAutoloader(string $basePath): void { spl_autoload_register(static function (string $class) use ($basePath): void { $prefix = 'App\\'; if (!str_starts_with($class, $prefix)) { return; } $relative = substr($class, strlen($prefix)); $file = $basePath . '/src/' . str_replace('\\', '/', $relative) . '.php'; if (is_file($file)) { require $file; } }); } /** * @return array{0: PDO, 1: string} */ function openDatabase(array $env, bool $useRemote): array { $host = $useRemote ? (string) ($env['DB_HOST_REMOTE'] ?? ($env['DB_HOST'] ?? '127.0.0.1')) : (string) ($env['DB_HOST'] ?? '127.0.0.1'); $port = (string) ($env['DB_PORT'] ?? '3306'); $db = (string) ($env['DB_DATABASE'] ?? ''); $user = (string) ($env['DB_USERNAME'] ?? ''); $pass = (string) ($env['DB_PASSWORD'] ?? ''); $dsn = 'mysql:host=' . $host . ';port=' . $port . ';dbname=' . $db . ';charset=utf8mb4'; $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); return [$pdo, $host]; } /** * @return array|null */ function fetchOrder(PDO $pdo, int $orderId): ?array { $stmt = $pdo->prepare('SELECT * FROM orders WHERE id = :id LIMIT 1'); $stmt->execute(['id' => $orderId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); return is_array($row) ? $row : null; } /** * @return array> */ function fetchAddresses(PDO $pdo, int $orderId): array { $stmt = $pdo->prepare('SELECT * FROM order_addresses WHERE order_id = :order_id ORDER BY id ASC'); $stmt->execute(['order_id' => $orderId]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); return is_array($rows) ? $rows : []; } /** * @return array */ function fetchSenderAddress(PDO $pdo): array { $stmt = $pdo->prepare('SELECT * FROM company_settings WHERE id = 1 LIMIT 1'); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); $data = is_array($row) ? $row : []; return [ 'name' => trim((string) (($data['person_name'] ?? '') !== '' ? $data['person_name'] : ($data['company_name'] ?? ''))), 'line1' => trim((string) ($data['street'] ?? '')), 'postal_code' => trim((string) ($data['postal_code'] ?? '')), 'city' => trim((string) ($data['city'] ?? '')), 'country_code' => strtoupper(trim((string) ($data['country_code'] ?? 'PL'))), 'phone' => trim((string) ($data['phone'] ?? '')), 'email' => trim((string) ($data['email'] ?? '')), ]; } /** * @param array $order * @return array|null */ function fetchDeliveryMapping(PDO $pdo, array $order): ?array { $source = strtolower(trim((string) ($order['source'] ?? ''))); $orderMethod = trim((string) ($order['external_carrier_id'] ?? '')); if ($source === '' || $orderMethod === '') { return null; } $sourceIntegrationId = $source === 'shoppro' ? max(0, (int) ($order['integration_id'] ?? 0)) : 0; $stmt = $pdo->prepare( 'SELECT * FROM carrier_delivery_method_mappings WHERE source_system = :source_system AND source_integration_id = :source_integration_id AND order_delivery_method = :order_delivery_method LIMIT 1' ); $stmt->execute([ 'source_system' => $source, 'source_integration_id' => $sourceIntegrationId, 'order_delivery_method' => $orderMethod, ]); $row = $stmt->fetch(PDO::FETCH_ASSOC); return is_array($row) ? $row : null; } /** * @return array{0: string, 1: string} */ function fetchApaczkaCredentials(PDO $pdo, array $env): array { $stmt = $pdo->prepare( 'SELECT a.app_id, COALESCE(NULLIF(i.api_key_encrypted, \'\'), a.app_secret_encrypted, a.api_key_encrypted) AS secret_encrypted FROM apaczka_integration_settings a LEFT JOIN integrations i ON i.id = a.integration_id WHERE a.id = 1 LIMIT 1' ); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!is_array($row)) { throw new RuntimeException('Missing apaczka_integration_settings row.'); } $appId = trim((string) ($row['app_id'] ?? '')); $encrypted = trim((string) ($row['secret_encrypted'] ?? '')); if ($appId === '' || $encrypted === '') { throw new RuntimeException('Missing Apaczka app_id or app_secret.'); } $cipher = new App\Modules\Settings\IntegrationSecretCipher((string) ($env['INTEGRATIONS_SECRET'] ?? '')); $appSecret = trim($cipher->decrypt($encrypted)); if ($appSecret === '') { throw new RuntimeException('Cannot decrypt Apaczka app_secret.'); } return [$appId, $appSecret]; } /** * @param array> $services * @return array> */ function buildServiceIndex(array $services): array { $result = []; foreach ($services as $service) { if (!is_array($service)) { continue; } $serviceId = trim((string) ($service['service_id'] ?? $service['id'] ?? '')); if ($serviceId === '') { continue; } $result[$serviceId] = $service; } return $result; } /** * @param array $order * @param array> $addresses * @param array $sender * @param array|null $mapping * @return array */ function buildBasePayload(array $order, array $addresses, array $sender, ?array $mapping): array { $delivery = null; $customer = null; foreach ($addresses as $address) { if (!is_array($address)) { continue; } $type = trim((string) ($address['address_type'] ?? '')); if ($type === 'delivery' && $delivery === null) { $delivery = $address; } if ($type === 'customer' && $customer === null) { $customer = $address; } } $receiver = is_array($delivery) ? $delivery : (is_array($customer) ? $customer : []); $receiverPointId = trim((string) ($receiver['parcel_external_id'] ?? '')); $receiverName = trim((string) ($receiver['name'] ?? '')); $customerName = trim((string) (($customer['name'] ?? ''))); if ($receiverPointId !== '' && $customerName !== '') { $receiverName = $customerName; } if ($receiverName === '') { $receiverName = 'Klient'; } $totalWithTax = (float) ($order['total_with_tax'] ?? 0); $serviceId = trim((string) ($mapping['provider_service_id'] ?? '')); if ($serviceId === '') { $serviceId = trim((string) ($order['external_carrier_account_id'] ?? '')); } return [ 'service_id' => $serviceId, 'source_order_id' => trim((string) ($order['source_order_id'] ?? $order['id'] ?? '')), 'insurance_cents' => $totalWithTax > 0 ? (int) round($totalWithTax * 100) : 0, 'receiver_point_id' => $receiverPointId, 'sender_point_id' => '', 'receiver' => [ 'name' => $receiverName, 'line1' => trim((string) ($receiver['street_name'] ?? '')), 'postal_code' => trim((string) ($receiver['zip_code'] ?? '')), 'city' => trim((string) ($receiver['city'] ?? '')), 'country_code' => strtoupper(trim((string) ($receiver['country'] ?? 'PL'))), 'phone' => trim((string) ($receiver['phone'] ?? '')), 'email' => trim((string) ($receiver['email'] ?? '')), ], 'sender' => $sender, 'shipment' => [ 'shipment_type_code' => 'PACZKA', 'dimension1' => 25, 'dimension2' => 20, 'dimension3' => 8, 'weight' => 1.0, 'is_nstd' => 0, ], ]; } /** * @param array $basePayload * @param array|null $mapping * @param array> $serviceIndex * @param array $options * @return array> */ function buildCandidates(array $basePayload, ?array $mapping, array $serviceIndex, array $options): array { $serviceIds = []; $forcedServiceId = trim((string) ($options['service_id'] ?? '')); if ($forcedServiceId !== '') { $serviceIds[] = $forcedServiceId; } else { $mapped = trim((string) ($basePayload['service_id'] ?? '')); if ($mapped !== '') { $serviceIds[] = $mapped; } foreach ($serviceIndex as $serviceId => $service) { $supplier = strtoupper(trim((string) ($service['supplier'] ?? ''))); $doorToPoint = (int) ($service['door_to_point'] ?? 0) === 1; $pointToPoint = (int) ($service['point_to_point'] ?? 0) === 1; if ($supplier !== 'INPOST') { continue; } if (!$doorToPoint && !$pointToPoint) { continue; } $serviceIds[] = $serviceId; } } $serviceIds = array_values(array_unique(array_filter($serviceIds, static fn(string $v): bool => $v !== ''))); if ($serviceIds === []) { return []; } $pointModes = ['all_keys', 'point_only', 'foreign_only', 'point_id_only']; $result = []; foreach ($serviceIds as $serviceId) { foreach ($pointModes as $mode) { $result[] = [ 'service_id' => $serviceId, 'point_mode' => $mode, ]; } } return $result; } /** * @param array $candidate * @param array> $serviceIndex */ function buildCandidateLabel(array $candidate, array $serviceIndex): string { $serviceId = (string) ($candidate['service_id'] ?? ''); $pointMode = (string) ($candidate['point_mode'] ?? ''); $service = $serviceIndex[$serviceId] ?? null; $serviceName = is_array($service) ? trim((string) ($service['name'] ?? '')) : ''; return 'service=' . $serviceId . ($serviceName !== '' ? ' (' . $serviceName . ')' : '') . ' point_mode=' . $pointMode; } /** * @param array $basePayload * @param array $candidate * @return array */ function buildApiPayloadFromCandidate(array $basePayload, array $candidate): array { $serviceId = (string) ($candidate['service_id'] ?? ''); $pointMode = (string) ($candidate['point_mode'] ?? 'all_keys'); $receiverPointId = trim((string) ($basePayload['receiver_point_id'] ?? '')); $senderPointId = trim((string) ($basePayload['sender_point_id'] ?? '')); $receiver = (array) ($basePayload['receiver'] ?? []); $sender = (array) ($basePayload['sender'] ?? []); unset($receiver['point'], $receiver['foreign_address_id'], $receiver['point_id']); unset($sender['point'], $sender['foreign_address_id'], $sender['point_id']); applyPointMode($receiver, $receiverPointId, $pointMode); if ($senderPointId !== '') { applyPointMode($sender, $senderPointId, $pointMode); } $payload = [ 'service_id' => ctype_digit($serviceId) ? (int) $serviceId : $serviceId, 'address' => [ 'receiver' => $receiver, 'sender' => $sender, ], 'shipment' => [(array) ($basePayload['shipment'] ?? [])], 'content' => 'orderPRO ' . (string) ($basePayload['source_order_id'] ?? ''), 'comment' => 'orderPRO ' . (string) ($basePayload['source_order_id'] ?? ''), ]; $insuranceCents = (int) ($basePayload['insurance_cents'] ?? 0); if ($insuranceCents > 0) { $payload['shipment_value'] = $insuranceCents; } return $payload; } /** * @param array $node */ function applyPointMode(array &$node, string $pointId, string $mode): void { if ($pointId === '') { return; } if ($mode === 'point_only') { $node['point'] = $pointId; return; } if ($mode === 'foreign_only') { $node['foreign_address_id'] = $pointId; return; } if ($mode === 'point_id_only') { $node['point_id'] = $pointId; return; } $node['point'] = $pointId; $node['foreign_address_id'] = $pointId; $node['point_id'] = $pointId; }