ifnex/04_Laravel/app/Services/ZarinpalService.php
Kazem Alghasi 78f5919b69 feat(wallet): complete wallet system with admin adjust, freeze/unfreeze, activity logs
- 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
2026-08-07 22:51:42 +03:30

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;
}
}