mirror of
				https://github.com/v2board/v2board.git
				synced 2025-11-01 01:41:47 +08:00 
			
		
		
		
	| @@ -45,6 +45,7 @@ class CheckOrder extends Command | ||||
|     { | ||||
|         ini_set('memory_limit', -1); | ||||
|         $orders = Order::whereIn('status', [0, 1]) | ||||
|             ->orderBy('created_at', 'ASC') | ||||
|             ->get(); | ||||
|         foreach ($orders as $order) { | ||||
|             OrderHandleJob::dispatch($order->trade_no); | ||||
|   | ||||
| @@ -64,12 +64,12 @@ class ResetTraffic extends Command | ||||
|                     break; | ||||
|                 } | ||||
|                 case 0: { | ||||
|                     $builder = $this->builder->where('plan_id', $plan->id); | ||||
|                     $builder = with(clone($this->builder))->where('plan_id', $plan->id); | ||||
|                     $this->resetByMonthFirstDay($builder); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 1: { | ||||
|                     $builder = $this->builder->where('plan_id', $plan->id); | ||||
|                     $builder = with(clone($this->builder))->where('plan_id', $plan->id); | ||||
|                     $this->resetByExpireDay($builder); | ||||
|                     break; | ||||
|                 } | ||||
|   | ||||
| @@ -4,8 +4,10 @@ namespace App\Console\Commands; | ||||
|  | ||||
| use App\Models\Order; | ||||
| use App\Models\User; | ||||
| use App\Utils\CacheKey; | ||||
| use App\Utils\Helper; | ||||
| use Illuminate\Console\Command; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Matriphe\Larinfo; | ||||
|  | ||||
| class Test extends Command | ||||
|   | ||||
| @@ -48,7 +48,7 @@ class V2boardInstall extends Command | ||||
|             $this->info("  \ V /  / __/| |_) | (_) | (_| | | | (_| | "); | ||||
|             $this->info("   \_/  |_____|____/ \___/ \__,_|_|  \__,_| "); | ||||
|             if (\File::exists(base_path() . '/.env')) { | ||||
|                 abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件'); | ||||
|                 abort(500, 'V2board 已安装,如需重新安装请删除目录下.env文件'); | ||||
|             } | ||||
|  | ||||
|             if (!copy(base_path() . '/.env.example', base_path() . '/.env')) { | ||||
| @@ -99,7 +99,6 @@ class V2boardInstall extends Command | ||||
|  | ||||
|             $this->info('一切就绪'); | ||||
|             $this->info('访问 http(s)://你的站点/admin 进入管理面板'); | ||||
|             \File::put(base_path() . '/.lock', time()); | ||||
|         } catch (\Exception $e) { | ||||
|             $this->error($e->getMessage()); | ||||
|         } | ||||
|   | ||||
| @@ -3,10 +3,12 @@ | ||||
| namespace App\Http\Controllers\Admin; | ||||
|  | ||||
| use App\Http\Requests\Admin\ConfigSave; | ||||
| use App\Jobs\SendEmailJob; | ||||
| use App\Services\TelegramService; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Utils\Dict; | ||||
| use App\Http\Controllers\Controller; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
|  | ||||
| class ConfigController extends Controller | ||||
| { | ||||
| @@ -32,6 +34,24 @@ class ConfigController extends Controller | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function testSendMail(Request $request) | ||||
|     { | ||||
|         $obj = new SendEmailJob([ | ||||
|             'email' => $request->session()->get('email'), | ||||
|             'subject' => 'This is v2board test email', | ||||
|             'template_name' => 'notify', | ||||
|             'template_value' => [ | ||||
|                 'name' => config('v2board.app_name', 'V2Board'), | ||||
|                 'content' => 'This is v2board test email', | ||||
|                 'url' => config('v2board.app_url') | ||||
|             ] | ||||
|         ]); | ||||
|         return response([ | ||||
|             'data' => true, | ||||
|             'log' => $obj->handle() | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function setTelegramWebhook(Request $request) | ||||
|     { | ||||
|         $telegramService = new TelegramService($request->input('telegram_bot_token')); | ||||
| @@ -82,7 +102,9 @@ class ConfigController extends Controller | ||||
|                     'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0), | ||||
|                     'recaptcha_key' => config('v2board.recaptcha_key'), | ||||
|                     'recaptcha_site_key' => config('v2board.recaptcha_site_key'), | ||||
|                     'tos_url' => config('v2board.tos_url') | ||||
|                     'tos_url' => config('v2board.tos_url'), | ||||
|                     'currency' => config('v2board.currency', 'CNY'), | ||||
|                     'currency_symbol' => config('v2board.currency_symbol', '¥') | ||||
|                 ], | ||||
|                 'subscribe' => [ | ||||
|                     'plan_change_enable' => (int)config('v2board.plan_change_enable', 1), | ||||
| @@ -151,7 +173,8 @@ class ConfigController extends Controller | ||||
|                 ], | ||||
|                 'telegram' => [ | ||||
|                     'telegram_bot_enable' => config('v2board.telegram_bot_enable', 0), | ||||
|                     'telegram_bot_token' => config('v2board.telegram_bot_token') | ||||
|                     'telegram_bot_token' => config('v2board.telegram_bot_token'), | ||||
|                     'telegram_discuss_link' => config('v2board.telegram_discuss_link') | ||||
|                 ], | ||||
|                 'app' => [ | ||||
|                     'windows_version' => config('v2board.windows_version'), | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class CouponController extends Controller | ||||
|         $current = $request->input('current') ? $request->input('current') : 1; | ||||
|         $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; | ||||
|         $sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC'; | ||||
|         $sort = $request->input('sort') ? $request->input('sort') : 'created_at'; | ||||
|         $sort = $request->input('sort') ? $request->input('sort') : 'id'; | ||||
|         $builder = Coupon::orderBy($sort, $sortType); | ||||
|         $total = $builder->count(); | ||||
|         $coupons = $builder->forPage($current, $pageSize) | ||||
| @@ -64,6 +64,7 @@ class CouponController extends Controller | ||||
|         $coupon = $request->validated(); | ||||
|         $coupon['created_at'] = $coupon['updated_at'] = time(); | ||||
|         $coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']); | ||||
|         $coupon['limit_period'] = json_encode($coupon['limit_period']); | ||||
|         unset($coupon['generate_count']); | ||||
|         for ($i = 0;$i < $request->input('generate_count');$i++) { | ||||
|             $coupon['code'] = Helper::randomChar(8); | ||||
|   | ||||
| @@ -146,11 +146,11 @@ class OrderController extends Controller | ||||
|         $orderService = new OrderService($order); | ||||
|         $order->user_id = $user->id; | ||||
|         $order->plan_id = $plan->id; | ||||
|         $order->cycle = $request->input('cycle'); | ||||
|         $order->period = $request->input('period'); | ||||
|         $order->trade_no = Helper::guid(); | ||||
|         $order->total_amount = $request->input('total_amount'); | ||||
|  | ||||
|         if ($order->cycle === 'reset_price') { | ||||
|         if ($order->period === 'reset_price') { | ||||
|             $order->type = 4; | ||||
|         } else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) { | ||||
|             $order->type = 3; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| namespace App\Http\Controllers\Admin; | ||||
|  | ||||
| use App\Http\Requests\Admin\PaymentSave; | ||||
| use App\Services\PaymentService; | ||||
| use App\Utils\Helper; | ||||
| use Illuminate\Http\Request; | ||||
| @@ -57,8 +58,18 @@ class PaymentController extends Controller | ||||
|                 'data' => true | ||||
|             ]); | ||||
|         } | ||||
|         $request->validate([ | ||||
|             'name' => 'required', | ||||
|             'payment' => 'required', | ||||
|             'config' => 'required' | ||||
|         ], [ | ||||
|             'name.required' => '显示名称不能为空', | ||||
|             'payment.required' => '网关参数不能为空', | ||||
|             'config.required' => '配置参数不能为空' | ||||
|         ]); | ||||
|         if (!Payment::create([ | ||||
|             'name' => $request->input('name'), | ||||
|             'icon' => $request->input('icon'), | ||||
|             'payment' => $request->input('payment'), | ||||
|             'config' => $request->input('config'), | ||||
|             'uuid' => Helper::randomChar(8) | ||||
|   | ||||
| @@ -3,9 +3,12 @@ | ||||
| namespace App\Http\Controllers\Admin\Server; | ||||
|  | ||||
| use App\Models\Plan; | ||||
| use App\Models\ServerShadowsocks; | ||||
| use App\Models\ServerTrojan; | ||||
| use App\Models\ServerV2ray; | ||||
| use App\Models\ServerGroup; | ||||
| use App\Models\User; | ||||
| use App\Services\ServerService; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Http\Controllers\Controller; | ||||
|  | ||||
| @@ -18,8 +21,20 @@ class GroupController extends Controller | ||||
|                 'data' => [ServerGroup::find($request->input('group_id'))] | ||||
|             ]); | ||||
|         } | ||||
|         $serverGroups = ServerGroup::get(); | ||||
|         $serverService = new ServerService(); | ||||
|         $servers = $serverService->getAllServers(); | ||||
|         foreach ($serverGroups as $k => $v) { | ||||
|             $serverGroups[$k]['user_count'] = User::where('group_id', $v['id'])->count(); | ||||
|             $serverGroups[$k]['server_count'] = 0; | ||||
|             foreach ($servers as $server) { | ||||
|                 if (in_array($v['id'], $server['group_id'])) { | ||||
|                     $serverGroups[$k]['server_count'] = $serverGroups[$k]['server_count']+1; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => ServerGroup::get() | ||||
|             'data' => $serverGroups | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -22,6 +22,7 @@ class ManageController extends Controller | ||||
|  | ||||
|     public function sort(Request $request) | ||||
|     { | ||||
|         ini_set('post_max_size', '1m'); | ||||
|         DB::beginTransaction(); | ||||
|         foreach ($request->input('sorts') as $k => $v) { | ||||
|             switch ($v['key']) { | ||||
|   | ||||
| @@ -30,20 +30,22 @@ class UserController extends Controller | ||||
|  | ||||
|     private function filter(Request $request, $builder) | ||||
|     { | ||||
|         if ($request->input('filter')) { | ||||
|             foreach ($request->input('filter') as $filter) { | ||||
|                 if ($filter['key'] === 'invite_by_email') { | ||||
|                     $user = User::where('email', $filter['value'])->first(); | ||||
|                     if (!$user) continue; | ||||
|                     $builder->where('invite_user_id', $user->id); | ||||
|                     continue; | ||||
|         $filters = $request->input('filter'); | ||||
|         if ($filters) { | ||||
|             foreach ($filters as $k => $filter) { | ||||
|                 if ($filter['condition'] === '模糊') { | ||||
|                     $filter['condition'] = 'like'; | ||||
|                     $filter['value'] = "%{$filter['value']}%"; | ||||
|                 } | ||||
|                 if ($filter['key'] === 'd' || $filter['key'] === 'transfer_enable') { | ||||
|                     $filter['value'] = $filter['value'] * 1073741824; | ||||
|                 } | ||||
|                 if ($filter['condition'] === '模糊') { | ||||
|                     $filter['condition'] = 'like'; | ||||
|                     $filter['value'] = "%{$filter['value']}%"; | ||||
|                 if ($filter['key'] === 'invite_by_email') { | ||||
|                     $user = User::where('email', $filter['condition'], $filter['value'])->first(); | ||||
|                     $inviteUserId = isset($user->id) ? $user->id : 0; | ||||
|                     $builder->where('invite_user_id', $inviteUserId); | ||||
|                     unset($filters[$k]); | ||||
|                     continue; | ||||
|                 } | ||||
|                 $builder->where($filter['key'], $filter['condition'], $filter['value']); | ||||
|             } | ||||
| @@ -179,6 +181,9 @@ class UserController extends Controller | ||||
|                 'uuid' => Helper::guid(true), | ||||
|                 'token' => Helper::guid() | ||||
|             ]; | ||||
|             if (User::where('email', $user['email'])->first()) { | ||||
|                 abort(500, '邮箱已存在于系统中'); | ||||
|             } | ||||
|             $user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT); | ||||
|             if (!User::create($user)) { | ||||
|                 abort(500, '生成失败'); | ||||
| @@ -251,7 +256,8 @@ class UserController extends Controller | ||||
|                     'url' => config('v2board.app_url'), | ||||
|                     'content' => $request->input('content') | ||||
|                 ] | ||||
|             ]); | ||||
|             ], | ||||
|             'send_email_mass'); | ||||
|         } | ||||
|  | ||||
|         return response([ | ||||
|   | ||||
| @@ -52,7 +52,19 @@ class Clash | ||||
|         $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy); | ||||
|         foreach ($config['proxy-groups'] as $k => $v) { | ||||
|             if (!is_array($config['proxy-groups'][$k]['proxies'])) continue; | ||||
|             $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); | ||||
|             $isFilter = false; | ||||
|             foreach ($config['proxy-groups'][$k]['proxies'] as $src) { | ||||
|                 foreach ($proxies as $dst) { | ||||
|                     if ($this->isMatch($src, $dst)) { | ||||
|                         $isFilter = true; | ||||
|                         $config['proxy-groups'][$k]['proxies'] = array_diff($config['proxy-groups'][$k]['proxies'], [$src]); | ||||
|                         array_push($config['proxy-groups'][$k]['proxies'], $dst); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (!$isFilter) { | ||||
|                 $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); | ||||
|             } | ||||
|         } | ||||
|         // Force the current subscription domain to be a direct rule | ||||
|         $subsDomain = $_SERVER['SERVER_NAME']; | ||||
| @@ -134,4 +146,13 @@ class Clash | ||||
|         if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false); | ||||
|         return $array; | ||||
|     } | ||||
|  | ||||
|     private function isMatch($exp, $str) | ||||
|     { | ||||
|         try { | ||||
|             return preg_match($exp, $str); | ||||
|         } catch (\Exception $e) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										159
									
								
								app/Http/Controllers/Client/Protocols/Stash.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								app/Http/Controllers/Client/Protocols/Stash.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\Http\Controllers\Client\Protocols; | ||||
|  | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
|  | ||||
| class Stash | ||||
| { | ||||
|     public $flag = 'stash'; | ||||
|     private $servers; | ||||
|     private $user; | ||||
|  | ||||
|     public function __construct($user, $servers) | ||||
|     { | ||||
|         $this->user = $user; | ||||
|         $this->servers = $servers; | ||||
|     } | ||||
|  | ||||
|     public function handle() | ||||
|     { | ||||
|         $servers = $this->servers; | ||||
|         $user = $this->user; | ||||
|         $appName = config('v2board.app_name', 'V2Board'); | ||||
|         header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); | ||||
|         header('profile-update-interval: 24'); | ||||
|         header("content-disposition: filename={$appName}"); | ||||
|         // 暂时使用clash配置文件,后续根据Stash更新情况更新 | ||||
|         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml'; | ||||
|         $customConfig = base_path() . '/resources/rules/custom.clash.yaml'; | ||||
|         if (\File::exists($customConfig)) { | ||||
|             $config = Yaml::parseFile($customConfig); | ||||
|         } else { | ||||
|             $config = Yaml::parseFile($defaultConfig); | ||||
|         } | ||||
|         $proxy = []; | ||||
|         $proxies = []; | ||||
|  | ||||
|         foreach ($servers as $item) { | ||||
|             if ($item['type'] === 'shadowsocks') { | ||||
|                 array_push($proxy, self::buildShadowsocks($user['uuid'], $item)); | ||||
|                 array_push($proxies, $item['name']); | ||||
|             } | ||||
|             if ($item['type'] === 'v2ray') { | ||||
|                 array_push($proxy, self::buildVmess($user['uuid'], $item)); | ||||
|                 array_push($proxies, $item['name']); | ||||
|             } | ||||
|             if ($item['type'] === 'trojan') { | ||||
|                 array_push($proxy, self::buildTrojan($user['uuid'], $item)); | ||||
|                 array_push($proxies, $item['name']); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy); | ||||
|         foreach ($config['proxy-groups'] as $k => $v) { | ||||
|             if (!is_array($config['proxy-groups'][$k]['proxies'])) continue; | ||||
|             $isFilter = false; | ||||
|             foreach ($config['proxy-groups'][$k]['proxies'] as $src) { | ||||
|                 foreach ($proxies as $dst) { | ||||
|                     if ($this->isMatch($src, $dst)) { | ||||
|                         $isFilter = true; | ||||
|                         $config['proxy-groups'][$k]['proxies'] = array_diff($config['proxy-groups'][$k]['proxies'], [$src]); | ||||
|                         array_push($config['proxy-groups'][$k]['proxies'], $dst); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (!$isFilter) { | ||||
|                 $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); | ||||
|             } | ||||
|         } | ||||
|         // Force the current subscription domain to be a direct rule | ||||
|         $subsDomain = $_SERVER['SERVER_NAME']; | ||||
|         $subsDomainRule = "DOMAIN,{$subsDomain},DIRECT"; | ||||
|         array_unshift($config['rules'], $subsDomainRule); | ||||
|  | ||||
|         $yaml = Yaml::dump($config); | ||||
|         $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml); | ||||
|         return $yaml; | ||||
|     } | ||||
|  | ||||
|     public static function buildShadowsocks($uuid, $server) | ||||
|     { | ||||
|         $array = []; | ||||
|         $array['name'] = $server['name']; | ||||
|         $array['type'] = 'ss'; | ||||
|         $array['server'] = $server['host']; | ||||
|         $array['port'] = $server['port']; | ||||
|         $array['cipher'] = $server['cipher']; | ||||
|         $array['password'] = $uuid; | ||||
|         $array['udp'] = true; | ||||
|         return $array; | ||||
|     } | ||||
|  | ||||
|     public static function buildVmess($uuid, $server) | ||||
|     { | ||||
|         $array = []; | ||||
|         $array['name'] = $server['name']; | ||||
|         $array['type'] = 'vmess'; | ||||
|         $array['server'] = $server['host']; | ||||
|         $array['port'] = $server['port']; | ||||
|         $array['uuid'] = $uuid; | ||||
|         $array['alterId'] = $server['alter_id']; | ||||
|         $array['cipher'] = 'auto'; | ||||
|         $array['udp'] = true; | ||||
|  | ||||
|         if ($server['tls']) { | ||||
|             $array['tls'] = true; | ||||
|             if ($server['tlsSettings']) { | ||||
|                 $tlsSettings = $server['tlsSettings']; | ||||
|                 if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) | ||||
|                     $array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false); | ||||
|                 if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) | ||||
|                     $array['servername'] = $tlsSettings['serverName']; | ||||
|             } | ||||
|         } | ||||
|         if ($server['network'] === 'ws') { | ||||
|             $array['network'] = 'ws'; | ||||
|             if ($server['networkSettings']) { | ||||
|                 $wsSettings = $server['networkSettings']; | ||||
|                 if (isset($wsSettings['path']) && !empty($wsSettings['path'])) | ||||
|                     $array['ws-path'] = $wsSettings['path']; | ||||
|                 if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host'])) | ||||
|                     $array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']]; | ||||
|             } | ||||
|         } | ||||
|         if ($server['network'] === 'grpc') { | ||||
|             $array['network'] = 'grpc'; | ||||
|             if ($server['networkSettings']) { | ||||
|                 $grpcObject = $server['networkSettings']; | ||||
|                 $array['grpc-opts'] = []; | ||||
|                 $array['grpc-opts']['grpc-service-name'] = $grpcObject['serviceName']; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $array; | ||||
|     } | ||||
|  | ||||
|     public static function buildTrojan($password, $server) | ||||
|     { | ||||
|         $array = []; | ||||
|         $array['name'] = $server['name']; | ||||
|         $array['type'] = 'trojan'; | ||||
|         $array['server'] = $server['host']; | ||||
|         $array['port'] = $server['port']; | ||||
|         $array['password'] = $password; | ||||
|         $array['udp'] = true; | ||||
|         if (!empty($server['server_name'])) $array['sni'] = $server['server_name']; | ||||
|         if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false); | ||||
|         return $array; | ||||
|     } | ||||
|  | ||||
|     private function isMatch($exp, $str) | ||||
|     { | ||||
|         try { | ||||
|             return preg_match($exp, $str); | ||||
|         } catch (\Exception $e) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -67,12 +67,11 @@ class Surfboard | ||||
|     public static function buildShadowsocks($password, $server) | ||||
|     { | ||||
|         $config = [ | ||||
|             "{$server['name']}=custom", | ||||
|             "{$server['name']}=ss", | ||||
|             "{$server['host']}", | ||||
|             "{$server['port']}", | ||||
|             "{$server['cipher']}", | ||||
|             "{$password}", | ||||
|             'https://raw.githubusercontent.com/Hackl0us/proxy-tool-backup/master/SSEncrypt.module', | ||||
|             "encrypt-method={$server['cipher']}", | ||||
|             "password={$password}", | ||||
|             'tfo=true', | ||||
|             'udp-relay=true' | ||||
|         ]; | ||||
|   | ||||
| @@ -14,9 +14,12 @@ class CommController extends Controller | ||||
|         return response([ | ||||
|             'data' => [ | ||||
|                 'is_telegram' => (int)config('v2board.telegram_bot_enable', 0), | ||||
|                 'telegram_discuss_link' => config('v2board.telegram_discuss_link'), | ||||
|                 'stripe_pk' => config('v2board.stripe_pk_live'), | ||||
|                 'withdraw_methods' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT), | ||||
|                 'withdraw_close' => (int)config('v2board.withdraw_close_enable', 0) | ||||
|                 'withdraw_close' => (int)config('v2board.withdraw_close_enable', 0), | ||||
|                 'currency' => config('v2board.currency', 'CNY'), | ||||
|                 'currency_symbol' => config('v2board.currency_symbol', '¥') | ||||
|             ] | ||||
|         ]); | ||||
|     } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class InviteController extends Controller | ||||
|         return response([ | ||||
|             'data' => Order::where('invite_user_id', $request->session()->get('id')) | ||||
|                 ->where('commission_balance', '>', 0) | ||||
|                 ->where('status', 3) | ||||
|                 ->whereIn('status', [3, 4]) | ||||
|                 ->select([ | ||||
|                     'id', | ||||
|                     'commission_status', | ||||
|   | ||||
| @@ -78,26 +78,27 @@ class OrderController extends Controller | ||||
|             abort(500, __('Subscription plan does not exist')); | ||||
|         } | ||||
|  | ||||
|         if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) { | ||||
|             if ($request->input('cycle') !== 'reset_price') { | ||||
|                 abort(500, __('This subscription has been sold out, please choose another subscription')); | ||||
|             } | ||||
|         if ($plan[$request->input('period')] === NULL) { | ||||
|             abort(500, __('This payment period cannot be purchased, please choose another period')); | ||||
|         } | ||||
|  | ||||
|         if (!$plan->renew && $user->plan_id == $plan->id && $request->input('cycle') !== 'reset_price') { | ||||
|             abort(500, __('This subscription cannot be renewed, please change to another subscription')); | ||||
|         } | ||||
|  | ||||
|         if ($plan[$request->input('cycle')] === NULL) { | ||||
|             abort(500, __('This payment cycle cannot be purchased, please choose another cycle')); | ||||
|         } | ||||
|  | ||||
|         if ($request->input('cycle') === 'reset_price') { | ||||
|         if ($request->input('period') === 'reset_price') { | ||||
|             if ($user->expired_at <= time() || !$user->plan_id) { | ||||
|                 abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package')); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) { | ||||
|             if ($request->input('period') !== 'reset_price') { | ||||
|                 abort(500, __('This subscription has been sold out, please choose another subscription')); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!$plan->renew && $user->plan_id == $plan->id && $request->input('period') !== 'reset_price') { | ||||
|             abort(500, __('This subscription cannot be renewed, please change to another subscription')); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) { | ||||
|             abort(500, __('This subscription has expired, please change to another subscription')); | ||||
|         } | ||||
| @@ -107,9 +108,9 @@ class OrderController extends Controller | ||||
|         $orderService = new OrderService($order); | ||||
|         $order->user_id = $request->session()->get('id'); | ||||
|         $order->plan_id = $plan->id; | ||||
|         $order->cycle = $request->input('cycle'); | ||||
|         $order->period = $request->input('period'); | ||||
|         $order->trade_no = Helper::generateOrderNo(); | ||||
|         $order->total_amount = $plan[$request->input('cycle')]; | ||||
|         $order->total_amount = $plan[$request->input('period')]; | ||||
|  | ||||
|         if ($request->input('coupon_code')) { | ||||
|             $couponService = new CouponService($request->input('coupon_code')); | ||||
| @@ -211,7 +212,8 @@ class OrderController extends Controller | ||||
|         $methods = Payment::select([ | ||||
|             'id', | ||||
|             'name', | ||||
|             'payment' | ||||
|             'payment', | ||||
|             'icon' | ||||
|         ]) | ||||
|             ->where('enable', 1)->get(); | ||||
|  | ||||
|   | ||||
| @@ -70,7 +70,8 @@ class UserController extends Controller | ||||
|                 'plan_id', | ||||
|                 'discount', | ||||
|                 'commission_rate', | ||||
|                 'telegram_id' | ||||
|                 'telegram_id', | ||||
|                 'uuid' | ||||
|             ]) | ||||
|             ->first(); | ||||
|         if (!$user) { | ||||
| @@ -103,7 +104,6 @@ class UserController extends Controller | ||||
|     { | ||||
|         $user = User::where('id', $request->session()->get('id')) | ||||
|             ->select([ | ||||
|                 'id', | ||||
|                 'plan_id', | ||||
|                 'token', | ||||
|                 'expired_at', | ||||
| @@ -189,6 +189,8 @@ class UserController extends Controller | ||||
|     private function getResetDay(User $user) | ||||
|     { | ||||
|         if ($user->expired_at <= time() || $user->expired_at === NULL) return null; | ||||
|         // if reset method is not reset | ||||
|         if (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 2) return null; | ||||
|         $day = date('d', $user->expired_at); | ||||
|         $today = date('d'); | ||||
|         $lastDay = date('d', strtotime('last day of +0 months')); | ||||
|   | ||||
| @@ -46,6 +46,8 @@ class ConfigSave extends FormRequest | ||||
|             'recaptcha_key' => '', | ||||
|             'recaptcha_site_key' => '', | ||||
|             'tos_url' => 'nullable|url', | ||||
|             'currency' => '', | ||||
|             'currency_symbol' => '', | ||||
|             // subscribe | ||||
|             'plan_change_enable' => 'in:0,1', | ||||
|             'reset_traffic_method' => 'in:0,1,2', | ||||
| @@ -110,6 +112,7 @@ class ConfigSave extends FormRequest | ||||
|             'telegram_bot_token' => '', | ||||
|             'telegram_discuss_id' => '', | ||||
|             'telegram_channel_id' => '', | ||||
|             'telegram_discuss_link' => 'nullable|url', | ||||
|             // app | ||||
|             'windows_version' => '', | ||||
|             'windows_download_url' => '', | ||||
| @@ -127,7 +130,8 @@ class ConfigSave extends FormRequest | ||||
|             'app_url.url' => '站点URL格式不正确,必须携带http(s)://', | ||||
|             'subscribe_url.url' => '订阅URL格式不正确,必须携带http(s)://', | ||||
|             'server_token.min' => '通讯密钥长度必须大于16位', | ||||
|             'tos_url.url' => '服务条款URL格式不正确' | ||||
|             'tos_url.url' => '服务条款URL格式不正确,必须携带http(s)://', | ||||
|             'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式,必须携带http(s)://' | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -23,6 +23,7 @@ class CouponGenerate extends FormRequest | ||||
|             'limit_use' => 'nullable|integer', | ||||
|             'limit_use_with_user' => 'nullable|integer', | ||||
|             'limit_plan_ids' => 'nullable|array', | ||||
|             'limit_period' => 'nullable|array', | ||||
|             'code' => '' | ||||
|         ]; | ||||
|     } | ||||
| @@ -43,7 +44,8 @@ class CouponGenerate extends FormRequest | ||||
|             'ended_at.integer' => '结束时间格式有误', | ||||
|             'limit_use.integer' => '最大使用次数格式有误', | ||||
|             'limit_use_with_user.integer' => '限制用户使用次数格式有误', | ||||
|             'limit_plan_ids.array' => '指定订阅格式有误' | ||||
|             'limit_plan_ids.array' => '指定订阅格式有误', | ||||
|             'limit_period.array' => '指定周期格式有误' | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ class OrderAssign extends FormRequest | ||||
|             'plan_id' => 'required', | ||||
|             'email' => 'required', | ||||
|             'total_amount' => 'required', | ||||
|             'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price' | ||||
|             'period' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price' | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
| @@ -27,8 +27,8 @@ class OrderAssign extends FormRequest | ||||
|             'plan_id.required' => '订阅不能为空', | ||||
|             'email.required' => '邮箱不能为空', | ||||
|             'total_amount.required' => '支付金额不能为空', | ||||
|             'cycle.required' => '订阅周期不能为空', | ||||
|             'cycle.in' => '订阅周期格式有误' | ||||
|             'period.required' => '订阅周期不能为空', | ||||
|             'period.in' => '订阅周期格式有误' | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class OrderFetch extends FormRequest | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'filter.*.key' => 'required|in:email,trade_no,status,commission_status,user_id,invite_user_id', | ||||
|             'filter.*.key' => 'required|in:email,trade_no,status,commission_status,user_id,invite_user_id,callback_no', | ||||
|             'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=', | ||||
|             'filter.*.value' => '' | ||||
|         ]; | ||||
|   | ||||
| @@ -15,7 +15,7 @@ class OrderSave extends FormRequest | ||||
|     { | ||||
|         return [ | ||||
|             'plan_id' => 'required', | ||||
|             'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price' | ||||
|             'period' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price' | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
| @@ -23,8 +23,8 @@ class OrderSave extends FormRequest | ||||
|     { | ||||
|         return [ | ||||
|             'plan_id.required' => __('Plan ID cannot be empty'), | ||||
|             'cycle.required' => __('Plan cycle cannot be empty'), | ||||
|             'cycle.in' => __('Wrong plan cycle') | ||||
|             'period.required' => __('Plan period cannot be empty'), | ||||
|             'period.in' => __('Wrong plan period') | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ class AdminRoute | ||||
|             $router->get ('/config/getEmailTemplate', 'Admin\\ConfigController@getEmailTemplate'); | ||||
|             $router->get ('/config/getThemeTemplate', 'Admin\\ConfigController@getThemeTemplate'); | ||||
|             $router->post('/config/setTelegramWebhook', 'Admin\\ConfigController@setTelegramWebhook'); | ||||
|             $router->post('/config/testSendMail', 'Admin\\ConfigController@testSendMail'); | ||||
|             // Plan | ||||
|             $router->get ('/plan/fetch', 'Admin\\PlanController@fetch'); | ||||
|             $router->post('/plan/save', 'Admin\\PlanController@save'); | ||||
|   | ||||
| @@ -21,10 +21,9 @@ class SendEmailJob implements ShouldQueue | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function __construct($params) | ||||
|     public function __construct($params, $queue = 'send_email') | ||||
|     { | ||||
|         $this->delay(now()->addSecond(2)); | ||||
|         $this->onQueue('send_email'); | ||||
|         $this->onQueue($queue); | ||||
|         $this->params = $params; | ||||
|     } | ||||
|  | ||||
| @@ -60,11 +59,15 @@ class SendEmailJob implements ShouldQueue | ||||
|             $error = $e->getMessage(); | ||||
|         } | ||||
|  | ||||
|         MailLog::create([ | ||||
|         $log = [ | ||||
|             'email' => $params['email'], | ||||
|             'subject' => $params['subject'], | ||||
|             'template_name' => $params['template_name'], | ||||
|             'error' => isset($error) ? $error : NULL | ||||
|         ]); | ||||
|         ]; | ||||
|  | ||||
|         MailLog::create($log); | ||||
|         $log['config'] = config('mail'); | ||||
|         return $log; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ class Coupon extends Model | ||||
|     protected $casts = [ | ||||
|         'created_at' => 'timestamp', | ||||
|         'updated_at' => 'timestamp', | ||||
|         'limit_plan_ids' => 'array' | ||||
|         'limit_plan_ids' => 'array', | ||||
|         'limit_period' => 'array' | ||||
|     ]; | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ class CouponService | ||||
|     public $coupon; | ||||
|     public $planId; | ||||
|     public $userId; | ||||
|     public $period; | ||||
|  | ||||
|     public function __construct($code) | ||||
|     { | ||||
| @@ -21,6 +22,7 @@ class CouponService | ||||
|     { | ||||
|         $this->setPlanId($order->plan_id); | ||||
|         $this->setUserId($order->user_id); | ||||
|         $this->setPeriod($order->period); | ||||
|         $this->check(); | ||||
|         switch ($this->coupon->type) { | ||||
|             case 1: | ||||
| @@ -30,6 +32,9 @@ class CouponService | ||||
|                 $order->discount_amount = $order->total_amount * ($this->coupon->value / 100); | ||||
|                 break; | ||||
|         } | ||||
|         if ($order->discount_amount > $order->total_amount) { | ||||
|             $order->discount_amount = $order->total_amount; | ||||
|         } | ||||
|         if ($this->coupon->limit_use !== NULL) { | ||||
|             $this->coupon->limit_use = $this->coupon->limit_use - 1; | ||||
|             if (!$this->coupon->save()) { | ||||
| @@ -59,6 +64,11 @@ class CouponService | ||||
|         $this->userId = $userId; | ||||
|     } | ||||
|  | ||||
|     public function setPeriod($period) | ||||
|     { | ||||
|         $this->period = $period; | ||||
|     } | ||||
|  | ||||
|     public function checkLimitUseWithUser():bool | ||||
|     { | ||||
|         $usedCount = Order::where('coupon_id', $this->coupon->id) | ||||
| @@ -88,6 +98,11 @@ class CouponService | ||||
|                 abort(500, __('The coupon code cannot be used for this subscription')); | ||||
|             } | ||||
|         } | ||||
|         if ($this->coupon->limit_period && $this->period) { | ||||
|             if (!in_array($this->period, $this->coupon->limit_period)) { | ||||
|                 abort(500, __('The coupon code cannot be used for this period')); | ||||
|             } | ||||
|         } | ||||
|         if ($this->coupon->limit_use_with_user !== NULL && $this->userId) { | ||||
|             if (!$this->checkLimitUseWithUser()) { | ||||
|                 abort(500, __('The coupon can only be used :limit_use_with_user per person', [ | ||||
|   | ||||
| @@ -46,7 +46,7 @@ class OrderService | ||||
|                 abort(500, '开通失败'); | ||||
|             } | ||||
|         } | ||||
|         switch ((string)$order->cycle) { | ||||
|         switch ((string)$order->period) { | ||||
|             case 'onetime_price': | ||||
|                 $this->buyByOneTime($plan); | ||||
|                 break; | ||||
| @@ -54,7 +54,7 @@ class OrderService | ||||
|                 $this->buyByResetTraffic(); | ||||
|                 break; | ||||
|             default: | ||||
|                 $this->buyByCycle($order, $plan); | ||||
|                 $this->buyByPeriod($order, $plan); | ||||
|         } | ||||
|  | ||||
|         switch ((int)$order->type) { | ||||
| @@ -86,7 +86,7 @@ class OrderService | ||||
|     public function setOrderType(User $user) | ||||
|     { | ||||
|         $order = $this->order; | ||||
|         if ($order->cycle === 'reset_price') { | ||||
|         if ($order->period === 'reset_price') { | ||||
|             $order->type = 4; | ||||
|         } else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id && ($user->expired_at > time() || $user->expired_at === NULL)) { | ||||
|             if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单操作'); | ||||
| @@ -156,7 +156,7 @@ class OrderService | ||||
|         if ($user->expired_at === NULL) { | ||||
|             $this->getSurplusValueByOneTime($user, $order); | ||||
|         } else { | ||||
|             $this->getSurplusValueByCycle($user, $order); | ||||
|             $this->getSurplusValueByPeriod($user, $order); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -170,23 +170,23 @@ class OrderService | ||||
|         } | ||||
|         $notUsedTraffic = $plan->transfer_enable - (($user->u + $user->d) / 1073741824); | ||||
|         $result = $trafficUnitPrice * $notUsedTraffic; | ||||
|         $orderModel = Order::where('user_id', $user->id)->where('cycle', '!=', 'reset_price')->where('status', 3); | ||||
|         $orderModel = Order::where('user_id', $user->id)->where('period', '!=', 'reset_price')->where('status', 3); | ||||
|         $order->surplus_amount = $result > 0 ? $result : 0; | ||||
|         $order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id'); | ||||
|     } | ||||
|  | ||||
|     private function orderIsUsed(Order $order):bool | ||||
|     { | ||||
|         $month = self::STR_TO_TIME[$order->cycle]; | ||||
|         $month = self::STR_TO_TIME[$order->period]; | ||||
|         $orderExpireDay = strtotime('+' . $month . ' month', $order->created_at); | ||||
|         if ($orderExpireDay < time()) return true; | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     private function getSurplusValueByCycle(User $user, Order $order) | ||||
|     private function getSurplusValueByPeriod(User $user, Order $order) | ||||
|     { | ||||
|         $orderModel = Order::where('user_id', $user->id) | ||||
|             ->where('cycle', '!=', 'reset_price') | ||||
|             ->where('period', '!=', 'reset_price') | ||||
|             ->where('status', 3); | ||||
|         $orders = $orderModel->get(); | ||||
|         $orderSurplusMonth = 0; | ||||
| @@ -194,9 +194,9 @@ class OrderService | ||||
|         $userSurplusMonth = ($user->expired_at - time()) / 2678400; | ||||
|         foreach ($orders as $k => $item) { | ||||
|             // 兼容历史余留问题 | ||||
|             if ($item->cycle === 'onetime_price') continue; | ||||
|             if ($item->period === 'onetime_price') continue; | ||||
|             if ($this->orderIsUsed($item)) continue; | ||||
|             $orderSurplusMonth = $orderSurplusMonth + self::STR_TO_TIME[$item->cycle]; | ||||
|             $orderSurplusMonth = $orderSurplusMonth + self::STR_TO_TIME[$item->period]; | ||||
|             $orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']); | ||||
|         } | ||||
|         if (!$orderSurplusMonth || !$orderSurplusAmount) return; | ||||
| @@ -252,7 +252,7 @@ class OrderService | ||||
|         $this->user->d = 0; | ||||
|     } | ||||
|  | ||||
|     private function buyByCycle(Order $order, Plan $plan) | ||||
|     private function buyByPeriod(Order $order, Plan $plan) | ||||
|     { | ||||
|         // change plan process | ||||
|         if ((int)$order->type === 3) { | ||||
| @@ -265,7 +265,7 @@ class OrderService | ||||
|         if ($order->type === 1) $this->buyByResetTraffic(); | ||||
|         $this->user->plan_id = $plan->id; | ||||
|         $this->user->group_id = $plan->group_id; | ||||
|         $this->user->expired_at = $this->getTime($order->cycle, $this->user->expired_at); | ||||
|         $this->user->expired_at = $this->getTime($order->period, $this->user->expired_at); | ||||
|     } | ||||
|  | ||||
|     private function buyByOneTime(Plan $plan) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ namespace App\Services; | ||||
| use App\Jobs\SendTelegramJob; | ||||
| use App\Models\User; | ||||
| use \Curl\Curl; | ||||
| use Illuminate\Mail\Markdown; | ||||
|  | ||||
| class TelegramService { | ||||
|     protected $api; | ||||
| @@ -15,6 +16,9 @@ class TelegramService { | ||||
|  | ||||
|     public function sendMessage(int $chatId, string $text, string $parseMode = '') | ||||
|     { | ||||
|         if ($parseMode === 'markdown') { | ||||
|             $text = str_replace('_', '\_', $text); | ||||
|         } | ||||
|         $this->request('sendMessage', [ | ||||
|             'chat_id' => $chatId, | ||||
|             'text' => $text, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user