config = $config; $this->client = $client; $this->walletBroadcast = $walletBroadcast; } /** * پیدا کردن مشتری با اولویت: شناسه مستقیم، کد تفصیلی، موبایل، کد ملی */ public function resolveCustomer(array $params): ?Customer { // 1. با شناسه مشتری در بورس مس if (! empty($params['customer_id'])) { $customer = Customer::find((int) $params['customer_id']); if ($customer) { return $customer; } } // 2. با کد تفصیلی شایگان (moshtari_code) $tafsil = trim((string) ($params['customer_tafsil'] ?? $params['moshtari_code'] ?? $params['job_number'] ?? '')); if ($tafsil !== '') { $customer = Customer::where('moshtari_code', $tafsil)->first(); if ($customer) { return $customer; } // بررسی فرمول احتمالی کد کل + کد کاربر ۷ رقمی $kol = trim($this->config->getString('shayegan_kol_customer', '121')); if ($kol !== '' && str_starts_with($tafsil, $kol)) { $subId = (int) substr($tafsil, strlen($kol)); if ($subId > 0) { $customer = Customer::find($subId); if ($customer) { return $customer; } } } } // 3. با شماره موبایل $mobile = trim((string) ($params['mobile'] ?? '')); if ($mobile !== '') { $customer = Customer::findActiveByMobile($mobile); if ($customer) { return $customer; } } // 4. با کد ملی $nCode = trim((string) ($params['n_code'] ?? $params['national_code'] ?? $params['mellicode'] ?? '')); if ($nCode !== '') { $customer = Customer::where('n_code', $nCode)->first(); if ($customer) { return $customer; } } return null; } /** * ثبت درخواست شارژ ورودی از شایگان در وضعیت pending */ public function registerCharge(array $data, string $source = 'webhook'): array { $customer = $this->resolveCustomer($data); if (! $customer) { return [ 'ok' => false, 'message' => 'مشتری با مشخصات ارسالی (کد مشتری، کد تفصیلی، موبایل یا کد ملی) یافت نشد.', ]; } $amount = (float) ($data['amount'] ?? $data['value'] ?? $data['RialBes'] ?? $data['AmountBes'] ?? 0); if ($amount <= 0) { return [ 'ok' => false, 'message' => 'مبلغ شارژ باید مقداری مثبت و بزرگتر از صفر باشد.', ]; } $shayeganNumber = trim((string) ($data['shayegan_number'] ?? $data['Number'] ?? $data['doc_number'] ?? '')); $tafsil = trim((string) ($data['customer_tafsil'] ?? $data['moshtari_code'] ?? $customer->moshtari_code ?? '')); $description = trim((string) ($data['description'] ?? $data['desc'] ?? $data['note'] ?? 'شارژ از طریق پنل حسابداری شایگان')); // جلوگیری از ثبت تکراری اگر قبلاً سند با همین شماره برای این مشتری ثبت شده است if ($shayeganNumber !== '') { $existing = ShayeganInboundCharge::where('shayegan_number', $shayeganNumber) ->where('customer_id', $customer->id) ->first(); if ($existing) { return [ 'ok' => true, 'message' => 'این سند شایگان قبلاً ثبت شده است.', 'charge' => $existing->load('customer'), 'duplicate' => true, ]; } } $charge = ShayeganInboundCharge::create([ 'customer_id' => $customer->id, 'customer_tafsil' => $tafsil ?: null, 'shayegan_number' => $shayeganNumber ?: null, 'amount' => $amount, 'description' => $description, 'source' => $source, 'status' => 'pending', 'raw_data' => $data, ]); Log::info('shayegan.inbound_charge_registered', [ 'charge_id' => $charge->id, 'customer_id' => $customer->id, 'amount' => $amount, 'shayegan_number' => $shayeganNumber, 'source' => $source, ]); return [ 'ok' => true, 'message' => "حساب شخص {$customer->name} از طریق پنل حسابداری مقدار " . number_format($amount) . " ریال شارژ شد.", 'charge' => $charge->load('customer'), ]; } /** * تایید شارژ و افزایش کیف پول ریالی مشتری در بورس مس */ public function approveCharge(ShayeganInboundCharge $charge, ?User $adminUser = null): array { return DB::transaction(function () use ($charge, $adminUser) { $charge = ShayeganInboundCharge::where('id', $charge->id)->lockForUpdate()->firstOrFail(); if ($charge->status === 'approved') { return [ 'ok' => true, 'message' => 'این شارژ قبلاً تایید شده است.', 'charge' => $charge->load(['customer', 'approver']), ]; } if ($charge->status !== 'pending') { throw new UnprocessableEntityHttpException('وضعیت این شارژ برای تایید معتبر نیست (' . $charge->status . ').'); } $customer = $charge->customer; if (! $customer) { throw new UnprocessableEntityHttpException('مشتری مرتبط با این شارژ یافت نشد.'); } $adminId = $adminUser ? (int) $adminUser->id : 1; $note = $charge->description ?: ('شارژ تایید شده از پنل حسابداری شایگان' . ($charge->shayegan_number ? " (سند {$charge->shayegan_number})" : '')); // 1. ثبت Withdraw (واریز ریالی با sts = 4) $withdraw = Withdraw::create([ 'user_id' => $adminId, 'customer_id' => $customer->id, 'cash' => $charge->amount, 'note' => $note, 'sts' => 4, ]); // 2. افزایش موجودی در کیف پول (Rooyesh Wallet) $walletInterface = new WalletInterface((int) $customer->id, (int) $customer->type); new TransactionInterface($walletInterface, [ 'gold' => 0, 'cash' => $charge->amount, 'expire_at' => now(), 'note' => $note, 'withdraw_id' => $withdraw->id, 'active' => true, ], 3, true, true, 0); // type 3 = deposit, deposit = true // 3. ثبت لاگ تراکنش ادمین $lastTx = Transaction::where('withdraw_id', $withdraw->id)->latest('id')->first(); TransactionsAdmin::create([ 'customer_id' => $customer->id, 'customer_to_id' => $customer->id, 'user_id' => $adminId, 'wallet' => null, 'transaction_id' => $lastTx?->id, 'cash' => $charge->amount, 'note' => $note, 'type' => 1, 'gold' => 0, 'balance_cash' => 0, ]); // 4. به‌روزرسانی وضعیت رکورد شارژ شایگان $charge->status = 'approved'; $charge->approved_by = $adminId; $charge->approved_at = now(); $charge->withdraw_id = $withdraw->id; $charge->transaction_id = $lastTx?->id; $charge->save(); // 5. برودکست ریل‌تایم موجودی کیف پول کاربر try { $this->walletBroadcast->broadcastForCustomer((int) $customer->id, (int) $customer->type); } catch (\Throwable $e) { Log::warning('shayegan.wallet_broadcast_failed', ['error' => $e->getMessage()]); } // 6. تریگر اتوماسیون try { \App\Services\Automation\AutomationEvents::fire('wallet.rial_charge', (int) $customer->id); } catch (\Throwable $e) { Log::warning('shayegan.automation_failed', ['error' => $e->getMessage()]); } Log::info('shayegan.inbound_charge_approved', [ 'charge_id' => $charge->id, 'customer_id' => $customer->id, 'amount' => $charge->amount, 'withdraw_id' => $withdraw->id, 'admin_id' => $adminId, ]); return [ 'ok' => true, 'message' => "کیف پول شخص {$customer->name} به مبلغ " . number_format($charge->amount) . " ریال با موفقیت شارژ شد.", 'charge' => $charge->load(['customer', 'approver']), ]; }); } /** * رد درخواست شارژ */ public function rejectCharge(ShayeganInboundCharge $charge, ?User $adminUser = null, ?string $reason = null): array { if ($charge->status === 'approved') { throw new UnprocessableEntityHttpException('شارژ تایید شده را نمی‌توان رد کرد.'); } $adminId = $adminUser ? (int) $adminUser->id : 1; $charge->status = 'rejected'; $charge->rejected_by = $adminId; $charge->rejected_at = now(); $charge->reject_reason = $reason; $charge->save(); Log::info('shayegan.inbound_charge_rejected', [ 'charge_id' => $charge->id, 'customer_id' => $charge->customer_id, 'admin_id' => $adminId, 'reason' => $reason, ]); return [ 'ok' => true, 'message' => 'درخواست شارژ رد شد.', 'charge' => $charge->load(['customer', 'rejecter']), ]; } /** * استعلام اسناد جدید از وب‌سرویس شایگان (Sync / Polling) */ public function pollFromShayegan(): array { if (! $this->client->isConfigured()) { return [ 'ok' => false, 'message' => 'تنظیمات اتصال وب‌سرویس شایگان در سیستم پیکربندی نشده است.', 'new_count' => 0, ]; } try { // استعلام از وب‌سرویس شایگان (مانند Document/Get با فیلتر بستانکاری تفصیلی مشتریان) $res = $this->client->get('Document/Get', [], 0, 50); if (empty($res['ok']) || empty($res['body']['Result']) || ! is_array($res['body']['Result'])) { return [ 'ok' => true, 'message' => 'سند جدیدی در استعلام شایگان یافت نشد.', 'new_count' => 0, ]; } $count = 0; foreach ($res['body']['Result'] as $doc) { $docNumber = (string) ($doc['Number'] ?? $doc['DocNumber'] ?? ''); if ($docNumber === '') { continue; } $rows = $doc['Body'] ?? []; if (! is_array($rows)) { continue; } foreach ($rows as $row) { $amountBes = (float) ($row['AmountBes'] ?? $row['RialBes'] ?? 0); $accountNumber = trim((string) ($row['AccountNumber'] ?? '')); if ($amountBes <= 0 || $accountNumber === '') { continue; } // جستجوی مشتری با شماره تفصیلی $customer = Customer::where('moshtari_code', $accountNumber)->first(); if (! $customer) { continue; } // آیا قبلا این شماره سند ثبت شده؟ $already = ShayeganInboundCharge::where('shayegan_number', $docNumber) ->where('customer_id', $customer->id) ->exists(); if (! $already) { $this->registerCharge([ 'customer_id' => $customer->id, 'customer_tafsil' => $accountNumber, 'shayegan_number' => $docNumber, 'amount' => $amountBes, 'description' => (string) ($row['DocRowDesc'] ?? $doc['DocDescription'] ?? 'استعلام خودکار از شایگان'), ], 'sync'); $count++; } } } return [ 'ok' => true, 'message' => $count > 0 ? "تعداد {$count} شارژ جدید از شایگان واکشی شد." : 'سند شارژ جدیدی یافت نشد.', 'new_count' => $count, ]; } catch (\Throwable $e) { Log::warning('shayegan.poll_failed', ['error' => $e->getMessage()]); return [ 'ok' => false, 'message' => 'خطا در ارتباط با وب‌سرویس شایگان: ' . $e->getMessage(), 'new_count' => 0, ]; } } }