json(['id'=>$id] , 201); } /** * add paginate to list * @param array $list * @param object $table * @return array mixed */ protected function addPaginate($table,$list){ $list = $this->addLimitPage($list); $list['count'] = $table->count(); $list['rows'] = $table->offset( $list['page']*$list['limit']) ->limit($list['limit']) ->get(); $list['page'] = (int)$list['page']; $list['limit'] = (int)$list['limit']; return $list; } /** * add page and limit to list * @param $list * @return mixed */ protected function addLimitPage($list){ if (!isset($list['page'])){ $list['page'] = 0; } if (!isset($list['limit'])){ $list['limit'] = 5; } if ($list['limit']>50){ $list['limit'] = 50; } return $list; } /** مبلغ ریالی در پیامک — جداکننده هزارگان */ public static function formatSmsRialAmount(float|int|string $value): string { return number_format(abs((float) $value), 0, '.', ','); } /** وزن طلا در پیامک — تا ۳ رقم اعشار، بدون گرد کردن به صفر برای زیر ۱ گرم */ public static function formatSmsGoldWeight(float|int|string $value): string { $formatted = rtrim(rtrim(number_format(abs((float) $value), 3, '.', ''), '0'), '.'); return $formatted !== '' ? $formatted : '0'; } /** * گزینه‌های Guzzle/cURL — فقط وقتی افزونه curl و ثابت‌ها موجود باشند. * * @param array $curlOptions * @return array> */ public static function httpClientOptions(array $curlOptions = []): array { $curl = $curlOptions; if (defined('CURLOPT_IPRESOLVE') && defined('CURL_IPRESOLVE_V4')) { $resolveOpt = (int) constant('CURLOPT_IPRESOLVE'); if (! array_key_exists($resolveOpt, $curl)) { $curl[$resolveOpt] = (int) constant('CURL_IPRESOLVE_V4'); } } return $curl === [] ? [] : ['curl' => $curl]; } public static function Send($to , $msg, $is_customer = true) { $r = self::SendMessage($to , $msg, $is_customer); // if ($r == 200){ // Sms::set($to,$msg , $is_customer); // } } /** آیا این نوع پیامک اعلان روشن است؟ پیش‌فرض true اگر کلید در settings نباشد. */ public static function isSmsNotifEnabled(string $key, bool $default = true): bool { return app(ConfigService::class)->isSmsNotifEnabled($key, $default); } /** * خرید/فروش فقط بعد از احراز هویت (is_legal ≠ 0). * جدا از active / auto_active_customer که فقط ورود به اپ را کنترل می‌کنند. */ public static function assertCustomerIdentityVerified(?Customer $customer): void { if (!$customer || (int) ($customer->is_legal ?? 0) === 0) { throw new UnprocessableEntityHttpException('لطفا ابتدا فرآیند احراز هویت خود را تکمیل کنید'); } } public static function SendMessage($to, $msg ,$is_send = false,$force_method ='null') { $credentials = [ "telegram" => [ "token" => self::Config('telegram_bot_token') ], "bale" => [ "token" => self::Config('bale_bot_token') ], "sms" => [ 'user_name' => 'ysms1362', 'password' => 'tL4w0PfNy2()', 'sender' => '3000164545', ], ]; $platforms = []; $user = User::where('mobile', $to)->first() ?: Customer::where('mobile', $to)->first(); // بله و پیامک مستقل‌اند — موفقیت یکی، دیگری را قطع نمی‌کند if ($user) { $baleToken = trim((string) ($credentials['bale']['token'] ?? '')); $baleChatId = trim((string) ($user->bale ?? '')); if ($baleToken !== '' && $baleChatId !== '') { try { $baleResponse = Http::withoutVerifying()->get( "https://tapi.bale.ai/bot{$baleToken}/sendMessage", [ 'chat_id' => $baleChatId, 'text' => "جناب {$user->name} \n{$msg}", ] ); $balePayload = $baleResponse->json(); if ($baleResponse->successful() && ($balePayload['ok'] ?? false) === true) { $platforms[] = "bale:{$baleChatId}"; } else { $platforms[] = "bale_failed:{$baleChatId}"; } } catch (\Throwable $e) { report($e); $platforms[] = "bale_error:{$baleChatId}"; } } } try { $gateway = app(SmsGatewayService::class); if ($force_method == "sms") { $smsResult = $gateway->sendOrThrow($to, trim($msg), 'notification'); } else { $smsResult = $gateway->send($to, trim($msg), 'notification'); } if ($smsResult->blocked) { $platforms[] = "blocked:{$to}"; } elseif ($smsResult->sent && $smsResult->provider) { $platforms[] = "{$smsResult->provider}:{$to}"; } else { $platforms[] = "sms_failed:{$to}"; } } catch (SmsBlockedException $e) { throw $e; } catch (\Throwable $e) { report($e); $platforms[] = "sms_error:{$to}"; } Message::create([ 'sms' => $msg, 'receiver' => $to, 'platforms' => implode(', ', $platforms), ]); } public static function SendTelegram($to , $msg) { // "chatId" => 504951108, // "chatId" => 5870340227, fathi $response = Http::withHeaders([ 'x-secret-key' => 'PvQM36mgk2FUELHjcm4YG49Nu56uQxot', ])->post('https://tele.ygram.ir/send-message', ["message" => $msg, "chatId" => $to, "botToken" => 'Paramisgold_bot']); return $response->json(); } public static function Config($name) { $row = Setting::where('key', $name)->first(); if ($row && $row->is_active && $row->value !== null && $row->value !== '') { return $row->value; } return false; } protected static function metalTypeSettingValue(): ?string { $metalType = Setting::where('key', 'metal_type')->value('value'); if ($metalType === null) { return null; } $metalType = trim((string) $metalType); return $metalType !== '' ? $metalType : null; } /** برچسب موجودی کیف اصلی: مقدار setting یا «طلا» */ public static function metalTypeLabel(): string { return self::metalTypeSettingValue() ?? 'طلا'; } public static function metalIdLabel(int $metalId): string { return match ($metalId) { 1 => 'طلا', 2 => 'نقره', 3 => 'مس', default => self::metalTypeLabel(), }; } /** برچسب فلز کیف اصلی از `main_wallet_products` یا setting */ public static function mainWalletMetalLabel(): string { $product = app(ConfigService::class)->mainWalletRepresentativeProduct(); if ($product && (int) ($product->metal_id ?? 0) > 0) { return self::metalIdLabel((int) $product->metal_id); } return self::metalTypeLabel(); } /** عنوان محصول(ات) کیف اصلی از `main_wallet_products` */ public static function mainWalletProductLabel(): string { $labels = app(ConfigService::class)->mainWalletProductLabels(); return $labels !== '' ? $labels : self::mainWalletMetalLabel(); } /** برچسب معاملات آبشده: مقدار setting یا «آبشده» */ public static function meltedGoldLabel(): string { return self::metalTypeSettingValue() ?? 'آبشده'; } public static function cashTransactionTitles(): array { return ['برداشت وجه نقد', 'واریز وجه نقد']; } public static function legacyMeltedTransactionTitles(): array { return [ 'واریز طلای ابشده', 'واریز طلای آبشده', 'برداشت طلای ابشده', 'برداشت طلای آبشده', ]; } protected static function metalLabelVariants(): array { return array_values(array_unique([ self::meltedGoldLabel(), self::metalTypeLabel(), 'طلا', 'آبشده', ])); } public static function genericMetalTransactionTitles(): array { $titles = array_merge(self::legacyMeltedTransactionTitles(), [ 'واریز طلایی', 'برداشت طلایی', ]); foreach (self::metalLabelVariants() as $label) { $titles[] = "موجودی {$label}"; $titles[] = "واریز {$label}"; $titles[] = "برداشت {$label}"; $titles[] = "خرید {$label}"; $titles[] = "فروش {$label}"; } return array_values(array_unique($titles)); } public static function isCashTransactionTitle(?string $title): bool { return $title !== null && in_array($title, self::cashTransactionTitles(), true); } public static function isGenericMetalTransactionTitle(?string $title): bool { if ($title === null || self::isCashTransactionTitle($title)) { return false; } return in_array($title, self::genericMetalTransactionTitles(), true); } public static function rialDepositTitle(): string { return 'واریز ریالی'; } public static function rialWithdrawTitle(): string { return 'برداشت ریالی'; } /** * type و title تراکنش ریالی — بر اساس علامت cash (داده‌های قدیمی گاهی type=4 با cash مثبت دارند). */ public static function normalizeRialTransactionPresentation($record, bool $persist = true): void { $type = (int) ($record->type ?? 0); if (!in_array($type, [3, 4], true)) { return; } $cash = (float) ($record->cash ?? 0); $correctType = $cash >= 0 ? 3 : 4; $correctTitle = $correctType === 3 ? self::rialDepositTitle() : self::rialWithdrawTitle(); $dirty = false; if ($correctType !== $type) { $record->type = $correctType; $dirty = true; } if (trim((string) ($record->title ?? '')) !== $correctTitle) { $record->title = $correctTitle; $dirty = true; } if ($dirty && $persist && $record instanceof \Rooyesh\Wallet\models\Transaction) { $record->save(); } } public static function productBuyTitle(string $productTitle): string { return 'خرید ' . $productTitle; } public static function productSellTitle(string $productTitle): string { return 'فروش ' . $productTitle; } public static function mainMetalBuyTitle(): string { return self::productBuyTitle(self::mainWalletProductLabel()); } public static function mainMetalSellTitle(): string { return self::productSellTitle(self::mainWalletProductLabel()); } public static function mainMetalDepositTitle(): string { return 'واریز ' . self::mainWalletProductLabel(); } public static function mainMetalWithdrawTitle(): string { return 'برداشت ' . self::mainWalletProductLabel(); } /** * آیا محصول به کیف اصلی متصل است؟ (setting: main_wallet_products) * موجودی این محصولات = gold کیف اصلی؛ تراکنش‌هایشان روی کیف اصلی می‌نشیند. */ public static function isMainWalletProduct(?int $productId): bool { if (!$productId) { return false; } return app(ConfigService::class)->isMainWalletProduct((int) $productId); } /** * شناسه کیف مؤثر برای تراکنش محصول. * محصولات متصل به کیف اصلی → null (کیف اصلی)؛ بقیه → کیف محصول خودشان. */ public static function effectiveProductWalletId(?int $productId): ?int { return self::isMainWalletProduct($productId) ? null : $productId; } /** واحد وزن معاملات فلز — از ستینگ unit؛ پیش‌فرض گرم */ public static function tradeWeightUnit(): string { return self::resolveTradeWeightUnit( Setting::where('key', 'unit')->value('value') ); } public static function resolveTradeWeightUnit(?string $value): string { $unit = trim((string) $value); if ($unit === 'کیلوگرم' || $unit === 'kilogram' || $unit === 'kg') { return 'کیلوگرم'; } return 'گرم'; } public static function weightToGrams(float $value, ?string $fromUnit = null): float { $from = $fromUnit ?? self::tradeWeightUnit(); return $from === 'کیلوگرم' ? $value * 1000 : $value; } public static function gramsToTradeUnit(float $grams): float { return self::tradeWeightUnit() === 'کیلوگرم' ? $grams / 1000 : $grams; } public static function productStorageWeightUnit($product): string { $unit = trim((string) (is_object($product) ? ($product->unit ?? '') : ($product['unit'] ?? ''))); if (self::isWeightBasedProductUnit($unit)) { return $unit; } return 'گرم'; } public static function productLineWeightToGrams($product, $quantity): float { $groupType = is_object($product) ? ($product->group_type ?? '') : ($product['group_type'] ?? ''); if ((string) $groupType === 'سکه') { return 0.0; } $qty = (float) $quantity; $unit = trim((string) (is_object($product) ? ($product->unit ?? '') : ($product['unit'] ?? ''))); $amount = (float) (is_object($product) ? ($product->amount ?? 0) : ($product['amount'] ?? 0)); if (self::isWeightBasedProductUnit($unit)) { return self::weightToGrams($qty, $unit); } return self::weightToGrams($amount * $qty, self::productStorageWeightUnit($product)); } public static function isWeightBasedProductUnit(?string $unit): bool { $unit = trim((string) $unit); return in_array($unit, ['کیلوگرم', 'گرم'], true); } public static function meltedGoldBuyTitle(): string { return 'خرید ' . self::meltedGoldLabel(); } public static function meltedGoldSellTitle(): string { return 'فروش ' . self::meltedGoldLabel(); } /** * شرح معامله از دید فروشگاه برای حسابداری (پرگار و …). * direction اپ: 1 = خرید مشتری → فروش به مشتری | 2 = فروش مشتری → خرید از مشتری * اپ مشتری همچنان از productBuyTitle / productSellTitle استفاده می‌کند. */ public static function accountingShopTradeDesc(int $customerDirection, bool $fromCart = false): string { if ($fromCart) { return 'فروش سبد به مشتری'; } if ($customerDirection === 1) { return 'فروش به مشتری'; } if ($customerDirection === 2) { return 'خرید از مشتری'; } return 'معامله'; } public static function meltedGoldDepositTitle(): string { return 'موجودی ' . self::meltedGoldLabel(); } public static function meltedGoldWithdrawTitle(): string { return 'برداشت ' . self::meltedGoldLabel(); } public static function isCountBasedProduct(?array $product): bool { if (!$product) { return false; } // معامله مقداری → نه تعدادی if (self::usesQuantityTrade($product)) { return false; } // آبشده/کاتد همیشه وزنی است if (self::isMeltedTradeGroup($product['group_type'] ?? null)) { return false; } // trade_by_quantity صراحتاً false/0 → تعدادی // (حتی اگر unit=گرم فقط وزن هر عدد باشد) if (array_key_exists('trade_by_quantity', $product) && $product['trade_by_quantity'] !== null) { return true; } // محصولات قدیمی بدون فلگ: واحد وزنی → مقداری if (self::isWeightBasedProductUnit($product['unit'] ?? null)) { return false; } if (isset($product['direct_price']) && (int) $product['direct_price'] !== 0) { return true; } $group = trim((string) ($product['group_type'] ?? '')); if (in_array($group, ['مفتول مس'], true)) { return true; } return in_array($group, ['سکه', 'گوی', 'مکعب', 'شمش', 'سکه پارسیان'], true); } public static function productUnitPayload($product): array { if (!$product) { return []; } if (is_array($product)) { return $product; } return [ 'unit' => $product->unit ?? null, 'direct_price' => $product->direct_price ?? null, 'group_type' => $product->group_type ?? null, 'amount' => $product->amount ?? null, 'trade_by_quantity' => $product->trade_by_quantity ?? null, ]; } public static function convertWeight(float $value, ?string $fromUnit, ?string $toUnit): float { $from = trim((string) ($fromUnit ?? '')); $to = trim((string) ($toUnit ?? '')); if ($from === '' || $to === '' || $from === $to) { return $value; } $grams = self::weightToGrams($value, $from); return $to === 'کیلوگرم' ? $grams / 1000 : $grams; } /** * مقدار و واحد نمایشی تراکنش — منبع حقیقت: unit محصول در ادمین. * معامله مقداری + وزن دارد: تعداد ذخیره‌شده × وزن هر واحد. */ public static function resolveTransactionDisplayGoldAndUnit(?array $payload, float $gold, bool $onMainWallet = false): array { if ($payload === []) { return [$gold, self::tradeWeightUnit()]; } $sign = $gold < 0 ? -1 : 1; $absGold = abs($gold); if ( self::usesQuantityTrade($payload) && self::hasProductUnitWeight($payload) && self::isWeightBasedProductUnit($payload['unit'] ?? null) ) { $factor = (float) ($payload['amount'] ?? 1); return [ round($absGold * $factor, 3) * $sign, trim((string) $payload['unit']), ]; } $unit = self::productQuantityUnit($payload); $productWeightUnit = self::isWeightBasedProductUnit($payload['unit'] ?? null) ? trim((string) $payload['unit']) : null; if ($onMainWallet && $productWeightUnit) { $tradeUnit = self::tradeWeightUnit(); if ($productWeightUnit !== $tradeUnit) { return [ round(self::convertWeight($absGold, $tradeUnit, $productWeightUnit), 3) * $sign, $productWeightUnit, ]; } return [$gold, $productWeightUnit]; } return [$gold, $unit]; } public static function productQuantityUnit(?array $product): string { if (self::isCountBasedProduct($product)) { return 'عدد'; } $unit = trim((string) ($product['unit'] ?? '')); if ($unit !== '') { return $unit; } return self::tradeWeightUnit(); } /** مطابق ادمین: وزن «دارد» => amount > 0 ؛ «ندارد» => amount = 0 */ public static function isMeltedTradeGroup(?string $groupType): bool { $group = trim((string) $groupType); return in_array($group, ['آبشده', 'کاتد مس'], true); } public static function hasProductUnitWeight(?array $product): bool { if (!$product || !isset($product['amount'])) { return false; } if (self::isMeltedTradeGroup($product['group_type'] ?? null)) { return false; } return (float) $product['amount'] > 0; } /** معامله مقداری: فقط فیلد trade_by_quantity */ public static function usesQuantityTrade($product): bool { $value = is_array($product) ? ($product['trade_by_quantity'] ?? null) : (is_object($product) ? ($product->trade_by_quantity ?? null) : null); return !empty($value); } /** ضریب قیمت محصول — فقط متغیر بدون مرجع اتوماتیک: قیمت هر گرم × وزن */ public static function productPriceMultiplier($product): float { $payload = is_array($product) ? $product : [ 'amount' => is_object($product) ? ($product->amount ?? null) : null, 'unit' => is_object($product) ? ($product->unit ?? null) : null, 'direct_price' => is_object($product) ? ($product->direct_price ?? null) : null, 'group_type' => is_object($product) ? ($product->group_type ?? null) : null, 'reading_method' => is_object($product) ? ($product->reading_method ?? null) : null, ]; if (! empty($payload['reading_method'])) { return 1.0; } if (self::isCountBasedProduct($payload)) { return 1.0; } if ((int) ($payload['direct_price'] ?? 0) !== 0) { return 1.0; } if (! self::hasProductUnitWeight($payload)) { return 1.0; } $amount = (float) ($payload['amount'] ?? 0); return $amount > 0 ? $amount : 1.0; } public function jibitNcode($nationalCode, $mobile) { $counter = Cnt::where('key', 'jibit_counter')->first(); if (empty($counter)) { $counter = new Cnt(); $counter->key = 'jibit_counter'; $counter->value = 0; $counter->save(); } if (!Storage::exists('accessTokenGibit') || !Storage::exists('refreshTokenGibit')) { Storage::put('accessTokenGibit', 'توکن اولیه'); Storage::put('refreshTokenGibit', 'توکن رفرش اولیه'); } $apiData['refresh'] = Http::withoutVerifying()->withToken(Storage::get('refreshTokenGibit')) ->post('https://napi.jibit.ir/ide/v1/tokens/generate', [ 'apiKey' => 'J5WxrPxPOg', 'secretKey' => '7dgeE70xQXsQL7r36j3wif6X9', ])->json(); Storage::put('accessTokenGibit', $apiData['refresh']['accessToken'] ?? ''); if (!empty($apiData['refresh']['refreshToken'])) { Storage::put('refreshTokenGibit', $apiData['refresh']['refreshToken']); } $response = Http::withoutVerifying()->withToken(Storage::get('accessTokenGibit')) ->get("https://napi.jibit.ir/ide/v1/services/matching?mobileNumber=$mobile&nationalCode=$nationalCode") ->json(); if ((isset($response['code'])) && $response['code'] == 'forbidden') { $refreshToken = Http::withoutVerifying()->withToken(Storage::get('refreshTokenGibit')) ->post('https://napi.jibit.ir/ide/v1/tokens/generate', [ 'apiKey' => 'J5WxrPxPOg', 'secretKey' => '7dgeE70xQXsQL7r36j3wif6X9', ])->json(); Storage::put('accessTokenGibit', $refreshToken['accessToken'] ?? ''); if (!empty($refreshToken['refreshToken'])) { Storage::put('refreshTokenGibit', $refreshToken['refreshToken']); } $response = Http::withoutVerifying()->withToken(Storage::get('accessTokenGibit')) ->get("https://napi.jibit.ir/ide/v1/services/matching?mobileNumber=$mobile&nationalCode=$nationalCode") ->json(); } $counter->value = (int) $counter->value + 1; $counter->save(); return $response; } public static function jibit($method, $p1 = null, $p2 = null, $p3 = null) { $counter = Cnt::where('key', 'jibit_counter')->first(); if(empty($counter)){ $counter = new Cnt(); $counter->key = "jibit_counter"; $counter->value = 0; $counter->save(); } $apiData['refresh'] = Http::withoutVerifying()->withoutVerifying()->withToken(Storage::get('refreshTokenGibit')) ->post("https://napi.jibit.ir/ide/v1/tokens/generate", ['apiKey' => "J5WxrPxPOg", 'secretKey' => "7dgeE70xQXsQL7r36j3wif6X9",])->json(); Storage::put('accessTokenGibit', $apiData['refresh']['accessToken']); if (!Storage::exists('accessTokenGibit') || !Storage::exists('refreshTokenGibit')) { Storage::put('accessTokenGibit', 'توکن اولیه'); Storage::put('refreshTokenGibit', 'توکن رفرش اولیه'); } $apiData['cartMatching'] = Http::withoutVerifying()->withToken(Storage::get('accessTokenGibit'))->get("https://napi.jibit.ir/ide/v1/services/matching?cardNumber=$p1&nationalCode=$p2&birthDate=$p3")->json(); if ((isset($apiData['cartMatching']['code'])) and $apiData['cartMatching']['code'] == "forbidden") { $apiData['refresh'] = Http::withoutVerifying()->withToken(Storage::get('refreshTokenGibit')) ->post("https://napi.jibit.ir/ide/v1/tokens/generate", ['apiKey' => "J5WxrPxPOg", 'secretKey' => "7dgeE70xQXsQL7r36j3wif6X9",])->json(); Storage::put('accessTokenGibit', $apiData['refresh']['accessToken']); Storage::put('refreshTokenGibit', $apiData['refresh']['refreshToken']); $apiData['cartMatching'] = Http::withoutVerifying()->withToken(Storage::get('accessTokenGibit'))->get("https://napi.jibit.ir/ide/v1/services/matching?cardNumber=$p1&nationalCode=$p2&birthDate=$p3")->json(); } //update jibit counter $counter->value = (int)$counter->value + 1; $counter->save(); if (isset( $apiData['cartMatching']['matched']) and $apiData['cartMatching']['matched'] == true) { $apiData['cartData'] = Http::withoutVerifying()->withToken(Storage::get('accessTokenGibit'))->get("https://napi.jibit.ir/ide/v1/cards?number=$p1&iban=true")->json(); return $apiData; }elseif(isset( $apiData['cartMatching']['matched']) and $apiData['cartMatching']['matched'] == false){ return $apiData; } else { throw new UnprocessableEntityHttpException($apiData['cartMatching']['message']); } } }