encode($data[$field]); } } if (isset($data['message'])) { $data['message'] = mb_substr((string)$data['message'], 0, 65535); } $data['created_at'] = gmdate('Y-m-d H:i:s'); $this->resource->getConnection('sales')->insert( $this->resource->getTableName(self::TABLE), $data ); } catch (\Throwable $exception) { $this->logger->error('Unable to write a 17TRACK API audit log.', [ 'operation' => (string)($data['operation'] ?? ''), 'tracking_number' => (string)($data['tracking_number'] ?? ''), 'exception' => $exception, ]); } } /** @return array{order_id:?int,order_number:?string,tracking_number:?string,event:?string} */ public function getWebhookContext(string $rawBody): array { $context = ['order_id' => null, 'order_number' => null, 'tracking_number' => null, 'event' => null]; try { $payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR); if (!is_array($payload)) { return $context; } $context['event'] = isset($payload['event']) ? mb_substr((string)$payload['event'], 0, 50) : null; $number = trim((string)($payload['data']['number'] ?? '')); $context['tracking_number'] = $number !== '' ? mb_substr($number, 0, 50) : null; if ($number === '') { return $context; } $connection = $this->resource->getConnection('sales'); $trackingTable = $this->resource->getTableName('esun_seventeen_track'); $orderTable = $this->resource->getTableName('sales_order'); $row = $connection->fetchRow( $connection->select() ->from(['tracking' => $trackingTable], ['order_id']) ->joinLeft(['orders' => $orderTable], 'orders.entity_id = tracking.order_id', ['order_number' => 'increment_id']) ->where('tracking.tracking_number = ?', $number) ->limit(1) ); if (is_array($row)) { $context['order_id'] = isset($row['order_id']) ? (int)$row['order_id'] : null; $context['order_number'] = isset($row['order_number']) ? (string)$row['order_number'] : null; } } catch (\Throwable) { // Invalid JSON and unavailable order context are captured by the processing result itself. } return $context; } public function getWebhookPath(): string { return '/seventeentrack/webhook/index'; } public function getRegistrationPath(): string { return rtrim($this->config->getApiUrl(), '/') . '/register'; } private function encode(mixed $value): ?string { if ($value === null || $value === '') { return null; } if (is_string($value)) { try { $decoded = json_decode($value, true, 512, JSON_THROW_ON_ERROR); $value = is_array($decoded) ? $decoded : $value; } catch (\JsonException) { return mb_substr($value, 0, self::MAX_TEXT_LENGTH); } } $encoded = json_encode( is_array($value) ? $this->sanitize($value) : $value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR ); return mb_substr($encoded, 0, self::MAX_TEXT_LENGTH); } private function sanitize(array $data): array { foreach ($data as $key => $value) { $normalized = strtolower((string)$key); if (in_array($normalized, self::SENSITIVE_KEYS, true) || str_contains($normalized, 'password') || str_contains($normalized, 'secret') ) { $data[$key] = '[REDACTED]'; } elseif (is_array($value)) { $data[$key] = $this->sanitize($value); } } return $data; } }