Files
boursecu_server/app/Services/Payment/Gateways/MontifyPartnerGateway.php
T

225 lines
7.5 KiB
PHP

<?php
namespace App\Services\Payment\Gateways;
use App\Services\ConfigService;
use App\Services\Payment\Contracts\WalletPaymentGatewayInterface;
use App\Services\Payment\Support\MontifyHmacSigner;
use App\Services\Payment\WalletDepositCreditor;
use App\Services\Payment\WalletPaymentManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* درگاه پارتنر Montify (HMAC) — فقط credentials روی سرور.
* @see docs/EXTERNAL_PAYMENT_PARTNER.md
*/
class MontifyPartnerGateway implements WalletPaymentGatewayInterface
{
public function __construct(
private ConfigService $config,
private WalletDepositCreditor $creditor,
) {
}
public function key(): string
{
return WalletPaymentManager::PROVIDER_MONTIFY;
}
public function isAvailable(): bool
{
return $this->isConfigured();
}
public function isConfigured(): bool
{
return $this->baseUrl() !== ''
&& $this->clientId() !== ''
&& $this->clientSecret() !== ''
&& $this->gatewayName() !== '';
}
public function returnUrl(): string
{
$fromSettings = $this->config->getString('external_payment_return_url', '');
if ($fromSettings !== '') {
return $fromSettings;
}
return url('/customer/wallet/payment/return/' . $this->key());
}
public function webhookUrl(): string
{
$fromSettings = $this->config->getString('external_payment_webhook_url', '');
if ($fromSettings !== '') {
return $fromSettings;
}
return url('/customer/wallet/payment/webhook/' . $this->key());
}
public function initiate(array $payload): array
{
if (!$this->isConfigured()) {
throw new RuntimeException('تنظیمات درگاه Montify ناقص است');
}
$path = '/external-payment/initiate';
$body = [
'amount' => (int) $payload['amount'],
'reference_id' => (string) $payload['reference_id'],
'gateway' => (string) ($payload['gateway'] ?? $this->gatewayName()),
'return_url' => (string) ($payload['return_url'] ?? $this->returnUrl()),
'webhook_url' => (string) ($payload['webhook_url'] ?? $this->webhookUrl()),
];
if (!empty($payload['description'])) {
$body['description'] = (string) $payload['description'];
}
if (!empty($payload['metadata']) && is_array($payload['metadata'])) {
$body['metadata'] = $payload['metadata'];
}
$signer = $this->signer();
$headers = $signer->signRequest('POST', $path, $body);
$response = Http::withHeaders($headers)
->withBody($signer->stableStringify($body), 'application/json')
->acceptJson()
->post($this->baseUrl() . $path);
$data = $response->json();
if (!$response->successful() || empty($data['payment_url'])) {
$message = is_array($data)
? (string) ($data['message'] ?? $data['error'] ?? 'initiate failed')
: 'initiate failed';
throw new RuntimeException($message);
}
return [
'payment_url' => (string) $data['payment_url'],
'session_id' => $data['session_id'] ?? null,
'reference_id' => $data['reference_id'] ?? $payload['reference_id'],
'expires_at' => $data['expires_at'] ?? null,
];
}
public function handleReturn(Request $request): array
{
$query = $request->query();
$signer = $this->signer();
if (!$signer->verifyReturnSignature($query)) {
return ['ok' => false, 'withdraw' => null, 'http_status' => 401, 'message' => 'امضای نامعتبر'];
}
if (!$signer->isTimestampFresh($query['timestamp'] ?? null)) {
return ['ok' => false, 'withdraw' => null, 'http_status' => 401, 'message' => 'زمان درخواست منقضی شده'];
}
$referenceId = (string) ($query['reference_id'] ?? '');
$status = (string) ($query['status'] ?? '');
$amount = (int) ($query['amount'] ?? 0);
$trackNumber = (string) ($query['track_number'] ?? '');
$sessionId = (string) ($query['session_id'] ?? '');
$withdraw = $this->creditor->findByReference($referenceId);
if (!$withdraw) {
return ['ok' => false, 'withdraw' => null, 'http_status' => 404, 'message' => 'سفارش یافت نشد'];
}
if ($amount > 0 && (int) $withdraw->cash !== $amount) {
return ['ok' => false, 'withdraw' => $withdraw, 'http_status' => 422, 'message' => 'مبلغ پرداختی دستکاری شده !'];
}
if ($status === 'success') {
$this->creditor->markPaidAndCredit(
$withdraw,
$trackNumber !== '' ? $trackNumber : $sessionId,
'واریز درگاه Montify'
);
} elseif ((int) $withdraw->sts === 1) {
$withdraw->sts = 3;
$withdraw->save();
}
return [
'ok' => true,
'withdraw' => $withdraw->fresh(['customer']),
];
}
public function handleWebhook(Request $request): array
{
$body = $request->all();
$signer = $this->signer();
if (!$signer->verifyWebhookSignature($body, $request->header('X-Signature'))) {
return ['ok' => false, 'http_status' => 401, 'message' => 'invalid_signature'];
}
$clientId = (string) $request->header('X-Client-Id', '');
if ($clientId !== '' && $clientId !== $this->clientId()) {
return ['ok' => false, 'http_status' => 401, 'message' => 'invalid_client'];
}
if (!$signer->isTimestampFresh($body['timestamp'] ?? $request->header('X-Timestamp'))) {
return ['ok' => false, 'http_status' => 401, 'message' => 'expired_timestamp'];
}
$status = (string) ($body['status'] ?? '');
if ($status !== 'success') {
return ['ok' => true, 'ignored' => true];
}
$referenceId = (string) ($body['reference_id'] ?? '');
$amount = (int) ($body['amount'] ?? 0);
$trackNumber = (string) ($body['track_number'] ?? '');
$sessionId = (string) ($body['session_id'] ?? '');
$withdraw = $this->creditor->findByReference($referenceId);
if (!$withdraw) {
return ['ok' => false, 'http_status' => 404, 'message' => 'not_found'];
}
if ($amount > 0 && (int) $withdraw->cash !== $amount) {
return ['ok' => false, 'http_status' => 422, 'message' => 'amount_mismatch'];
}
$this->creditor->markPaidAndCredit(
$withdraw,
$trackNumber !== '' ? $trackNumber : $sessionId,
'واریز درگاه Montify'
);
return ['ok' => true];
}
private function baseUrl(): string
{
return rtrim($this->config->getString('external_payment_base_url', ''), '/');
}
private function clientId(): string
{
return $this->config->getString('external_payment_client_id', '');
}
private function clientSecret(): string
{
return $this->config->getString('external_payment_client_secret', '');
}
private function gatewayName(): string
{
return strtolower($this->config->getString('external_payment_gateway', 'zarinpall'));
}
private function signer(): MontifyHmacSigner
{
return new MontifyHmacSigner($this->clientId(), $this->clientSecret());
}
}