doge пре 1 час
родитељ
комит
0eefe6ef27

+ 17 - 0
app/Http/Controllers/admin/RechargeChannel.php

@@ -16,6 +16,23 @@ use App\Constants\HttpStatus;
 
 class RechargeChannel extends Controller
 {
+    public function logoOptions(\App\Services\Payment\RechargeChannelLogoService $service)
+    {
+        return $this->logoResponse(fn () => $service->options());
+    }
+
+    public function saveLogo(\App\Services\Payment\RechargeChannelLogoService $service)
+    {
+        return $this->logoResponse(fn () => $service->save(request()->all()));
+    }
+
+    private function logoResponse(callable $callback)
+    {
+        try { return $this->success($callback()); }
+        catch (ValidationException $e) { return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first()); }
+        catch (\Throwable $e) { \Illuminate\Support\Facades\Log::error('recharge_channel_logo_failed', ['exception' => $e]); return $this->error(HttpStatus::CUSTOM_ERROR, '充值Logo操作失败,请稍后重试'); }
+    }
+
     public function delete(RechargeChannelConfigurationService $service)
     {
         try {

+ 5 - 1
app/Models/RechargeChannel.php

@@ -93,6 +93,7 @@ class RechargeChannel extends BaseModel
         if ((int)$data_type === 1) $fields[] = 'logo';
         $product = $query->orderBy('sort', 'asc')->orderBy('id')->select($fields)->get()->toArray();
         
+        $logos = (int)$data_type === 1 ? \App\Services\Payment\RechargeChannelLogoService::configured(array_column($product, 'type')) : [];
         $list = [];
         foreach($product as $key => $pv) {
             if (isset($list[$pv['type']]['config'])) {
@@ -124,7 +125,10 @@ class RechargeChannel extends BaseModel
                 'value' => $pv['type'],
                 'config' => $config ?? [],
             ];
-            if ((int)$data_type === 1) $list[$pv['type']]['logo'] = (string)($config['logo'] ?? '');
+            if ((int)$data_type === 1) {
+                $logo = array_key_exists($pv['type'], $logos) ? $logos[$pv['type']] : ($config['logo'] ?? '');
+                $list[$pv['type']]['logo'] = $logo ? (string)Util::ensureUrl($logo) : '';
+            }
         }
         $list = array_column($list, null, 'key');
     

+ 4 - 14
app/Services/Payment/RechargeChannelConfigurationService.php

@@ -104,18 +104,7 @@ class RechargeChannelConfigurationService
 
     public function save(array $params): RechargeChannel
     {
-        if (array_key_exists('logo', $params)) {
-            \Illuminate\Support\Facades\Validator::make($params, ['logo' => ['nullable', 'string', 'max:2048']])->validate();
-            $logo = trim((string)($params['logo'] ?? ''));
-            $parts = parse_url($logo);
-            $relative = str_starts_with($logo, '/') && !str_starts_with($logo, '//');
-            $absolute = filter_var($logo, FILTER_VALIDATE_URL) && $parts && in_array(strtolower($parts['scheme'] ?? ''), ['http', 'https'], true)
-                && !isset($parts['user']) && !isset($parts['pass']);
-            if ($logo !== '' && (preg_match('/[\x00-\x20\x7f\\\\]/', $logo) || (!$relative && !$absolute))) {
-                throw \Illuminate\Validation\ValidationException::withMessages(['logo' => '请使用上传后的图片路径或HTTP/HTTPS图片地址']);
-            }
-            $params['logo'] = $logo === '' ? null : $logo;
-        }
+        if (array_key_exists('logo', $params)) $params['logo'] = RechargeChannelLogoService::normalize($params['logo']);
         return DB::transaction(function () use ($params) {
             $id = (int)($params['id'] ?? 0);
             unset($params['id']);
@@ -142,13 +131,14 @@ class RechargeChannelConfigurationService
             }
             if (!$channel->exists) $params += ['status' => 1, 'sort' => 0];
             if ($identityChanged && (int)$params['data_type'] === 1 && !array_key_exists('logo', $params)) {
+                $logos = RechargeChannelLogoService::configured([$params['type']]);
                 $peer = RechargeChannel::where('data_type', 1)->where('type', $params['type'])->orderBy('sort')->orderBy('id')->first();
-                $params['logo'] = $peer ? $peer->getRawOriginal('logo') : null;
+                $params['logo'] = array_key_exists($params['type'], $logos) ? $logos[$params['type']] : ($peer ? $peer->getRawOriginal('logo') : null);
             }
             $channel->fill($params);
             $channel->save();
             if ((int)$channel->data_type === 1 && array_key_exists('logo', $params)) {
-                RechargeChannel::where('data_type', 1)->where('type', $channel->type)->update(['logo' => $params['logo']]);
+                RechargeChannelLogoService::store((string)$channel->type, $params['logo']);
             }
             return $channel;
         }, 3);

+ 83 - 0
app/Services/Payment/RechargeChannelLogoService.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace App\Services\Payment;
+
+use App\Constants\Util;
+use App\Models\RechargeChannel;
+use App\Services\OperationAuditService;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Validation\ValidationException;
+
+final class RechargeChannelLogoService
+{
+    public static function normalize($logo): ?string
+    {
+        Validator::make(['logo' => $logo], ['logo' => ['nullable', 'string', 'max:2048']])->validate();
+        $logo = trim((string)($logo ?? '')); $parts = parse_url($logo);
+        $relative = str_starts_with($logo, '/') && !str_starts_with($logo, '//');
+        $absolute = filter_var($logo, FILTER_VALIDATE_URL) && $parts && in_array(strtolower($parts['scheme'] ?? ''), ['http', 'https'], true)
+            && !isset($parts['user']) && !isset($parts['pass']);
+        if ($logo !== '' && (preg_match('/[\x00-\x20\x7f\\\\]/', $logo) || (!$relative && !$absolute))) {
+            throw ValidationException::withMessages(['logo' => '请使用上传后的图片路径或HTTP/HTTPS图片地址']);
+        }
+        return $logo === '' ? null : $logo;
+    }
+
+    public static function configured(array $types): array
+    {
+        if (!Schema::hasTable('recharge_method_logos')) return [];
+        return DB::table('recharge_method_logos')->whereIn('channel_type', $types)->pluck('logo', 'channel_type')->all();
+    }
+
+    public function options(): array
+    {
+        $rows = [];
+        foreach (PaymentProviderCatalog::channelFormOptions(1)['types'] as $type) {
+            $rows[$type['value']] = ['channel_type' => $type['value'],
+                'channel_name' => ($type['payment_company_label'] === '' ? '' : $type['payment_company_label'] . ' / ') . $type['label'], 'logo' => ''];
+        }
+        $fallback = [];
+        foreach (RechargeChannel::where('data_type', 1)->orderBy('sort')->orderBy('id')->get() as $channel) {
+            if (!isset($rows[$channel->type])) $rows[$channel->type] = ['channel_type' => $channel->type, 'channel_name' => $channel->name, 'logo' => ''];
+            if (!isset($fallback[$channel->type]) && $channel->logo !== '') $fallback[$channel->type] = $channel->logo;
+        }
+        $configured = self::configured(array_keys($rows));
+        foreach ($rows as $type => &$row) {
+            $logo = array_key_exists($type, $configured) ? $configured[$type] : ($fallback[$type] ?? '');
+            $row['logo'] = $logo ? (string)Util::ensureUrl($logo) : '';
+        }
+        unset($row);
+        return ['total' => count($rows), 'data' => array_values($rows)];
+    }
+
+    public static function store(string $type, ?string $logo): void
+    {
+        if (Schema::hasTable('recharge_method_logos')) {
+            $before = DB::table('recharge_method_logos')->where('channel_type', $type)->first();
+            DB::table('recharge_method_logos')->updateOrInsert(['channel_type' => $type],
+                ['logo' => $logo, 'created_at' => $before->created_at ?? now(), 'updated_at' => now()]);
+        }
+        RechargeChannel::where('data_type', 1)->where('type', $type)->update(['logo' => $logo]);
+    }
+
+    public function save(array $input): array
+    {
+        Validator::make($input, ['channel_type' => ['required', 'string', 'max:100'], 'logo' => ['present', 'nullable', 'string', 'max:2048']])->validate();
+        $types = array_column(PaymentProviderCatalog::channelFormOptions(1)['types'], 'value');
+        $type = $input['channel_type'];
+        if (!in_array($type, $types, true) && !RechargeChannel::where('data_type', 1)->where('type', $type)->exists()) {
+            throw ValidationException::withMessages(['channel_type' => '充值方式不存在']);
+        }
+        if (!Schema::hasTable('recharge_method_logos')) throw ValidationException::withMessages(['config' => '充值Logo配置未初始化,请先完成系统更新']);
+        $logo = self::normalize($input['logo']);
+        return DB::transaction(function () use ($type, $logo) {
+            $before = DB::table('recharge_method_logos')->where('channel_type', $type)->lockForUpdate()->first();
+            self::store($type, $logo);
+            $result = ['channel_type' => $type, 'logo' => $logo ? (string)Util::ensureUrl($logo) : ''];
+            OperationAuditService::record('recharge_channel_logo', null, 'save', (array)$before, $result);
+            return $result;
+        }, 3);
+    }
+}

+ 22 - 0
database/migrations/2026_10_28_160000_create_recharge_method_logos.php

@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        Schema::create('recharge_method_logos', function (Blueprint $t) {
+            $t->string('channel_type', 100)->primary(); $t->string('logo', 2048)->nullable(); $t->timestamps();
+        });
+        if (Schema::hasColumn('recharge_channel', 'logo')) {
+            foreach (DB::table('recharge_channel')->where('data_type', 1)->whereNull('deleted_at')->whereNotNull('logo')->where('logo', '<>', '')->orderBy('sort')->orderBy('id')->get() as $channel) {
+                DB::table('recharge_method_logos')->insertOrIgnore(['channel_type' => $channel->type, 'logo' => $channel->logo,
+                    'created_at' => now(), 'updated_at' => now()]);
+            }
+        }
+    }
+    public function down(): void { Schema::dropIfExists('recharge_method_logos'); }
+};

+ 18 - 12
docs/前端接口/充值渠道Logo-前端接入.md

@@ -1,23 +1,29 @@
 # 充值渠道Logo
 
-在现有充值通道编辑表单增加可选的Logo上传项。
+Logo管理使用以下专用接口:
 
-| 接口 | 用法 |
-| --- | --- |
-| POST `/admin/upload/uploadFile` | 上传图片,multipart字段 `image`,使用返回的 `data.path` |
-| POST `/admin/rechargeChannel/update` | 按原表单提交,额外增加 `logo` 字段 |
-| GET `/api/wallet/channel` | 每个充值方式的 `data.list[].logo` 为Logo完整地址 |
+| 方法 | 接口 | 用途 |
+| --- | --- | --- |
+| GET | `/admin/rechargeChannel/logoOptions` | 完整充值方式及当前Logo,不分页 |
+| POST | `/admin/rechargeChannel/saveLogo` | 保存某个充值方式的Logo |
+| POST | `/admin/upload/uploadFile` | 上传图片,multipart字段 `image`,使用返回的 `data.path` |
 
-上传支持jpg/jpeg/png/webp,最大10MB。
+选项读取 `data.data[]`,下拉框文字用 `channel_name`,选中值用 `channel_type`:
 
-保存时新增字段示例,其他原必填字段仍照常提交:
+```json
+{"channel_type":"wxsm","channel_name":"三斤支付 / 微信扫码","logo":""}
+```
+
+选项包含三斤全部充值方式、JD、NO、808、USDT、人工充值及已有其他方式,不受会员分组、通道或支付公司开关限制。不要用分页通道列表或前台可用渠道列表替代。
+
+上传图片后保存,只提交两个字段:
 
 ```json
-{"logo":"/storage/images/channel.png"}
+{"channel_type":"wxsm","logo":"/storage/images/wechat.png"}
 ```
 
-后台原通道列表返回的 `logo` 用于编辑回填。省略时保留原Logo,传空字符串或null清除。
+成功 `code=0`,失败展示返回的 `msg`。上传支持jpg/jpeg/png/webp,最大10MB。保存时传空字符串或null可清除Logo。
 
-同一充值方式的多条底层通道共用Logo,修改任意一条会同步;新增同方式通道时自动沿用已有Logo。
+同一充值方式共用Logo,底层通道未创建时也可提前配置。
 
-前台直接使用 `item.logo` 展示图片,未上传时为 `""`,沿用原默认图标。Logo与赠送开关独立。
+前台原 `GET /api/wallet/channel` 使用每项的 `data.list[].logo`,返回完整图片地址。未配置时为 `""`,沿用原默认图标。

+ 12 - 0
docs/后端/充值渠道Logo-部署说明.md

@@ -11,3 +11,15 @@ php artisan migrate --path=database/migrations/2026_10_28_150000_add_recharge_ch
 Logo保存复用原通道更新接口和权限,不新增菜单。相同data_type=1和type的有效通道共用Logo,提现同名type不会一起更新。原赠送配置接口不增加Logo字段。
 
 尚未执行运行环境迁移、图片上传或部署。
+
+## 完整Logo选项
+
+在上述Logo字段迁移已执行后,再执行:
+
+```bash
+php artisan migrate --path=database/migrations/2026_10_28_160000_create_recharge_method_logos.php --force
+```
+
+新增按channel_type保存的独立Logo表,迁移会保留当前已上传的Logo。专用logoOptions接口使用支付方式目录与现有充值方式的并集,不按分页、会员分组、通道状态或支付公司状态过滤;saveLogo允许在创建底层通道前保存图片,不会自动创建或开启支付通道。
+
+读取复用admin/rechargeChannel/list权限,保存复用admin/rechargeChannel/update权限。原通道编辑保存Logo仍兼容,并同步独立配置。

+ 2 - 0
routes/admin.php

@@ -401,6 +401,8 @@ Route::middleware(['admin.jwt'])->group(function () {
         });
 
         Route::prefix('/rechargeChannel')->group(function () {
+            Route::get('/logoOptions', [RechargeChannel::class, 'logoOptions'])->defaults('permission_uri', 'admin/rechargeChannel/list');
+            Route::post('/saveLogo', [RechargeChannel::class, 'saveLogo'])->defaults('permission_uri', 'admin/rechargeChannel/update');
             Route::post('/delete', [RechargeChannel::class, 'delete']);
             Route::get("/options", [RechargeChannel::class, 'options']);
             Route::get("/list", [RechargeChannel::class, 'list']);

+ 52 - 0
tests/Integration/PaymentChannelOptionsTest.php

@@ -67,6 +67,7 @@ class PaymentChannelOptionsTest extends TestCase
         (require dirname(__DIR__, 2) . '/database/migrations/2026_09_08_160000_add_payment_provider_status_and_channel_soft_deletes.php')->up();
         (require dirname(__DIR__, 2) . '/database/migrations/2026_09_08_170000_add_soft_deletes_to_recharge_channel_groups.php')->up();
         (require dirname(__DIR__, 2) . '/database/migrations/2026_10_28_150000_add_recharge_channel_logo.php')->up();
+        (require dirname(__DIR__, 2) . '/database/migrations/2026_10_28_160000_create_recharge_method_logos.php')->up();
         $db->schema()->create('operation_audits', function (Blueprint $t) {
             $t->id(); $t->string('resource_type'); $t->unsignedBigInteger('resource_id')->nullable();
             $t->string('action'); $t->json('changes'); $t->unsignedBigInteger('operator_id')->nullable();
@@ -94,6 +95,57 @@ class PaymentChannelOptionsTest extends TestCase
         return $response['data'];
     }
 
+    public function test_logo_options_include_every_payment_method_even_without_enabled_or_existing_channels(): void
+    {
+        $this->request('/admin/rechargeChannel/saveLogo', []);
+        $service = new \App\Services\Payment\RechargeChannelLogoService();
+        DB::table('recharge_channel')->where('data_type', 1)->update(['status' => 0]);
+        DB::table('payment_providers')->update(['status' => 0]);
+        $options = $service->options();
+        $this->assertSame(13, $options['total']);
+        $rows = array_column($options['data'], null, 'channel_type');
+        foreach (['wxsm','ylsm','szrmb','zfbsm','zfbge','zfbzk','sdjt','JDpay','NOpay12','NOpay13','ZIMUpay','usdt','rgcz'] as $type) {
+            $this->assertArrayHasKey($type, $rows);
+        }
+        $this->assertSame('三斤支付 / 微信扫码', $rows['wxsm']['channel_name']);
+        DB::table('recharge_channel')->delete(); DB::table('recharge_channel_group')->delete();
+        $this->assertSame(13, $service->options()['total']);
+        $result = $service->save(['channel_type' => 'wxsm', 'logo' => 'https://cdn.example.test/wx.png']);
+        $this->assertSame('https://cdn.example.test/wx.png', $result['logo']);
+        $this->assertSame(0, DB::table('recharge_channel')->count());
+        $this->assertSame('https://cdn.example.test/wx.png', array_column($service->options()['data'], null, 'channel_type')['wxsm']['logo']);
+        $this->assertSame('', $service->save(['channel_type' => 'wxsm', 'logo' => null])['logo']);
+        $this->request('/admin/rechargeChannel/logoOptions', []);
+        $this->assertSame(13, (new RechargeChannel())->logoOptions($service)->getData(true)['data']['total']);
+        $router = new \Illuminate\Routing\Router(new \Illuminate\Events\Dispatcher($this->app), $this->app);
+        $this->app->instance('router', $router);
+        $router->group(['prefix' => 'admin'], function () { require dirname(__DIR__, 2) . '/routes/admin.php'; });
+        foreach (['logoOptions' => ['GET', 'admin/rechargeChannel/list'], 'saveLogo' => ['POST', 'admin/rechargeChannel/update']] as $path => [$method, $permission]) {
+            $route = $router->getRoutes()->match(Request::create('/admin/rechargeChannel/' . $path, $method));
+            $this->assertSame($permission, $route->defaults['permission_uri']);
+            $this->assertContains('admin.jwt', $route->middleware()); $this->assertContains('check.button.uri', $route->middleware());
+        }
+
+        try { $service->save(['channel_type' => 'DF001', 'logo' => '/a.png']); $this->fail('Withdrawal-only method accepted'); }
+        catch (\Illuminate\Validation\ValidationException $e) { $this->assertArrayHasKey('channel_type', $e->errors()); }
+    }
+
+    public function test_logo_preconfiguration_is_inherited_by_later_channels_and_migration_retains_old_uploads(): void
+    {
+        $this->request('/admin/rechargeChannel/saveLogo', []);
+        $migration = require dirname(__DIR__, 2) . '/database/migrations/2026_10_28_160000_create_recharge_method_logos.php';
+        $migration->down();
+        DB::table('recharge_channel')->where('id', 2)->update(['logo' => '/storage/wx.png']);
+        $migration->up();
+        $this->assertSame('/storage/wx.png', DB::table('recharge_method_logos')->where('channel_type', 'wxsm')->value('logo'));
+        DB::table('recharge_channel')->where('type', 'JDpay')->where('data_type', 1)->delete();
+        $service = new \App\Services\Payment\RechargeChannelLogoService();
+        $service->save(['channel_type' => 'JDpay', 'logo' => 'https://cdn.example.test/jd.png']);
+        $body = $this->saveChannel($this->newChannel(['payment_company' => 'jd', 'type' => 'JDpay']));
+        $this->assertSame(0, $body['code'], $body['msg']);
+        $this->assertSame('https://cdn.example.test/jd.png', \App\Models\RechargeChannel::where('data_type', 1)->where('type', 'JDpay')->first()->logo);
+    }
+
     public function test_deposit_logos_upload_save_share_by_method_and_can_be_cleared(): void
     {
         $this->app['config']->set('app.url', 'https://admin.example.test');

+ 8 - 1
tests/Integration/RechargeBonusTest.php

@@ -48,7 +48,7 @@ class RechargeBonusTest extends TestCase
             'balance_logs' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,member_id TEXT,type INTEGER DEFAULT 1,amount TEXT,change_type TEXT,before_balance TEXT,after_balance TEXT,related_id INTEGER,remark TEXT,frozen_status INTEGER,created_at TEXT,updated_at TEXT',
             'payment_orders' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,member_id TEXT,type INTEGER,channel TEXT,status INTEGER,state TEXT,amount TEXT,order_no TEXT,callback_data TEXT,remark TEXT,created_at TEXT,updated_at TEXT',
             'recharges' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,member_id TEXT,type INTEGER,status INTEGER DEFAULT 0,coin TEXT,net TEXT,txid TEXT,amount TEXT,exchange_rate TEXT,created_at TEXT,updated_at TEXT',
-            'recharge_channel' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,data_type INTEGER,type TEXT,name TEXT,sort INTEGER DEFAULT 0,status INTEGER DEFAULT 1,deleted_at TEXT,min TEXT,max TEXT,fixed TEXT,rate TEXT DEFAULT "0.02"',
+            'recharge_channel' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,data_type INTEGER,type TEXT,name TEXT,sort INTEGER DEFAULT 0,status INTEGER DEFAULT 1,deleted_at TEXT,min TEXT,max TEXT,fixed TEXT,rate TEXT DEFAULT "0.02",created_at TEXT,updated_at TEXT',
             'recharge_channel_group' => 'id INTEGER PRIMARY KEY,activity_type TEXT,recharge_type TEXT,withdraw_type TEXT,deleted_at TEXT',
             'payment_providers' => 'id INTEGER PRIMARY KEY,code TEXT,status INTEGER',
             'operation_audits' => 'id INTEGER PRIMARY KEY AUTOINCREMENT,resource_type TEXT,resource_id INTEGER,action TEXT,changes TEXT,operator_id INTEGER,operator_name TEXT,created_at TEXT',
@@ -61,6 +61,7 @@ class RechargeBonusTest extends TestCase
         (require dirname(__DIR__, 2) . '/database/migrations/2026_10_27_140000_add_promotion_activity_translations.php')->up();
         (require dirname(__DIR__, 2) . '/database/migrations/2026_10_28_140000_create_recharge_bonuses.php')->up();
         (require dirname(__DIR__, 2) . '/database/migrations/2026_10_28_150000_add_recharge_channel_logo.php')->up();
+        (require dirname(__DIR__, 2) . '/database/migrations/2026_10_28_160000_create_recharge_method_logos.php')->up();
         DB::table('recharge_channel_group')->insert(['id' => 1, 'activity_type' => '', 'recharge_type' => 'JDpay,rgcz,usdt']);
         foreach (['JDpay', 'rgcz', 'usdt'] as $type) DB::table('recharge_channel')->insert(['data_type' => 1, 'type' => $type, 'name' => $type]);
         foreach (['huishui_percentage' => '0', 'huishui_restriction' => '0', 'recharge_rate' => '0', 'recharges_bibi_return' => '0'] as $field => $val) DB::table('config')->insert(compact('field', 'val'));
@@ -197,6 +198,12 @@ class RechargeBonusTest extends TestCase
         $this->assertSame('5', $methods['JDpay']['bonus_percent']);
         $this->assertSame('https://admin.example.test/storage/images/jd.png', $methods['JDpay']['logo']);
         $this->assertSame('', $methods['usdt']['logo']);
+        (new \App\Services\Payment\RechargeChannelLogoService())->save(['channel_type' => 'JDpay', 'logo' => '/storage/images/shared.png']);
+        $updated = (new \App\Http\Controllers\api\Wallet())->getChannel()->getData(true);
+        $this->assertSame('https://admin.example.test/storage/images/shared.png', array_column($updated['data']['list'], null, 'value')['JDpay']['logo']);
+        (new \App\Services\Payment\RechargeChannelLogoService())->save(['channel_type' => 'JDpay', 'logo' => null]);
+        $cleared = (new \App\Http\Controllers\api\Wallet())->getChannel()->getData(true);
+        $this->assertSame('', array_column($cleared['data']['list'], null, 'value')['JDpay']['logo']);
         $this->assertSame('0.02', $methods['JDpay']['config']['rate']);
         $this->assertArrayNotHasKey('bonus_percent', \App\Models\RechargeChannel::first()->toArray());
         $request->user = \App\Models\User::find(2);