214 lines
7.1 KiB
PHP
214 lines
7.1 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,
|
|
null,
|
|
$url,
|
|
$this->redact($payload)
|
|
);
|
|
}
|
|
|
|
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']['errorCode']
|
|
?? $data['error']['code']
|
|
?? $data['code']
|
|
?? $data['statusCode']
|
|
?? null;
|
|
if ($errCode !== null && (string) $errCode !== '0' && strtoupper((string) $errCode) !== 'SUCCESS') {
|
|
return true;
|
|
}
|
|
// validationMessages without a success payload
|
|
if (!empty($data['error']['validationMessages'])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected function extractDigitCode($data, int $httpCode)
|
|
{
|
|
if (!is_array($data)) {
|
|
return $httpCode ?: null;
|
|
}
|
|
return $data['error']['errorCode']
|
|
?? $data['error']['code']
|
|
?? $data['code']
|
|
?? $data['errorCode']
|
|
?? $data['responseCode']
|
|
?? $data['statusCode']
|
|
?? ($httpCode >= 400 ? (string) $httpCode : null);
|
|
}
|
|
|
|
protected function extractMessage($data, int $httpCode): string
|
|
{
|
|
if (!is_array($data)) {
|
|
return 'Digit API request failed (HTTP ' . $httpCode . ').';
|
|
}
|
|
|
|
// Digit often returns validationMessages[] instead of a single message
|
|
$validation = $data['error']['validationMessages'] ?? $data['validationMessages'] ?? null;
|
|
if (is_array($validation) && $validation) {
|
|
$parts = [];
|
|
foreach ($validation as $item) {
|
|
if (is_string($item) && trim($item) !== '') {
|
|
$parts[] = trim($item);
|
|
} elseif (is_array($item)) {
|
|
$parts[] = trim((string) ($item['message'] ?? json_encode($item)));
|
|
}
|
|
}
|
|
if ($parts) {
|
|
return implode(' | ', $parts);
|
|
}
|
|
}
|
|
|
|
$msg = $data['error']['message']
|
|
?? $data['message']
|
|
?? $data['errorMessage']
|
|
?? $data['responseMessage']
|
|
?? $data['statusMessage']
|
|
?? null;
|
|
if (is_array($msg)) {
|
|
$msg = implode(' | ', array_map('strval', $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;
|
|
}
|
|
}
|