sa()->enabled(); } private function abortSaUnavailable(): void { abort(502, 'ارتباط با کیف پول پیامک سوپرادمین برقرار نشد.'); } /** * @param array $chargeInfo * @return array{card_number: string, sheba: string, account_holder: string, note: string} */ private function depositFromChargeInfo(array $chargeInfo): array { return [ 'card_number' => trim((string) ($chargeInfo['card'] ?? $chargeInfo['card_number'] ?? '')), 'sheba' => trim((string) ($chargeInfo['iban'] ?? $chargeInfo['sheba'] ?? '')), 'account_holder' => trim((string) ($chargeInfo['account_holder'] ?? $chargeInfo['accountHolder'] ?? '')), 'note' => trim((string) ($chargeInfo['instruction'] ?? $chargeInfo['note'] ?? '')), ]; } /** @param array $deposit */ private function depositHasAny(array $deposit): bool { foreach (['card_number', 'sheba', 'account_holder', 'note'] as $field) { if (trim((string) ($deposit[$field] ?? '')) !== '') { return true; } } return false; } /** * اگر سوپرادمین کارت/شبا خالی بدهد، دیفالت محلی را نگه دار — وگرنه هر رفرش هدر تنظیمات را خالی می‌کند. * * @param array|null $chargeInfo * @return array{card_number: string, sheba: string, account_holder: string, note: string} */ private function resolveDeposit(?array $chargeInfo): array { $fromSa = is_array($chargeInfo) ? $this->depositFromChargeInfo($chargeInfo) : [ 'card_number' => '', 'sheba' => '', 'account_holder' => '', 'note' => '', ]; $local = SmsWalletService::getDepositInfo(); $out = []; foreach (['card_number', 'sheba', 'account_holder', 'note'] as $field) { $saVal = trim((string) ($fromSa[$field] ?? '')); $out[$field] = $saVal !== '' ? $saVal : $local[$field]; } return $out; } /** * @param array $data */ private function mirrorLocalFromSa(array $data): void { try { $wallet = is_array($data['wallet'] ?? null) ? $data['wallet'] : $data; $balance = null; if (isset($wallet['balance'])) { $balance = (int) $wallet['balance']; } elseif (isset($data['balance'])) { $balance = (int) $data['balance']; } $price = null; if (isset($wallet['price_per_part'])) { $price = (int) $wallet['price_per_part']; } elseif (isset($data['price_per_part'])) { $price = (int) $data['price_per_part']; } $blocked = null; if (is_array($data['wallet'] ?? null) && array_key_exists('is_blocked', $wallet)) { $blocked = (bool) $wallet['is_blocked']; } elseif (array_key_exists('is_blocked', $data)) { $blocked = (bool) $data['is_blocked']; } if ($balance === null && $price === null && $blocked === null) { return; } app(SmsWalletService::class)->mirrorFromSuperAdmin($balance, $price, $blocked); if (isset($data['charge_info']) && is_array($data['charge_info'])) { $fromCharge = $this->depositFromChargeInfo($data['charge_info']); if ($this->depositHasAny($fromCharge)) { SmsWalletService::setDepositInfo($fromCharge); } } if (isset($data['deposit']) && is_array($data['deposit'])) { if ($this->depositHasAny($data['deposit'])) { SmsWalletService::setDepositInfo($data['deposit']); } } } catch (\Throwable $e) { report($e); } } public function overview() { if ($this->useSuperAdmin()) { $data = $this->sa()->overview(); if ($data === null) { $this->abortSaUnavailable(); } $charge = is_array($data['charge_info'] ?? null) ? $data['charge_info'] : []; $data['deposit'] = $this->resolveDeposit($charge); $this->mirrorLocalFromSa($data); return response()->json($data); } $wallet = app(SmsWalletService::class)->getWallet(); $monthStart = Carbon::now()->startOfMonth(); $monthStats = SmsSendLog::query() ->where('created_at', '>=', $monthStart) ->selectRaw('COALESCE(SUM(parts), 0) as total_parts, COALESCE(SUM(total_amount), 0) as total_amount, COUNT(*) as total_messages') ->first(); $todayStats = SmsSendLog::query() ->whereDate('created_at', Carbon::today()) ->selectRaw('COALESCE(SUM(parts), 0) as total_parts, COALESCE(SUM(total_amount), 0) as total_amount, COUNT(*) as total_messages') ->first(); return response()->json([ 'wallet' => [ 'balance' => (int) $wallet->balance, 'price_per_part' => (int) $wallet->price_per_part, 'is_blocked' => (bool) $wallet->is_blocked, 'alert_days_sent' => (int) $wallet->alert_days_sent, 'last_alert_date' => $wallet->last_alert_date?->toDateString(), ], 'deposit' => SmsWalletService::getDepositInfo(), 'stats' => [ 'today' => [ 'parts' => (int) ($todayStats->total_parts ?? 0), 'amount' => (int) ($todayStats->total_amount ?? 0), 'messages' => (int) ($todayStats->total_messages ?? 0), ], 'month' => [ 'parts' => (int) ($monthStats->total_parts ?? 0), 'amount' => (int) ($monthStats->total_amount ?? 0), 'messages' => (int) ($monthStats->total_messages ?? 0), ], ], ]); } /** * خلاصه سبک کیف پول برای نمایش «اعتبار پیامک» در هدر؛ برای همه ادمین‌ها. * اگر سوپرادمین قطع باشد، روی آینهٔ محلی برمی‌گردیم — نه 502 (ویجت هدر نباید بترکد). */ public function walletSummary() { if (! SmsWalletService::isBillingEnabled()) { return response()->json([ 'enabled' => false, 'balance' => 0, 'is_blocked' => false, 'deposit' => SmsWalletService::getDepositInfo(), ]); } if ($this->useSuperAdmin()) { $balanceData = $this->sa()->balance(); $charge = $this->sa()->chargeInfo(); if ($balanceData !== null) { $deposit = $this->resolveDeposit(is_array($charge) ? $charge : null); $this->mirrorLocalFromSa(array_merge($balanceData, [ 'deposit' => $deposit, ])); return response()->json([ 'enabled' => true, 'balance' => (int) ($balanceData['balance'] ?? 0), 'is_blocked' => (bool) ($balanceData['is_blocked'] ?? false), 'deposit' => $deposit, 'source' => 'superadmin', 'sa_partial' => $charge === null, ]); } // SA فعال است ولی balance نرسید — آخرین آینهٔ محلی return response()->json($this->localWalletSummaryPayload('local_fallback')); } return response()->json($this->localWalletSummaryPayload('local')); } /** * @return array{enabled: bool, balance: int, is_blocked: bool, deposit: array, source: string} */ private function localWalletSummaryPayload(string $source): array { $balance = 0; $isBlocked = false; if (SmsWalletService::isBillingOperational()) { $wallet = app(SmsWalletService::class)->getWallet(); $balance = (int) $wallet->balance; $isBlocked = (bool) $wallet->is_blocked; } return [ 'enabled' => true, 'balance' => $balance, 'is_blocked' => $isBlocked, 'deposit' => SmsWalletService::getDepositInfo(), 'source' => $source, ]; } /** * ویرایش اطلاعات واریز (کارت/شبا/نام/متن) — فقط سوپرادمین پنل پیامک. */ public function updateDepositInfo(Request $request) { $data = $request->validate([ 'card_number' => 'nullable|string|max:50', 'sheba' => 'nullable|string|max:50', 'account_holder' => 'nullable|string|max:191', 'note' => 'nullable|string|max:2000', ]); if ($this->useSuperAdmin()) { $result = $this->sa()->updateChargeInfo([ 'card' => (string) ($data['card_number'] ?? ''), 'iban' => (string) ($data['sheba'] ?? ''), 'account_holder' => (string) ($data['account_holder'] ?? ''), 'instruction' => (string) ($data['note'] ?? ''), ]); if ($result === null) { $this->abortSaUnavailable(); } $charge = is_array($result['charge_info'] ?? null) ? $result['charge_info'] : []; $deposit = $this->depositFromChargeInfo($charge); $this->mirrorLocalFromSa(array_merge($result, ['deposit' => $deposit])); return response()->json([ 'message' => (string) ($result['message'] ?? 'اطلاعات واریز به‌روزرسانی شد.'), 'deposit' => $deposit, 'source' => 'superadmin', ]); } $deposit = SmsWalletService::setDepositInfo($data); return response()->json([ 'message' => 'اطلاعات واریز به‌روزرسانی شد.', 'deposit' => $deposit, ]); } public function charge(Request $request) { $data = $request->validate([ 'amount' => 'required|integer|min:1', 'note' => 'nullable|string|max:500', ]); if ($this->useSuperAdmin()) { $result = $this->sa()->charge((int) $data['amount'], $data['note'] ?? null); if ($result === null) { $this->abortSaUnavailable(); } $this->mirrorLocalFromSa($result); return response()->json([ 'message' => (string) ($result['message'] ?? 'کیف پول با موفقیت شارژ شد.'), 'balance' => (int) ($result['balance'] ?? 0), 'source' => 'superadmin', ]); } $wallet = app(SmsWalletService::class)->charge( (int) $data['amount'], auth()->id(), $data['note'] ?? null ); return response()->json([ 'message' => 'کیف پول با موفقیت شارژ شد.', 'balance' => (int) $wallet->balance, ]); } public function updatePrice(Request $request) { $data = $request->validate([ 'price_per_part' => 'required|integer|min:0', ]); if ($this->useSuperAdmin()) { $result = $this->sa()->updatePrice((int) $data['price_per_part']); if ($result === null) { $this->abortSaUnavailable(); } $this->mirrorLocalFromSa($result); return response()->json([ 'message' => (string) ($result['message'] ?? 'قیمت هر پارت به‌روزرسانی شد.'), 'price_per_part' => (int) ($result['price_per_part'] ?? 0), 'source' => 'superadmin', ]); } $wallet = app(SmsWalletService::class)->updatePricePerPart((int) $data['price_per_part']); return response()->json([ 'message' => 'قیمت هر پارت به‌روزرسانی شد.', 'price_per_part' => (int) $wallet->price_per_part, ]); } public function updateBlock(Request $request) { $data = $request->validate([ 'is_blocked' => 'required|boolean', ]); if ($this->useSuperAdmin()) { $result = $this->sa()->setBlocked((bool) $data['is_blocked']); if ($result === null) { $this->abortSaUnavailable(); } $this->mirrorLocalFromSa($result); return response()->json([ 'message' => (string) ($result['message'] ?? ''), 'is_blocked' => (bool) ($result['is_blocked'] ?? false), 'source' => 'superadmin', ]); } $wallet = app(SmsWalletService::class)->setBlocked((bool) $data['is_blocked']); return response()->json([ 'message' => $wallet->is_blocked ? 'ارسال پیامک مسدود شد.' : 'ارسال پیامک فعال شد.', 'is_blocked' => (bool) $wallet->is_blocked, ]); } public function logsTable(Request $request) { if ($this->useSuperAdmin()) { $data = $this->sa()->logsTable($request->all()); if ($data === null) { $this->abortSaUnavailable(); } return response()->json($data); } $records = SmsSendLog::query()->orderByDesc('id'); if ($request->filled('from_date')) { $records->whereDate('created_at', '>=', $request->input('from_date')); } if ($request->filled('to_date')) { $records->whereDate('created_at', '<=', $request->input('to_date')); } $table = TableEngine::create($records, $request->all()) ->initSearch(['receiver', 'body', 'provider', 'source']) ->select([ 'id', 'receiver', 'body', 'parts', 'price_per_part', 'total_amount', 'provider', 'source', 'is_alert', 'created_at', ]); return response()->json($table->export()); } public function ledgerTable(Request $request) { if ($this->useSuperAdmin()) { $data = $this->sa()->ledgerTable($request->all()); if ($data === null) { $this->abortSaUnavailable(); } return response()->json($data); } $records = SmsLedger::query()->orderByDesc('id'); if ($request->filled('from_date')) { $records->whereDate('created_at', '>=', $request->input('from_date')); } if ($request->filled('to_date')) { $records->whereDate('created_at', '<=', $request->input('to_date')); } $table = TableEngine::create($records, $request->all()) ->initSearch(['type', 'note']) ->select([ 'id', 'type', 'amount', 'balance_after', 'price_per_part', 'parts', 'note', 'created_by', 'created_at', ]); return response()->json($table->export()); } public function report(Request $request) { $from = $request->input('from_date', Carbon::now()->startOfMonth()->toDateString()); $to = $request->input('to_date', Carbon::now()->toDateString()); if ($this->useSuperAdmin()) { $data = $this->sa()->report($from, $to); if ($data === null) { $this->abortSaUnavailable(); } return response()->json($data); } $summary = SmsSendLog::query() ->whereDate('created_at', '>=', $from) ->whereDate('created_at', '<=', $to) ->selectRaw('COALESCE(SUM(parts), 0) as total_parts, COALESCE(SUM(total_amount), 0) as total_amount, COUNT(*) as total_messages') ->first(); $byProvider = SmsSendLog::query() ->whereDate('created_at', '>=', $from) ->whereDate('created_at', '<=', $to) ->select('provider', DB::raw('COUNT(*) as messages'), DB::raw('SUM(parts) as parts'), DB::raw('SUM(total_amount) as amount')) ->groupBy('provider') ->get(); $wallet = app(SmsWalletService::class)->getWallet(); return response()->json([ 'from_date' => $from, 'to_date' => $to, 'balance' => (int) $wallet->balance, 'summary' => [ 'parts' => (int) ($summary->total_parts ?? 0), 'amount' => (int) ($summary->total_amount ?? 0), 'messages' => (int) ($summary->total_messages ?? 0), ], 'by_provider' => $byProvider, ]); } public function chargeReport(Request $request) { $request->validate([ 'from_date' => 'nullable|date', 'to_date' => 'nullable|date', ]); if ($this->useSuperAdmin()) { $data = $this->sa()->chargeReport( $request->input('from_date'), $request->input('to_date') ); if ($data === null) { $this->abortSaUnavailable(); } return response()->json($data); } $baseQuery = SmsLedger::query()->where('type', SmsWalletService::TYPE_CHARGE); if ($request->filled('from_date')) { $baseQuery->whereDate('created_at', '>=', $request->input('from_date')); } if ($request->filled('to_date')) { $baseQuery->whereDate('created_at', '<=', $request->input('to_date')); } $summary = (clone $baseQuery) ->selectRaw('COUNT(*) as total_charges, COALESCE(SUM(amount), 0) as total_amount') ->first(); $items = (clone $baseQuery) ->orderByDesc('created_at') ->select([ 'id', 'amount', 'balance_after', 'note', 'created_by', 'created_at', ]) ->get(); return response()->json([ 'from_date' => $request->input('from_date'), 'to_date' => $request->input('to_date'), 'summary' => [ 'charges' => (int) ($summary->total_charges ?? 0), 'amount' => (int) ($summary->total_amount ?? 0), ], 'items' => $items, ]); } }