Files
2026-09-15 10:35:56 +08:00

135 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\SeventeenTrack\Model;
use Magento\Framework\App\ResourceConnection;
use Psr\Log\LoggerInterface;
class ApiLogWriter
{
private const TABLE = 'esun_seventeen_track_api_log';
private const MAX_TEXT_LENGTH = 16777215;
private const SENSITIVE_KEYS = [
'17token', 'api_key', 'apikey', 'token', 'sign', 'signature',
'phone', 'telephone', 'phone_number', 'phone_number_last_4',
'email', 'address', 'address_line1', 'address_line2', 'street',
'postal_code', 'destination_postal_code', 'consignee', 'shipper',
'cpf_or_cnpj',
];
public function __construct(
private readonly ResourceConnection $resource,
private readonly Config $config,
private readonly LoggerInterface $logger
) {
}
/** Log without allowing a logging failure to interrupt synchronization or webhook acknowledgement. */
public function write(array $data): void
{
try {
foreach (['request_data', 'response_data'] as $field) {
if (array_key_exists($field, $data)) {
$data[$field] = $this->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;
}
}