nhance/app/Libraries/DigitMotor/DigitApiClient.php
2026-07-21 09:32:13 +05:30

178 lines
5.9 KiB
PHP

<?php
namespace App\Libraries\DigitMotor;
use App\Models\MotorApiLogModel;
use Config\DigitMotor as DigitMotorConfig;
/**
* Thin HTTP client for Digit OneAPI executor calls.
*/
class DigitApiClient
{
protected DigitMotorConfig $config;
protected DigitAuthClient $authClient;
protected MotorApiLogModel $logModel;
public function __construct(?DigitMotorConfig $config = null, ?DigitAuthClient $authClient = null)
{
$this->config = $config ?? config('DigitMotor');
$this->authClient = $authClient ?? new DigitAuthClient($this->config);
$this->logModel = new MotorApiLogModel();
helper('api');
}
/**
* POST to Digit executor (or arbitrary path under baseUrl).
*
* @throws DigitApiException
*/
public function post(string $path, array $payload, ?string $integrationId = null, ?int $quoteId = null): array
{
return $this->request('POST', $path, $payload, $integrationId, $quoteId, false);
}
/**
* @throws DigitApiException
*/
protected function request(
string $method,
string $path,
array $payload,
?string $integrationId,
?int $quoteId,
bool $isRetry
): array {
$token = $this->authClient->getToken($isRetry);
$url = $this->config->baseUrl . $path;
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
];
if ($integrationId) {
$headers[] = 'integrationId: ' . $integrationId;
}
$started = microtime(true);
$response = call_third_party_api($url, $method, $headers, $payload);
$durationMs = (int) round((microtime(true) - $started) * 1000);
$httpCode = (int) ($response['code'] ?? 0);
$data = $response['data'] ?? [];
if (is_string($data)) {
$decoded = json_decode($data, true);
$data = is_array($decoded) ? $decoded : ['raw' => $data];
}
$digitCode = $this->extractDigitCode($data, $httpCode);
$this->writeLog($quoteId, $integrationId, $path, $payload, $data, $httpCode, $digitCode, $durationMs);
// Stale token — refresh once and retry
if (!$isRetry && ($httpCode === 401 || (string) $digitCode === '401')) {
$this->authClient->refreshToken();
return $this->request($method, $path, $payload, $integrationId, $quoteId, true);
}
if ($this->isHardFail($httpCode, $digitCode, $response['status'] ?? false, $data)) {
$message = $this->extractMessage($data, $httpCode);
throw new DigitApiException($message, $digitCode, $httpCode, $data);
}
return is_array($data) ? $data : [];
}
protected function isHardFail(int $httpCode, $digitCode, bool $httpOk, $data): bool
{
$code = (string) $digitCode;
if (in_array($code, ['403', '999', '400', '500'], true)) {
return true;
}
if ($httpCode >= 400) {
return true;
}
if (!$httpOk) {
return true;
}
// Digit sometimes returns 200 with error object
if (is_array($data) && isset($data['error']) && !isset($data['grossPremium']) && !isset($data['premium']) && !isset($data['quoteNumber'])) {
$errCode = $data['error']['code'] ?? $data['code'] ?? null;
if ($errCode !== null && (string) $errCode !== '0' && strtoupper((string) $errCode) !== 'SUCCESS') {
return true;
}
}
return false;
}
protected function extractDigitCode($data, int $httpCode)
{
if (!is_array($data)) {
return $httpCode ?: null;
}
return $data['error']['code']
?? $data['code']
?? $data['errorCode']
?? $data['responseCode']
?? ($httpCode >= 400 ? (string) $httpCode : null);
}
protected function extractMessage($data, int $httpCode): string
{
if (!is_array($data)) {
return 'Digit API request failed (HTTP ' . $httpCode . ').';
}
$msg = $data['error']['message']
?? $data['message']
?? $data['errorMessage']
?? $data['responseMessage']
?? null;
if (is_array($msg)) {
$msg = json_encode($msg);
}
return $msg ?: 'Digit API request failed (HTTP ' . $httpCode . ').';
}
protected function writeLog(
?int $quoteId,
?string $integrationId,
string $endpoint,
array $requestBody,
$responseBody,
int $httpStatus,
$errorCode,
int $durationMs
): void {
try {
$this->logModel->insert([
'quote_id' => $quoteId,
'integration_id' => $integrationId,
'endpoint' => $endpoint,
'request_body' => json_encode($this->redact($requestBody)),
'response_body' => json_encode($this->redact(is_array($responseBody) ? $responseBody : ['raw' => $responseBody])),
'http_status' => $httpStatus,
'error_code' => $errorCode !== null ? (string) $errorCode : null,
'duration_ms' => $durationMs,
]);
} catch (\Throwable $e) {
log_message('error', 'DigitMotor API log write failed: ' . $e->getMessage());
}
}
protected function redact(array $data): array
{
$sensitive = ['password', 'access_token', 'refresh_token', 'Authorization', 'authorization'];
$out = [];
foreach ($data as $key => $value) {
if (in_array((string) $key, $sensitive, true)) {
$out[$key] = '***REDACTED***';
} elseif (is_array($value)) {
$out[$key] = $this->redact($value);
} else {
$out[$key] = $value;
}
}
return $out;
}
}