- Add Enums: TransactionType, TransactionStatus, PaymentGateway - Migrations: wallets, wallet_transactions, wallet_activity_logs - Models: Wallet, WalletTransaction, WalletActivityLog - Services: WalletService with complete business logic - Controllers: WalletController, PaymentController - Filament Resources: WalletResource, WalletTransactionResource - Artisan Command: ifnex:token for API token generation - Sanctum integration with statefulApi and exception handling - Full API endpoints: balance, transactions, admin-adjust, freeze/unfreeze
128 lines
3.9 KiB
PHP
128 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\WalletTransaction;
|
|
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');
|
|
$isProduction = config('ifnex.zarinpal.sandbox', true);
|
|
|
|
// URLs برای محیط تست و واقعی
|
|
$this->baseUrl = $isProduction
|
|
? 'https://sandbox.zarinpal.com/pg/v4'
|
|
: 'https://api.zarinpal.com/pg/v4';
|
|
|
|
$this->callbackUrl = config('ifnex.zarinpal.callback_url', url('/api/v1/payment/callback'));
|
|
}
|
|
|
|
/**
|
|
* ایجاد درخواست پرداخت (Payment Request)
|
|
*/
|
|
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 ($result['data']['code'] !== 100) {
|
|
throw new \Exception('خطا در ارتباط با زرینپال: ' . ($result['errors']['message'] ?? 'نامشخص'));
|
|
}
|
|
|
|
$authority = $result['data']['authority'];
|
|
$paymentUrl = $this->getPaymentUrl($authority);
|
|
|
|
return [
|
|
'authority' => $authority,
|
|
'payment_url' => $paymentUrl,
|
|
'amount' => $amount,
|
|
'amount_toman' => $amountToman,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* بررسی صحت پرداخت (Verification)
|
|
*/
|
|
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 ($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) {
|
|
// قبلاً verify شده
|
|
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
|
|
{
|
|
$isProduction = config('ifnex.zarinpal.sandbox', true);
|
|
$gatewayUrl = $isProduction
|
|
? 'https://sandbox.zarinpal.com/pg/StartPay/'
|
|
: 'https://www.zarinpal.com/pg/StartPay/';
|
|
|
|
return $gatewayUrl . $authority;
|
|
}
|
|
} |