Enable gated multi-level referral profit and harden chat/SMS notify paths.

Honor enable_customer_level_profit for order referral payouts, and tighten support-chat webhook/SMS plus SuperAdmin SMS panel sync settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-10 16:48:53 +03:30
co-authored by Cursor
parent f421fcb83f
commit 279e4bc881
8 changed files with 277 additions and 110 deletions
@@ -82,6 +82,10 @@ class GiftCardController extends Controller
throw new UnprocessableEntityHttpException('auth failed!');
}
if (!app(ConfigService::class)->getBool('has_page_gift_card', false)) {
throw new UnprocessableEntityHttpException('قابلیت کارت هدیه غیرفعال است.');
}
$redeem = $request->code;
$giftcode = GiftCardCodes::where('code', $redeem)->first();
$gift = GiftCard::find($giftcode->giftcard_id);
@@ -144,6 +144,7 @@ class SmsPanelController extends Controller
/**
* خلاصه سبک کیف پول برای نمایش «اعتبار پیامک» در هدر؛ برای همه ادمین‌ها.
* اگر سوپرادمین قطع باشد، روی آینهٔ محلی برمی‌گردیم — نه 502 (ویجت هدر نباید بترکد).
*/
public function walletSummary()
{
@@ -159,24 +160,39 @@ class SmsPanelController extends Controller
if ($this->useSuperAdmin()) {
$balanceData = $this->sa()->balance();
$charge = $this->sa()->chargeInfo();
if ($balanceData === null || $charge === null) {
$this->abortSaUnavailable();
if ($balanceData !== null) {
$deposit = is_array($charge)
? $this->depositFromChargeInfo($charge)
: SmsWalletService::getDepositInfo();
$this->mirrorLocalFromSa(array_merge($balanceData, [
'charge_info' => is_array($charge) ? $charge : [],
'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,
]);
}
$this->mirrorLocalFromSa(array_merge($balanceData, [
'charge_info' => $charge,
'deposit' => $this->depositFromChargeInfo($charge),
]));
return response()->json([
'enabled' => true,
'balance' => (int) ($balanceData['balance'] ?? 0),
'is_blocked' => (bool) ($balanceData['is_blocked'] ?? false),
'deposit' => $this->depositFromChargeInfo($charge),
'source' => 'superadmin',
]);
// 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;
@@ -186,12 +202,13 @@ class SmsPanelController extends Controller
$isBlocked = (bool) $wallet->is_blocked;
}
return response()->json([
return [
'enabled' => true,
'balance' => $balance,
'is_blocked' => $isBlocked,
'deposit' => SmsWalletService::getDepositInfo(),
]);
'source' => $source,
];
}
/**
@@ -134,16 +134,18 @@ class SupportChatNotifyController extends Controller
/**
* ثبت/تازه‌سازی وب‌هوک روی ah_chat (از پنل ادمین).
* همیشه HTTP 200 — شکست ثبت best-effort است و نباید toast خطای 422 بسازد.
*/
public function ensureWebhook()
{
$service = app(ChatWebhookService::class);
$ok = $service->ensureRegistered();
$result = app(ChatWebhookService::class)->ensureRegisteredDetailed();
return response()->json([
'ok' => $ok,
'webhook_url' => $service->webhookUrl(),
'message' => $ok ? 'webhook registered' : 'webhook register skipped or failed',
], $ok ? 200 : 422);
'ok' => (bool) ($result['ok'] ?? false),
'webhook_url' => (string) ($result['webhook_url'] ?? ''),
'reason' => (string) ($result['reason'] ?? ''),
'message' => (string) ($result['message'] ?? ''),
'attempts' => (int) ($result['attempts'] ?? 0),
]);
}
}
@@ -259,7 +259,7 @@ class SingleOrderController extends Controller
report($e);
}
}
// $response['reward'] = $this->give_order_reward($data, $wallet, $wallet_interface);
$response['reward'] = $this->give_order_reward($data, $wallet, $wallet_interface);
if ((int) $data['direction'] === 1 && $customer) {
$referrer = Customer::where('referral_id', $customer->referral_to_id)->first();
if (!empty($referrer) && $referrer->first_buy_order == 1 && $customer->first_buy_order !== 1) {
@@ -810,64 +810,97 @@ class SingleOrderController extends Controller
private function give_order_reward(&$data, $wallet, $wallet_interface)
{
if (!app(\App\Services\ConfigService::class)->getBool('enable_customer_level_profit', false)) {
return $data;
}
$serviceCharge = (float) ($data['order']['service_charge'] ?? 0);
if ($serviceCharge <= 0) {
return $data;
}
$data['aCustomers'] = [];
$i = 0;
$data['aCustomers'][$i] = Customer::findOrFail($data['customer_id']);
while ($data['aCustomers'][$i + 1] = Customer::where('referral_id', $data['aCustomers'][$i]->referral_to_id)->first()) {
if ($data['aCustomers'][$i + 1]->profit_amount_by_lvl) {
$levels = json_decode(str_replace(['{', '}'], ['[', ']'], $data['aCustomers'][$i + 1]->profit_amount_by_lvl), true);
foreach ($levels as $level) {
if ($level[0] == $i + 1) {
// [$level[1],$data['aCustomers'][$i+1]];
if ($this->Config('has_page_earn_money') == true) {
$gift_for_invite = ($data['order']['service_charge'] * $level[1]) / 100;
$profit_receiver = $data['aCustomers'][$i + 1]->id;
$profit_sender = $data['aCustomers'][0]->id;
$Withdraw = Withdraw::create([
'user_id' => $profit_sender,
'customer_id' => $profit_receiver,
"cash" => 0,
"gold" => $gift_for_invite,
'sts' => 9,
]);
$wallet_interface = new WalletInterface($profit_receiver, 2);
new TransactionInterface($wallet_interface, $_inp = [
"cash" => 0,
"gold" => $gift_for_invite,
'expire_at' => now(),
'active' => true,
'withdraw_id' => $Withdraw->id,
'start_at' => now(),
], 9, true, false);
$transaction = Transaction::where('customer_id', $profit_receiver)->latest()->first();
TransactionsAdmin::create([
'customer_id' => $profit_sender,
'customer_to_id' => $profit_receiver,
'user_id' => $profit_sender,
'order_id' => $data['order']['id'],
'wallet' => null, // یا مقدار مناسب دیگری
'transaction_id' => $transaction->id,
'cash' => 0,
'note' => ' سود بابت سفارش',
'type' => 1, // یا مقدار مناسب دیگری
'gold' => $gift_for_invite,
'balance_cash' => 0, // یا مقدار مناسب دیگری
'balance_gold' => $gift_for_invite, // مقدار صحیح برای `balance_gold`
'withdraw_id' => $Withdraw->id,
]);
}
}
$levels = $this->parseProfitAmountByLevel($data['aCustomers'][$i + 1]->profit_amount_by_lvl);
foreach ($levels as $levelRow) {
$levelNo = (int) ($levelRow['level'] ?? $levelRow[0] ?? 0);
$percent = (float) ($levelRow['present'] ?? $levelRow[1] ?? 0);
if ($levelNo !== ($i + 1) || $percent <= 0) {
continue;
}
$gift_for_invite = ($serviceCharge * $percent) / 100;
if ($gift_for_invite <= 0) {
continue;
}
$profit_receiver = $data['aCustomers'][$i + 1]->id;
$profit_sender = $data['aCustomers'][0]->id;
$Withdraw = Withdraw::create([
'user_id' => $profit_sender,
'customer_id' => $profit_receiver,
'cash' => 0,
'gold' => $gift_for_invite,
'sts' => 9,
]);
$wallet_interface = new WalletInterface($profit_receiver, 2);
new TransactionInterface($wallet_interface, [
'cash' => 0,
'gold' => $gift_for_invite,
'expire_at' => now(),
'active' => true,
'withdraw_id' => $Withdraw->id,
'start_at' => now(),
], 9, true, false);
$transaction = Transaction::where('customer_id', $profit_receiver)->latest()->first();
TransactionsAdmin::create([
'customer_id' => $profit_sender,
'customer_to_id' => $profit_receiver,
'user_id' => $profit_sender,
'order_id' => $data['order']['id'],
'wallet' => null,
'transaction_id' => $transaction->id,
'cash' => 0,
'note' => ' سود بابت سفارش',
'type' => 1,
'gold' => $gift_for_invite,
'balance_cash' => 0,
'balance_gold' => $gift_for_invite,
'withdraw_id' => $Withdraw->id,
]);
}
if ($i == 100) break;
$i++; // برای جلوگیری از حلقه بی‌نهایت
if ($i == 100) {
break;
}
$i++;
}
return $data;
}
/** @return list<array{level?:int,present?:float}|array{0:int,1:float}> */
private function parseProfitAmountByLevel(mixed $raw): array
{
if (is_array($raw)) {
return $raw;
}
if (!is_string($raw) || trim($raw) === '') {
return [];
}
$decoded = json_decode($raw, true);
if (is_array($decoded)) {
return $decoded;
}
// فرمت قدیمی: رشته شبیه {1,5},{2,2}
$legacy = json_decode(str_replace(['{', '}'], ['[', ']'], $raw), true);
return is_array($legacy) ? $legacy : [];
}
private function sendTransactionNotifications(array $data, string $transactionType)
{
$customer = Customer::findOrFail($data['customer_id']);
@@ -880,8 +913,9 @@ class SingleOrderController extends Controller
$totalPrice = $data['total_price'] ?? ($data['payment_amount'] ?? 0);
$subjectTitle = $this->transactionSubjectTitle($data);
$amountLabel = self::formatSmsGoldWeight($amount);
$message = "کاربر {$customer->name} با شماره موبایل {$customer->mobile} اقدام به $transactionType {$subjectTitle} به مقدار " .
"{$amount} گرم به مبلغ " . number_format($totalPrice) .
"{$amountLabel} گرم به مبلغ " . number_format($totalPrice) .
" ریال کرد. با تشکر، $companyName";
if (self::isSmsNotifEnabled('sms_notif_order_admin')) {
User::where('send_sms', 1)->get()->each(function ($admin) use ($message) {
@@ -895,9 +929,10 @@ class SingleOrderController extends Controller
$balanceLabel = self::metalTypeLabel();
$goldBalance = $wallet ? ($wallet->gold ?? 0) : 0;
$cashBalance = $wallet ? ($wallet->cash ?? 0) : 0;
$customerMessage = "$companyName\n$transactionType {$subjectTitle}\nگرم: {$amount}\n" .
$goldBalanceLabel = self::formatSmsGoldWeight($goldBalance);
$customerMessage = "$companyName\n$transactionType {$subjectTitle}\nگرم: {$amountLabel}\n" .
"مبلغ کل: " . number_format($totalPrice) . "\n\n" .
"موجودی {$balanceLabel}: {$goldBalance}\n" .
"موجودی {$balanceLabel}: {$goldBalanceLabel}\n" .
"مانده ریالی: " . number_format($cashBalance) . "\n" .
self::publicSiteUrl(request());
@@ -1036,7 +1071,7 @@ class SingleOrderController extends Controller
$userMobile = $customer->mobile; // User's mobile number
$userName = $customer->name; // User's name
$chargedAmount = $data['total_price']; // Total price in Rial
$chargedAmountGold = $data['amount']; // Amount of gold
$chargedAmountGold = self::formatSmsGoldWeight($data['amount']); // Amount of gold
// Company name
$companyName = $this->Config('name_fa'); // Company name from configuration
@@ -1055,15 +1090,16 @@ class SingleOrderController extends Controller
->main()
->first();
$companyurl = self::publicSiteUrl(request());
$weight = $data['amount'];
$weight = (float) $data['amount'];
$weightLabel = self::formatSmsGoldWeight($weight);
$totalAmount = $data['total_price'];
$pricePerGram = $weight > 0 ? $totalAmount / $weight : 0;
$goldBalance = $mainWallet->gold ?? 0;
$goldBalance = self::formatSmsGoldWeight($mainWallet->gold ?? 0);
$remainingBalance = $mainWallet->cash ?? 0;
$message = "$companyName\n";
$message .= "خرید آبشده\n";
$message .= "گرم: $weight\n";
$message .= "گرم: $weightLabel\n";
$message .= "قیمت گرم: " . number_format($pricePerGram) . "\n";
$message .= "مبلغ کل: " . number_format($totalAmount) . "\n\n";
$message .= "موجودی طلایی: $goldBalance\n";
@@ -1110,6 +1146,28 @@ class SingleOrderController extends Controller
return null;
}
/**
* مقدار ضرب‌شونده در قیمت واحد برای مبلغ سفارش.
* قیمت فرمولی = قیمت هر عدد (وزن داخل فرمول است) → فقط تعداد.
* بقیه = تعداد × وزن واحد محصول.
*/
private function resolveProductOrderPricingAmount(Product|array|null $product, float $number): float
{
if ($product instanceof Product && FormulaPriceService::isEnabled($product)) {
return $number;
}
if (is_array($product) && ! empty($product['price_formula_enabled'])) {
return $number;
}
$unitAmount = $product instanceof Product
? (float) ($product->amount ?? 0)
: (float) (is_array($product) ? ($product['amount'] ?? 0) : 0);
return $number * $unitAmount;
}
private function assertMinimumSellCash(array $data): void
{
$minSell = Setting::where('key', 'minimum_sell')->where('is_active', 1)->first();
@@ -158,8 +158,9 @@ class RequestsController extends Controller
$message = "کاربر {$customer->name} با شماره موبایل {$customer->mobile} اقدام به برداشت موجودی از طرف $montifyCompanyName به مقدار " .
"{$data['amount']} ریال " . " کرد. با تشکر، $companyName";
}elseif ($data['method'] == 'gold') {
$amountLabel = self::formatSmsGoldWeight($data['amount']);
$message = "کاربر {$customer->name} با شماره موبایل {$customer->mobile} اقدام به $transactionType طلا به مقدار " .
"{$data['amount']} گرم به مبلغ " . number_format($data['total_price']) .
"{$amountLabel} گرم به مبلغ " . number_format($data['total_price']) .
" ریال کرد. با تشکر، $companyName";
}
User::where('send_sms', 1)->get()->each(function ($admin) use ($message, $data, $transactionType, $companyName) {
+2 -1
View File
@@ -52,7 +52,7 @@ class Customer extends Authenticatable implements JWTSubject
'credential_id',
'moshtari_code',
'pargar_account_id',
'profit_amount_by_lvl',
];
/**
* The attributes that should be hidden for arrays.
@@ -75,6 +75,7 @@ class Customer extends Authenticatable implements JWTSubject
"finger_print" => "boolean",
"conditional_order_active" => "boolean",
"conditional_order_limit" => "integer",
"profit_amount_by_lvl" => "array",
];
static $TYPE = [
1=>'همکار',
+80 -28
View File
@@ -12,62 +12,114 @@ class ChatWebhookService
{
public function ensureRegistered(): bool
{
return (bool) ($this->ensureRegisteredDetailed()['ok'] ?? false);
}
/**
* @return array{ok: bool, reason: string, message: string, webhook_url: string, attempts?: int}
*/
public function ensureRegisteredDetailed(int $maxAttempts = 3): array
{
$webhookUrl = $this->webhookUrl();
$chatApi = Controller::Config('chat_api');
$chatApiKey = Controller::Config('chat_api_key');
if (!$chatApi || !$chatApiKey) {
return false;
if (! $chatApi || ! $chatApiKey) {
return [
'ok' => false,
'reason' => 'missing_chat_config',
'message' => 'تنظیمات chat_api یا chat_api_key خالی است',
'webhook_url' => $webhookUrl,
];
}
$host = preg_replace('#^https?://#', '', rtrim((string) $chatApi, '/'));
$secret = $this->ensureSecret();
if ($host === '' || $secret === '') {
return false;
return [
'ok' => false,
'reason' => 'invalid_host_or_secret',
'message' => 'آدرس چت یا رمز وب‌هوک نامعتبر است',
'webhook_url' => $webhookUrl,
];
}
$webhookUrl = $this->webhookUrl();
if ($this->isUnreachableWebhookUrl($webhookUrl)) {
Log::warning('chat webhook register refused: unreachable/local URL', [
'webhook_url' => $webhookUrl,
'hint' => 'Set chat_webhook_base_url to public Laravel URL (e.g. https://niazitala.ir/server) or fix APP_URL',
]);
return false;
return [
'ok' => false,
'reason' => 'unreachable_webhook_url',
'message' => 'آدرس وب‌هوک محلی/غیرقابل‌دسترس است؛ chat_webhook_base_url را روی آدرس عمومی سرور بگذارید',
'webhook_url' => $webhookUrl,
];
}
try {
$response = Http::timeout(8)->post('https://'.$host.'/api/projects/set-webhook', [
'api_key' => $chatApiKey,
'webhook_url' => $webhookUrl,
'webhook_secret' => $secret,
]);
$attempts = max(1, $maxAttempts);
$lastReason = 'unknown';
$lastMessage = 'ثبت وب‌هوک ناموفق بود';
if (! $response->successful()) {
for ($i = 1; $i <= $attempts; $i++) {
try {
$response = Http::timeout(8)
->retry(0)
->post('https://'.$host.'/api/projects/set-webhook', [
'api_key' => $chatApiKey,
'webhook_url' => $webhookUrl,
'webhook_secret' => $secret,
]);
if ($response->successful()) {
Log::info('chat webhook registered', [
'host' => $host,
'webhook_url' => $webhookUrl,
'attempt' => $i,
]);
return [
'ok' => true,
'reason' => 'registered',
'message' => 'webhook registered',
'webhook_url' => $webhookUrl,
'attempts' => $i,
];
}
$lastReason = 'chat_api_http_'.$response->status();
$lastMessage = 'سرور چت ثبت وب‌هوک را رد کرد (HTTP '.$response->status().')';
Log::warning('chat webhook register non-2xx', [
'host' => $host,
'webhook_url' => $webhookUrl,
'status' => $response->status(),
'body' => mb_substr($response->body(), 0, 300),
'attempt' => $i,
]);
} catch (\Throwable $e) {
$lastReason = 'chat_api_exception';
$lastMessage = 'ارتباط با سرور چت برای ثبت وب‌هوک برقرار نشد';
Log::warning('chat webhook register failed', [
'host' => $host,
'webhook_url' => $webhookUrl,
'error' => $e->getMessage(),
'attempt' => $i,
]);
return false;
}
Log::info('chat webhook registered', [
'host' => $host,
'webhook_url' => $webhookUrl,
]);
return true;
} catch (\Throwable $e) {
Log::warning('chat webhook register failed', [
'host' => $host,
'webhook_url' => $webhookUrl,
'error' => $e->getMessage(),
]);
return false;
if ($i < $attempts) {
usleep(250000 * $i);
}
}
return [
'ok' => false,
'reason' => $lastReason,
'message' => $lastMessage,
'webhook_url' => $webhookUrl,
'attempts' => $attempts,
];
}
/**
+35 -3
View File
@@ -405,6 +405,18 @@ SELECT 'enable_product_funds', 'false', 'قلک محصول', 0, 1,
'switch'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'enable_product_funds' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'has_page_gift_card', 'false', 'کارت هدیه', 0, 1,
'روشن: منوی «ثبت کارت هدیه» و صفحه ثبت کد در اپ کاربر فعال است. خاموش: منو و صفحه مخفی است و API استفاده از کد هدیه در دسترس نیست. فقط از دیپلوی قابل تغییر است.',
'switch'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'has_page_gift_card' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'enable_customer_level_profit', 'false', 'سود چندسطحی معرف', 0, 1,
'روشن: در لیست مشتریان آیکون تنظیم درصد سود هر سطح زیرمجموعه فعال است و پس از سفارش، سهم کارمزد طبق درصدهای هر معرف به کیف طلا واریز می‌شود. خاموش: آیکون و واریز سود چندسطحی غیرفعال است. فقط از دیپلوی قابل تغییر است.',
'switch'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'enable_customer_level_profit' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'has_page_price_trigger', 'true', 'هشدار قیمت', 0, 1,
'روشن: منوی هشدار قیمت در اپ کاربر نمایش داده می‌شود. خاموش: منو و صفحه مخفی است. فقط از دیپلوی قابل تغییر است.',
@@ -773,6 +785,11 @@ SELECT 'deposit_card_number', '', 'شماره کارت واریز', 1, 1,
'شماره کارت واریز — در متن‌ها با ٪کارت یا %کارت جایگزین می‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'deposit_card_number' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'deposit_account_number', '', 'شماره حساب واریز', 1, 1,
'شماره حساب بانکی واریز — در مودال راهنمای شارژ کنار کارت و شبا نمایش داده می‌شود. در متن‌ها با ٪حساب یا %حساب جایگزین می‌شود.', 'text'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'deposit_account_number' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
SELECT 'deposit_sheba', '', 'شبا واریز', 1, 1,
'شماره شبا واریز — در متن‌ها با ٪شبا یا %شبا جایگزین می‌شود.', 'text'
@@ -1110,7 +1127,8 @@ UPDATE `settings` SET `type` = 'switch' WHERE `key` IN (
'fee_deduct_from_gold',
'require_zero_main_gold_before_other_buy',
'profit_gold_api_enabled', 'payping_round_toman', 'payping_strict_failed_withdraw',
'has_page_earn_money', 'has_page_price_trigger', 'has_page_notification', 'charge_notif', 'group_sms', 'sms_panel', 'customer_direct_sms',
'has_page_earn_money', 'has_page_price_trigger', 'has_page_notification', 'has_page_gift_card',
'enable_customer_level_profit', 'charge_notif', 'group_sms', 'sms_panel', 'customer_direct_sms',
'enable_conditional_order', 'enable_product_funds',
'sms_notif_order_admin', 'sms_notif_order_customer',
'sms_notif_withdraw_admin', 'sms_notif_withdraw_otp', 'sms_notif_login_otp',
@@ -1358,6 +1376,13 @@ UPDATE `settings` SET
`description` = 'شماره کارت واریز — در متن‌ها با ٪کارت یا %کارت جایگزین می‌شود.'
WHERE `key` = 'deposit_card_number';
UPDATE `settings` SET
`title_fa` = 'شماره حساب واریز',
`type` = 'text',
`can_edit` = 1,
`description` = 'شماره حساب بانکی واریز — در مودال راهنمای شارژ کنار کارت و شبا نمایش داده می‌شود. در متن‌ها با ٪حساب یا %حساب جایگزین می‌شود.'
WHERE `key` = 'deposit_account_number';
UPDATE `settings` SET
`title_fa` = 'شبا واریز',
`type` = 'text',
@@ -1603,6 +1628,10 @@ UPDATE `settings` SET `title_fa` = 'احراز هویت جیبیت' WHERE `key`
UPDATE `settings` SET `title_fa` = 'سفارش شرطی' WHERE `key` = 'enable_conditional_order';
UPDATE `settings` SET `title_fa` = 'قلک محصول' WHERE `key` = 'enable_product_funds';
UPDATE `settings` SET `description` = 'روشن: ساخت و مدیریت قلک per-product در اپ مشتری فعال است (یک محصول در هر قلک؛ انتقال بدون سند حسابداری). خاموش: منو و API ساخت قلک جدید در دسترس نیست. فقط از دیپلوی قابل تغییر است.' WHERE `key` = 'enable_product_funds';
UPDATE `settings` SET `title_fa` = 'کارت هدیه' WHERE `key` = 'has_page_gift_card';
UPDATE `settings` SET `description` = 'روشن: منوی «ثبت کارت هدیه» و صفحه ثبت کد در اپ کاربر فعال است. خاموش: منو و صفحه مخفی است و API استفاده از کد هدیه در دسترس نیست. فقط از دیپلوی قابل تغییر است.' WHERE `key` = 'has_page_gift_card';
UPDATE `settings` SET `title_fa` = 'سود چندسطحی معرف' WHERE `key` = 'enable_customer_level_profit';
UPDATE `settings` SET `description` = 'روشن: در لیست مشتریان آیکون تنظیم درصد سود هر سطح زیرمجموعه فعال است و پس از سفارش، سهم کارمزد طبق درصدهای هر معرف به کیف طلا واریز می‌شود. خاموش: آیکون و واریز سود چندسطحی غیرفعال است. فقط از دیپلوی قابل تغییر است.' WHERE `key` = 'enable_customer_level_profit';
UPDATE `settings` SET `description` = 'روشن: امکان ارسال پیامک گروهی از پنل ادمین فعال است.' WHERE `key` = 'group_sms';
UPDATE `settings` SET `description` = 'روشن: پنل پیامک و کیف پول billing فعال است. خاموش: پیامک‌ها مثل قبل ارسال می‌شوند بدون billing.' WHERE `key` = 'sms_panel';
@@ -1817,6 +1846,7 @@ UPDATE `settings` SET `title_fa` = 'متن راهنمای واریز' WHERE `key
UPDATE `settings` SET `title_fa` = 'متن پیام به پشتیبانی (واریز)' WHERE `key` = 'deposit_support_message_text';
UPDATE `settings` SET `title_fa` = 'نام صاحب حساب واریز' WHERE `key` = 'deposit_account_holder';
UPDATE `settings` SET `title_fa` = 'شماره کارت واریز' WHERE `key` = 'deposit_card_number';
UPDATE `settings` SET `title_fa` = 'شماره حساب واریز' WHERE `key` = 'deposit_account_number';
UPDATE `settings` SET `title_fa` = 'شبا واریز' WHERE `key` = 'deposit_sheba';
UPDATE `settings` SET `title_fa` = 'حساب بانکی ته‌حساب' WHERE `key` = 'tahesab_bank_account';
UPDATE `settings` SET `title_fa` = 'نام دیتابیس ته‌حساب' WHERE `key` = 'tahesab_db';
@@ -1863,6 +1893,8 @@ UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN (
'enable_product_funds',
'has_page_price_trigger',
'has_page_notification',
'has_page_gift_card',
'enable_customer_level_profit',
'jibit_enabled',
'has_page_earn_money',
'is_store',
@@ -1906,7 +1938,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
'sms_notif_fund_withdraw_admin', 'sms_notif_fund_withdraw_customer',
'sms_notif_price_trigger', 'sms_notif_wallet_alert', 'sms_notif_support_admin',
'deposit_sms_support_number', 'deposit_support_message_text',
'deposit_account_holder', 'deposit_card_number', 'deposit_sheba',
'deposit_account_holder', 'deposit_card_number', 'deposit_account_number', 'deposit_sheba',
'endDay',
'fee_free', 'fee_service_charge_percentage',
'gift_for_referral', 'gift_for_referrer',
@@ -1925,7 +1957,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
'startDay',
'support_messenger', 'support_messenger_id', 'support_messenger_text', 'support_messengers',
'has_chat',
'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba',
'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_account_number', 'deposit_sheba',
'text_about', 'text_rule',
'price_channel_enabled', 'price_channel_messengers', 'price_channel_diff',
'price_channel_min_seconds', 'price_channel_message_text',