Files
boursecu_server/app/Services/Payment/Support/MontifyHmacSigner.php
T

118 lines
3.3 KiB
PHP

<?php
namespace App\Services\Payment\Support;
use Illuminate\Support\Str;
/**
* ابزار امضای HMAC مطابق EXTERNAL_PAYMENT_PARTNER.md (Montify).
* فقط سمت سرور استفاده شود.
*/
class MontifyHmacSigner
{
public function __construct(private string $clientId, private string $clientSecret)
{
}
/**
* @param array<string, mixed>|null $body
* @return array<string, string>
*/
public function signRequest(string $method, string $path, ?array $body): array
{
$timestamp = (string) time();
$nonce = (string) Str::uuid();
$bodyString = $body !== null ? $this->stableStringify($body) : '';
$bodyHash = hash('sha256', $bodyString);
$canonical = implode("\n", [
strtoupper($method),
$path,
$bodyHash,
$timestamp,
$nonce,
]);
$signature = hash_hmac('sha256', $canonical, $this->clientSecret);
return [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Client-Id' => $this->clientId,
'X-Timestamp' => $timestamp,
'X-Nonce' => $nonce,
'X-Signature' => $signature,
];
}
/**
* @param array<string, mixed> $query
*/
public function verifyReturnSignature(array $query): bool
{
$signature = (string) ($query['signature'] ?? '');
if ($signature === '') {
return false;
}
unset($query['signature']);
$expected = hash_hmac('sha256', $this->stableStringify($query), $this->clientSecret);
return $this->hashEqualsHex($signature, $expected);
}
/**
* @param array<string, mixed> $body
*/
public function verifyWebhookSignature(array $body, ?string $signatureHeader): bool
{
if ($signatureHeader === null || $signatureHeader === '') {
return false;
}
$expected = hash_hmac('sha256', $this->stableStringify($body), $this->clientSecret);
return $this->hashEqualsHex($signatureHeader, $expected);
}
public function isTimestampFresh(int|string|null $timestamp, int $skewSeconds = 300): bool
{
if ($timestamp === null || $timestamp === '') {
return false;
}
return abs(time() - (int) $timestamp) <= $skewSeconds;
}
/**
* @param mixed $value
*/
public function stableStringify($value): string
{
if ($value === null || !is_array($value)) {
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
if (array_is_list($value)) {
$parts = array_map(fn ($item) => $this->stableStringify($item), $value);
return '[' . implode(',', $parts) . ']';
}
ksort($value);
$parts = [];
foreach ($value as $key => $item) {
$parts[] = '"' . $key . '":' . $this->stableStringify($item);
}
return '{' . implode(',', $parts) . '}';
}
private function hashEqualsHex(string $a, string $b): bool
{
if (!ctype_xdigit($a) || !ctype_xdigit($b) || strlen($a) !== strlen($b)) {
return false;
}
return hash_equals(strtolower($a), strtolower($b));
}
}