Feat add payment with gateway of montify
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user