- Add ZarinpalService for production payment gateway - Add MockZarinpalService for testing without real merchant ID - Add MockGatewayController for simulating payment page - Add beautiful mock gateway UI with RTL support - Update PaymentController to switch between mock/real based on config - Fix double-click bug: prevent re-processing completed transactions - Add payment-result page with success/failure UI - All API endpoints tested successfully: * balance, transactions, activity-log * admin-adjust (deposit/withdrawal) * freeze/unfreeze * online payment redirect + callback + verify - Wallet balance verified: 67,700,000 IRR after all transactions - 5 transactions recorded with correct gateways (manual/zarinpal) Closes: Payment gateway integration for Phase 2
126 lines
4.0 KiB
PHP
126 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ZarinpalService
|
|
{
|
|
private string $merchantId;
|
|
private string $baseUrl;
|
|
private ?string $callbackUrl;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->merchantId = config('ifnex.zarinpal.merchant_id', 'fake-merchant-id-for-testing');
|
|
$isSandbox = config('ifnex.zarinpal.sandbox', true);
|
|
|
|
$this->baseUrl = $isSandbox
|
|
? 'https://sandbox.zarinpal.com/pg/v4'
|
|
: 'https://api.zarinpal.com/pg/v4';
|
|
|
|
// ✅ اصلاح: مدیریت null و تنظیم callback
|
|
$this->callbackUrl = config('ifnex.zarinpal.callback_url')
|
|
?? url('/api/v1/payment/callback');
|
|
}
|
|
|
|
public function requestPayment(
|
|
float $amount,
|
|
string $description,
|
|
?string $mobile = null,
|
|
?string $email = null
|
|
): array {
|
|
$amountToman = (int) round($amount / 10);
|
|
|
|
$response = Http::timeout(30)->post("{$this->baseUrl}/PaymentRequest.json", [
|
|
'merchant_id' => $this->merchantId,
|
|
'amount' => $amountToman,
|
|
'currency' => 'IRR',
|
|
'description' => $description,
|
|
'callback_url' => $this->callbackUrl,
|
|
'metadata' => [
|
|
'mobile' => $mobile,
|
|
'email' => $email,
|
|
],
|
|
]);
|
|
|
|
$result = $response->json();
|
|
|
|
Log::info('Zarinpal Payment Request', [
|
|
'amount' => $amountToman,
|
|
'response' => $result,
|
|
]);
|
|
|
|
if (!isset($result['data']['code']) || $result['data']['code'] !== 100) {
|
|
throw new \Exception('خطا در ارتباط با زرینپال: ' . ($result['errors']['message'] ?? $result['data']['message'] ?? 'نامشخص'));
|
|
}
|
|
|
|
$authority = $result['data']['authority'];
|
|
$paymentUrl = $this->getPaymentUrl($authority);
|
|
|
|
return [
|
|
'authority' => $authority,
|
|
'payment_url' => $paymentUrl,
|
|
'amount' => $amount,
|
|
'amount_toman' => $amountToman,
|
|
];
|
|
}
|
|
|
|
public function verifyPayment(string $authority, float $expectedAmount): array
|
|
{
|
|
$amountToman = (int) round($expectedAmount / 10);
|
|
|
|
$response = Http::timeout(30)->post("{$this->baseUrl}/PaymentVerification.json", [
|
|
'merchant_id' => $this->merchantId,
|
|
'authority' => $authority,
|
|
'amount' => $amountToman,
|
|
]);
|
|
|
|
$result = $response->json();
|
|
|
|
Log::info('Zarinpal Payment Verification', [
|
|
'authority' => $authority,
|
|
'response' => $result,
|
|
]);
|
|
|
|
if (!isset($result['data']['code'])) {
|
|
return [
|
|
'success' => false,
|
|
'error_code' => -1,
|
|
'error_message' => 'پاسخ نامعتبر از زرینپال',
|
|
];
|
|
}
|
|
|
|
if ($result['data']['code'] === 100) {
|
|
return [
|
|
'success' => true,
|
|
'ref_id' => $result['data']['ref_id'],
|
|
'card_hash' => $result['data']['card_hash'] ?? null,
|
|
'card_pan' => $result['data']['card_pan'] ?? null,
|
|
];
|
|
} elseif ($result['data']['code'] === 101) {
|
|
return [
|
|
'success' => true,
|
|
'ref_id' => $result['data']['ref_id'] ?? null,
|
|
'already_verified' => true,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'success' => false,
|
|
'error_code' => $result['data']['code'] ?? null,
|
|
'error_message' => $result['errors']['message'] ?? 'نامشخص',
|
|
];
|
|
}
|
|
|
|
private function getPaymentUrl(string $authority): string
|
|
{
|
|
$isSandbox = config('ifnex.zarinpal.sandbox', true);
|
|
$gatewayUrl = $isSandbox
|
|
? 'https://sandbox.zarinpal.com/pg/StartPay/'
|
|
: 'https://www.zarinpal.com/pg/StartPay/';
|
|
|
|
return $gatewayUrl . $authority;
|
|
}
|
|
} |