286 lines
10 KiB
PHP
286 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\pay;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Withdraw;
|
|
use App\Services\ConfigService;
|
|
use App\Services\Payment\Contracts\SupportsStatusQuery;
|
|
use App\Services\Payment\WalletDepositCreditor;
|
|
use App\Services\Payment\WalletPaymentManager;
|
|
use Illuminate\Http\Request;
|
|
use InvalidArgumentException;
|
|
use RuntimeException;
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
/**
|
|
* API عمومی شارژ آنلاین کیف — بدون افشای credential به کلاینت.
|
|
* انتخاب درگاه فقط سمت سرور (wallet_payment_provider).
|
|
*/
|
|
class WalletPaymentController extends Controller
|
|
{
|
|
public function __construct(
|
|
private WalletPaymentManager $payments,
|
|
private ConfigService $config,
|
|
private WalletDepositCreditor $creditor,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* فقط فلگ نمایش دکمه — بدون نام درگاه و بدون تنظیمات محرمانه.
|
|
*/
|
|
public function options()
|
|
{
|
|
return response()->json($this->payments->publicOptions());
|
|
}
|
|
|
|
public function initiate(Request $request)
|
|
{
|
|
$gateway = $this->payments->resolve();
|
|
if (!$gateway || !$gateway->isAvailable()) {
|
|
throw new UnprocessableEntityHttpException('پرداخت آنلاین در حال حاضر فعال نیست');
|
|
}
|
|
|
|
$amount = (int) $request->input('value', $request->input('amount', 0));
|
|
$minDeposit = $this->config->getInt('min_deposit', 0);
|
|
if ($amount < 1000) {
|
|
throw new UnprocessableEntityHttpException('حداقل مبلغ پرداخت ۱۰۰۰ ریال است');
|
|
}
|
|
if ($minDeposit > 0 && $amount < $minDeposit) {
|
|
throw new UnprocessableEntityHttpException(
|
|
'حداقل مقدار واریز ' . number_format($minDeposit) . ' ریال است'
|
|
);
|
|
}
|
|
|
|
$customer = auth('customer')->user();
|
|
$withdraw = Withdraw::create([
|
|
'customer_id' => $customer->id,
|
|
'cash' => $amount,
|
|
'note' => 'واریز آنلاین',
|
|
'sts' => 1,
|
|
]);
|
|
|
|
$referenceId = 'WD-' . $withdraw->id;
|
|
|
|
try {
|
|
$result = $gateway->initiate([
|
|
'amount' => $amount,
|
|
'reference_id' => $referenceId,
|
|
'description' => 'شارژ کیف پول کاربر ' . ($customer->name ?? $customer->id),
|
|
'metadata' => [
|
|
'withdraw_id' => $withdraw->id,
|
|
'customer_id' => $customer->id,
|
|
],
|
|
]);
|
|
} catch (RuntimeException $e) {
|
|
$withdraw->sts = 3;
|
|
$withdraw->save();
|
|
|
|
\Log::warning('wallet.payment.initiate_failed', [
|
|
'withdraw_id' => $withdraw->id,
|
|
'message' => $e->getMessage(),
|
|
]);
|
|
|
|
return response()->json([
|
|
'message' => $e->getMessage() ?: 'مشکل در ارتباط با درگاه',
|
|
], 422);
|
|
}
|
|
|
|
if (!empty($result['session_id'])) {
|
|
$withdraw->tracked_number = (string) $result['session_id'];
|
|
$withdraw->save();
|
|
}
|
|
|
|
// حداقل اطلاعات برای redirect — بدون credential و بدون جزئیات داخلی درگاه
|
|
return response()->json([
|
|
'payment_url' => $result['payment_url'],
|
|
], 201);
|
|
}
|
|
|
|
/**
|
|
* وریفای برگشت مرورگر از فرانت — امضا و شارژ سمت سرور؛ بدون افشای secret.
|
|
* Body یا query: status, reference_id, session_id, amount, track_number, timestamp, signature
|
|
*/
|
|
public function verifyReturn(Request $request)
|
|
{
|
|
$gateway = $this->payments->resolve();
|
|
if (!$gateway) {
|
|
throw new UnprocessableEntityHttpException('پرداخت آنلاین فعال نیست');
|
|
}
|
|
|
|
// همه پارامترهای برگشت عیناً وارد امضا میشوند؛ whitelist کردن باعث
|
|
// مغایرت امضا میشود اگر پارتنر پارامتر اضافهای بفرستد.
|
|
$payload = [];
|
|
foreach ($request->all() as $key => $value) {
|
|
if (!is_string($key) || $key === '' || is_array($value) || $value === null) {
|
|
continue;
|
|
}
|
|
if (count($payload) >= 30) {
|
|
break;
|
|
}
|
|
$payload[$key] = is_bool($value) ? ($value ? 'true' : 'false') : (string) $value;
|
|
}
|
|
|
|
if (($payload['signature'] ?? '') === '' || ($payload['reference_id'] ?? '') === '') {
|
|
return response()->json([
|
|
'ok' => false,
|
|
'message' => 'پارامترهای بازگشت ناقص است',
|
|
], 422);
|
|
}
|
|
|
|
$result = $gateway->handleReturnPayload($payload);
|
|
|
|
if (!($result['ok'] ?? false)) {
|
|
// redirect مرورگر قابل اعتماد نیست؛ منبع حقیقت، استعلام مستقیم از درگاه است.
|
|
$fallback = $this->statusFallback((string) $payload['reference_id']);
|
|
if ($fallback) {
|
|
return $fallback;
|
|
}
|
|
|
|
return response()->json([
|
|
'ok' => false,
|
|
'message' => $result['message'] ?? 'خطا در تأیید پرداخت',
|
|
], (int) ($result['http_status'] ?? 400));
|
|
}
|
|
|
|
/** @var Withdraw|null $withdraw */
|
|
$withdraw = $result['withdraw'] ?? null;
|
|
if (!$withdraw) {
|
|
return response()->json(['ok' => false, 'message' => 'سفارش یافت نشد'], 404);
|
|
}
|
|
|
|
if (!$this->ownsWithdraw($withdraw)) {
|
|
return response()->json(['ok' => false, 'message' => 'دسترسی غیرمجاز'], 403);
|
|
}
|
|
|
|
$paid = (int) $withdraw->sts === 2;
|
|
|
|
return $this->paymentStateResponse(
|
|
$withdraw,
|
|
$paid ? 'success' : ((string) ($payload['status'] ?? '') === 'success' ? 'pending' : 'failed')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* استعلام وضعیت یک پرداخت از درگاه — برای polling فرانت وقتی نتیجه هنوز قطعی نیست.
|
|
*/
|
|
public function status(Request $request)
|
|
{
|
|
$referenceId = trim((string) $request->input('reference_id', ''));
|
|
if ($referenceId === '') {
|
|
return response()->json(['ok' => false, 'message' => 'شناسه پرداخت ارسال نشده'], 422);
|
|
}
|
|
|
|
$response = $this->statusFallback($referenceId);
|
|
if ($response) {
|
|
return $response;
|
|
}
|
|
|
|
return response()->json(['ok' => false, 'message' => 'سفارش یافت نشد'], 404);
|
|
}
|
|
|
|
/**
|
|
* استعلام مستقیم وضعیت از درگاه با بررسی مالکیت سفارش.
|
|
*/
|
|
private function statusFallback(string $referenceId): ?\Illuminate\Http\JsonResponse
|
|
{
|
|
$withdraw = $this->creditor->findByReference($referenceId);
|
|
if (!$withdraw || !$this->ownsWithdraw($withdraw)) {
|
|
return null;
|
|
}
|
|
|
|
if ((int) $withdraw->sts !== 2) {
|
|
$gateway = $this->payments->resolve();
|
|
if ($gateway instanceof SupportsStatusQuery) {
|
|
$gateway->syncStatusByReference($referenceId);
|
|
$withdraw = $withdraw->fresh() ?: $withdraw;
|
|
}
|
|
}
|
|
|
|
$sts = (int) $withdraw->sts;
|
|
|
|
return $this->paymentStateResponse(
|
|
$withdraw,
|
|
match ($sts) {
|
|
2 => 'success',
|
|
3 => 'failed',
|
|
default => 'pending',
|
|
}
|
|
);
|
|
}
|
|
|
|
private function ownsWithdraw(Withdraw $withdraw): bool
|
|
{
|
|
return (int) $withdraw->customer_id === (int) auth('customer')->id();
|
|
}
|
|
|
|
private function paymentStateResponse(Withdraw $withdraw, string $status): \Illuminate\Http\JsonResponse
|
|
{
|
|
return response()->json([
|
|
'ok' => true,
|
|
'paid' => (int) $withdraw->sts === 2,
|
|
'status' => $status,
|
|
'amount' => (int) $withdraw->cash,
|
|
'reference_id' => 'WD-' . $withdraw->id,
|
|
'withdraw_id' => $withdraw->id,
|
|
'track_number' => $withdraw->tracked_number,
|
|
]);
|
|
}
|
|
|
|
public function returnFromGateway(Request $request, string $provider)
|
|
{
|
|
try {
|
|
$gateway = $this->payments->resolveOrFail($provider);
|
|
} catch (InvalidArgumentException $e) {
|
|
return response('درگاه نامعتبر', 404);
|
|
}
|
|
|
|
$result = $gateway->handleReturn($request);
|
|
if (!($result['ok'] ?? false)) {
|
|
return response(
|
|
(string) ($result['message'] ?? 'خطا'),
|
|
(int) ($result['http_status'] ?? 400)
|
|
);
|
|
}
|
|
|
|
$Withdraw = $result['withdraw'];
|
|
$site_url = $this->config->resolvePublicSiteUrl($request) ?: '/';
|
|
|
|
return view('showTransactionReport', compact('Withdraw', 'site_url'));
|
|
}
|
|
|
|
public function webhook(Request $request, string $provider)
|
|
{
|
|
try {
|
|
$gateway = $this->payments->resolveOrFail($provider);
|
|
} catch (InvalidArgumentException $e) {
|
|
return response()->json(['message' => 'invalid_provider'], 404);
|
|
}
|
|
|
|
$result = $gateway->handleWebhook($request);
|
|
$status = (int) ($result['http_status'] ?? 200);
|
|
|
|
if (!($result['ok'] ?? false)) {
|
|
return response()->json([
|
|
'message' => $result['message'] ?? 'error',
|
|
], $status >= 400 ? $status : 400);
|
|
}
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'ignored' => (bool) ($result['ignored'] ?? false),
|
|
]);
|
|
}
|
|
|
|
/** سازگاری با URLهای قبلی external-payment/* → montify */
|
|
public function returnLegacy(Request $request)
|
|
{
|
|
return $this->returnFromGateway($request, WalletPaymentManager::PROVIDER_MONTIFY);
|
|
}
|
|
|
|
public function webhookLegacy(Request $request)
|
|
{
|
|
return $this->webhook($request, WalletPaymentManager::PROVIDER_MONTIFY);
|
|
}
|
|
}
|