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(); self::assertCustomerIdentityVerified($customer instanceof \App\Models\Customer ? $customer : null); $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); } }