119 lines
4.6 KiB
PHP
119 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Integrations;
|
|
|
|
use App\Jobs\Business\ArchiveCourierQuoteSignature;
|
|
use App\Jobs\Business\ArchiveCourierAgreementSignature;
|
|
use App\Models\Business\CourierAgreementSignature;
|
|
use App\Models\Business\CourierQuoteSignature;
|
|
use App\Support\CourierAgreementSignatureService;
|
|
use App\Support\CourierQuoteSignatureService;
|
|
use App\Services\Esign\EsignConfiguration;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Routing\Controller;
|
|
|
|
class EsignCallbacks extends Controller
|
|
{
|
|
public function __invoke(Request $request, CourierQuoteSignatureService $service, CourierAgreementSignatureService $agreementService): JsonResponse
|
|
{
|
|
$configuration = EsignConfiguration::fromConfig();
|
|
|
|
if (! $configuration->isConfigured()) {
|
|
return response()->json(['code' => '503', 'msg' => 'not configured'], 503);
|
|
}
|
|
|
|
$appId = $configuration->appId();
|
|
$appSecret = $configuration->appSecret();
|
|
|
|
$timestamp = trim((string) $request->header('X-Tsign-Open-TIMESTAMP'));
|
|
$algorithm = trim((string) $request->header('X-Tsign-Open-SIGNATURE-ALGORITHM'));
|
|
$callbackAppId = trim((string) $request->header('X-Tsign-Open-App-Id'));
|
|
$signature = strtolower(trim((string) $request->header('X-Tsign-Open-SIGNATURE')));
|
|
$rawBody = $request->getContent();
|
|
|
|
if (! ctype_digit($timestamp)
|
|
|| strcasecmp($algorithm, 'HmacSHA256') !== 0
|
|
|| ! hash_equals($appId, $callbackAppId)
|
|
|| $signature === ''
|
|
|| ! $this->timestampIsFresh($timestamp)) {
|
|
return response()->json(['code' => '401', 'msg' => 'invalid callback'], 401);
|
|
}
|
|
|
|
$queryValues = $request->query();
|
|
ksort($queryValues, SORT_STRING);
|
|
$data = $timestamp . collect($queryValues)->map(fn ($value): string => $this->queryValue($value))->implode('') . $rawBody;
|
|
$expected = strtolower(hash_hmac('sha256', $data, $appSecret));
|
|
|
|
if (! hash_equals($expected, $signature)) {
|
|
return response()->json(['code' => '401', 'msg' => 'invalid callback'], 401);
|
|
}
|
|
|
|
$payload = json_decode($rawBody, true);
|
|
|
|
if (! is_array($payload)) {
|
|
return response()->json(['code' => '400', 'msg' => 'invalid json'], 400);
|
|
}
|
|
|
|
if (($payload['action'] ?? null) !== 'SIGN_FLOW_COMPLETE') {
|
|
return $this->success();
|
|
}
|
|
|
|
$signFlowId = trim((string) ($payload['signFlowId'] ?? ''));
|
|
$remoteStatus = trim((string) ($payload['signFlowStatus'] ?? ''));
|
|
|
|
if ($signFlowId === '' || $remoteStatus === '') {
|
|
return $this->success();
|
|
}
|
|
|
|
$payloadHash = hash('sha256', $rawBody);
|
|
$signatureRecord = $service->acceptCallback($signFlowId, $remoteStatus, $payloadHash);
|
|
|
|
if ($signatureRecord === null) {
|
|
$agreementRecord = $agreementService->acceptCallback($signFlowId, $remoteStatus, $payloadHash, [
|
|
'event_key' => hash('sha256', $timestamp . '|' . ($payload['action'] ?? '') . '|' . $signFlowId . '|' . $payloadHash),
|
|
'action' => (string) ($payload['action'] ?? 'SIGN_FLOW_COMPLETE'),
|
|
'timestamp' => $timestamp,
|
|
'raw_body' => $rawBody,
|
|
]);
|
|
|
|
if ($agreementRecord
|
|
&& $remoteStatus === '2'
|
|
&& $agreementService->autoArchive($agreementRecord)
|
|
&& $agreementRecord->status !== CourierAgreementSignature::STATUS_ARCHIVED) {
|
|
ArchiveCourierAgreementSignature::dispatch($agreementRecord->id);
|
|
}
|
|
}
|
|
|
|
if ($signatureRecord
|
|
&& $remoteStatus === '2'
|
|
&& $signatureRecord->status !== CourierQuoteSignature::STATUS_ARCHIVED) {
|
|
ArchiveCourierQuoteSignature::dispatchAfterResponse($signatureRecord->id);
|
|
}
|
|
|
|
return $this->success();
|
|
}
|
|
|
|
private function timestampIsFresh(string $timestamp): bool
|
|
{
|
|
$seconds = strlen($timestamp) >= 13 ? intdiv((int) $timestamp, 1000) : (int) $timestamp;
|
|
$tolerance = EsignConfiguration::fromConfig()->callbackToleranceSeconds();
|
|
|
|
return abs(now()->timestamp - $seconds) <= $tolerance;
|
|
}
|
|
|
|
private function queryValue(mixed $value): string
|
|
{
|
|
if (is_array($value)) {
|
|
return collect($value)->map(fn ($item): string => $this->queryValue($item))->implode('');
|
|
}
|
|
|
|
return is_scalar($value) ? (string) $value : '';
|
|
}
|
|
|
|
private function success(): JsonResponse
|
|
{
|
|
return response()->json(['code' => '200', 'msg' => 'success']);
|
|
}
|
|
}
|