From c5eff6ad4e4dbd919a40648f1efffa3548dc0625 Mon Sep 17 00:00:00 2001 From: "iVampireSP.com" Date: Tue, 16 May 2023 20:41:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E6=B5=81=E9=87=8F?= =?UTF-8?q?=E5=85=85=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Api/TrafficController.php | 84 ++++++++++ app/Http/Kernel.php | 32 ++-- app/Http/Middleware/WHMCSApi.php | 76 +++++++++ app/Support/WHMCS.php | 140 ++++++++++++++++ config/settings.php | 3 +- config/whmcs.php | 12 ++ resources/js/components/Menu.vue | 15 +- resources/js/plugins/router.js | 8 + resources/js/views/Charge.vue | 155 ++++++++++++++++++ routes/api.php | 8 +- 10 files changed, 510 insertions(+), 23 deletions(-) create mode 100644 app/Http/Middleware/WHMCSApi.php create mode 100644 app/Support/WHMCS.php create mode 100644 config/whmcs.php create mode 100644 resources/js/views/Charge.vue diff --git a/app/Http/Controllers/Api/TrafficController.php b/app/Http/Controllers/Api/TrafficController.php index 66fb080..6a2a292 100644 --- a/app/Http/Controllers/Api/TrafficController.php +++ b/app/Http/Controllers/Api/TrafficController.php @@ -3,10 +3,94 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Support\WHMCS; use Illuminate\Support\Facades\Cache; +use Illuminate\Http\Request; + class TrafficController extends Controller { + + public function price() + { + return $this->success([ + 'price_per_gb' => config('settings.price_per_gb') + ]); + } + + public function providers() + { + $config = config('whmcs'); + + // 获取 config 的所有的 key + $providers = array_keys($config); + + return $this->success($providers); + } + + public function payments(Request $request, $provider) + { + try { + $whmcs = new WHMCS($provider); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + + $payments = $whmcs->getPayments(); + + return $this->success($payments); + } + + public function index(Request $request) + { + $user = auth()->user(); + + $traffic = $user->traffic ?? 0; + + $day = now()->day; + $last_sign_at = Cache::get('traffic_sign:' . $day . '-' . $user->id, null); + + return $this->success([ + 'traffic' => $traffic, + 'is_signed' => $last_sign_at + ]); + } + + public function charge(Request $request, string $provider) + { + $request->validate([ + 'payment' => 'required', + 'traffic' => 'required|integer|min:1' + ]); + + $price = bcmul(config('settings.price_per_gb'), $request->input('traffic'), 2); + + + try { + $whmcs = new WHMCS($provider); + } catch (\Exception $e) { + return $this->error('提供商不存在'); + } + + if (!$whmcs->hasPayment($request->input('payment'))) { + return $this->notFound('支付方式不存在'); + } + + $user = $request->user(); + + try { + $result = $whmcs->api_addTraffic($user->email, $request->input('payment'), $request->input('traffic'), $price); + + return $this->success($result); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + + + + } + + public function free() { $user = auth()->user(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 7d16dd9..3334bca 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -4,29 +4,30 @@ use App\Http\Middleware\Admin; use App\Http\Middleware\ApiToken; -use App\Http\Middleware\Authenticate; -use App\Http\Middleware\EncryptCookies; -use App\Http\Middleware\PreventRequestsDuringMaintenance; -use App\Http\Middleware\RedirectIfAuthenticated; +use App\Http\Middleware\WHMCSApi; use App\Http\Middleware\TrimStrings; +use App\Http\Middleware\Authenticate; use App\Http\Middleware\TrustProxies; -use App\Http\Middleware\ValidateSignature; +use App\Http\Middleware\EncryptCookies; use App\Http\Middleware\VerifyCsrfToken; -use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Illuminate\Auth\Middleware\Authorize; -use Illuminate\Auth\Middleware\EnsureEmailIsVerified; -use Illuminate\Auth\Middleware\RequirePassword; -use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; -use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; -use Illuminate\Foundation\Http\Middleware\ValidatePostSize; +use App\Http\Middleware\ValidateSignature; use Illuminate\Http\Middleware\HandleCors; +use Illuminate\Auth\Middleware\RequirePassword; use Illuminate\Http\Middleware\SetCacheHeaders; -use Illuminate\Routing\Middleware\SubstituteBindings; -use Illuminate\Routing\Middleware\ThrottleRequests; -use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Session\Middleware\StartSession; +use App\Http\Middleware\RedirectIfAuthenticated; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Foundation\Http\Kernel as HttpKernel; +use Illuminate\Auth\Middleware\EnsureEmailIsVerified; +use Illuminate\Routing\Middleware\SubstituteBindings; +use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; +use App\Http\Middleware\PreventRequestsDuringMaintenance; +use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; +use Illuminate\Foundation\Http\Middleware\ValidatePostSize; +use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; +use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; class Kernel extends HttpKernel @@ -89,5 +90,6 @@ class Kernel extends HttpKernel 'throttle' => ThrottleRequests::class, 'verified' => EnsureEmailIsVerified::class, 'api_token' => ApiToken::class, + 'whmcs_api' => WHMCSApi::class, ]; } diff --git a/app/Http/Middleware/WHMCSApi.php b/app/Http/Middleware/WHMCSApi.php new file mode 100644 index 0000000..1432442 --- /dev/null +++ b/app/Http/Middleware/WHMCSApi.php @@ -0,0 +1,76 @@ +headers->set('Accept', 'application/json'); + + // bearer token + if (!$request->hasHeader('Authorization')) { + return $this->unauthorized('No Authorization header found.'); + } + + $tokens = $request->bearerToken(); + $tokens = explode('|', $tokens); + + if (count($tokens) !== 2) { + return $this->unauthorized('Invalid Authorization header.'); + } + + $whmcs_id = $tokens[0]; + $token = $tokens[1]; + + $whmcs = config('whmcs.' . $whmcs_id); + + if (is_null($whmcs)) { + return $this->unauthorized('Invalid WHMCS ID.'); + } + + + $config_token = config('whmcs.' . $whmcs_id . '.api_token'); + + if ($config_token == null) { + return $this->unauthorized('Token not allowed.'); + } + + if ($token !== $config_token) { + return $this->unauthorized('Invalid token.'); + } + + if ($request->user_id) { + $user = User::where('id', $request->user_id)->first(); + // if user null + if (!$user) { + $http = Http::remote('remote')->asForm(); + $user = $http->get('/users/' . $request->user_id)->json(); + + $user = User::create([ + 'id' => $user['id'], + 'name' => $user['name'], + 'email' => $user['email'], + ]); + } + + Auth::guard('user')->login($user); + } + + return $next($request); + } + + public function unauthorized($message = 'Unauthorized.') + { + return response()->json([ + 'message' => $message, + ], 401); + } +} diff --git a/app/Support/WHMCS.php b/app/Support/WHMCS.php new file mode 100644 index 0000000..d375790 --- /dev/null +++ b/app/Support/WHMCS.php @@ -0,0 +1,140 @@ +config_key = 'whmcs.' . $whmcs_id; + $whmcs = config($this->config_key); + $this->config = $whmcs; + + $this->platform = $whmcs_id; + + if (is_null($whmcs)) { + throw new \Exception('WHMCS config not found'); + } + + $this->url = $whmcs['url']; + $this->username = $whmcs['username'] ?? ''; + $this->password = md5($whmcs['password'] ?? ''); + $this->api_token = $whmcs['api_token']; + $this->payments = $whmcs['payments'] ?? []; + } + + public function hasPayment(string $payment): bool + { + return in_array($payment, array_keys($this->payments)); + } + + public function getPayments(): array + { + $results = []; + + // 获取 config 的所有的 key + $payments = array_keys($this->config['payments'] ?? []); + + foreach ($payments as $payment) { + $results[] = [ + 'name' => $payment, + 'title' => $this->config['payments'][$payment] + ]; + } + + return $results; + } + + private function request($action, $params = []): ?array + { + $params = array_merge([ + 'action' => $action, + 'username' => $this->username, + 'password' => md5($this->password), + 'responsetype' => 'json', + ], $params); + + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->url . '/includes/api.php'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt( + $ch, + CURLOPT_POSTFIELDS, + http_build_query($params) + ); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $response = curl_exec($ch); + curl_close($ch); + + $json = json_decode($response, true); + + throw_if( + is_null($json), + new \Exception('WHMCS response is not valid JSON') + ); + + throw_if( + isset($json['result']) && $json['result'] !== 'success', + new \Exception($json['message']) + ); + + return $json; + } + + public function api($action, $params = []): ?array + { + $params = array_merge([ + 'api_token' => $this->api_token + ], $params); + + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->url . '/modules/addons/PortIOInvoice/api/' . $action . '.php'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt( + $ch, + CURLOPT_POSTFIELDS, + http_build_query($params) + ); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $response = curl_exec($ch); + curl_close($ch); + + $json = json_decode($response, true); + + throw_if( + is_null($json), + new \Exception('WHMCS response is not valid JSON') + ); + + throw_if( + isset($json['status']) && $json['status'] !== true, + new \Exception($json['message'] ?? '未知错误') + ); + + return $json; + } + + public function api_addTraffic(string $email, string $payment, int|float $traffic, int|float|string $price) + { + return $this->api('addTraffic', [ + 'email' => $email, + 'traffic' => $traffic, + 'price' => $price, + 'platform' => $this->platform, + 'payment' => $payment + ]); + } + +} diff --git a/config/settings.php b/config/settings.php index b7149ab..749b4fc 100644 --- a/config/settings.php +++ b/config/settings.php @@ -4,5 +4,6 @@ 'sign' => [ 'max' => 10, 'min' => 1, - ] + ], + 'price_per_gb' => "0.01", ]; diff --git a/config/whmcs.php b/config/whmcs.php new file mode 100644 index 0000000..47ff7a6 --- /dev/null +++ b/config/whmcs.php @@ -0,0 +1,12 @@ + [ + 'url' => 'http://whmcs.test', + 'api_token' => 'abc123456', + 'payments' => [ + 'laeFastPay' => '莱云 快捷支付', + 'mailin' => '邮入', + ] + ] +]; diff --git a/resources/js/components/Menu.vue b/resources/js/components/Menu.vue index 7d1b42e..43e93ad 100644 --- a/resources/js/components/Menu.vue +++ b/resources/js/components/Menu.vue @@ -37,17 +37,22 @@ const items = ref([ route: "index", }, { - name: "签到", - route: "sign", - }, - { - name: "隧道", + name: "穿透隧道", route: "tunnels", }, { name: "创建隧道", route: "tunnels.create", }, + { + name: "签到", + route: "sign", + }, + { + name: "充值", + route: "charge", + }, + { name: "客户端下载", route: "downloads", diff --git a/resources/js/plugins/router.js b/resources/js/plugins/router.js index 0bc93e7..d8c4268 100644 --- a/resources/js/plugins/router.js +++ b/resources/js/plugins/router.js @@ -51,6 +51,14 @@ const routes = [ title: "签到", }, }, + { + path: "/charge", + name: "charge", + component: () => import("../views/Charge.vue"), + meta: { + title: "流量充值", + }, + }, ]; diff --git a/resources/js/views/Charge.vue b/resources/js/views/Charge.vue new file mode 100644 index 0000000..ad19017 --- /dev/null +++ b/resources/js/views/Charge.vue @@ -0,0 +1,155 @@ + + + diff --git a/routes/api.php b/routes/api.php index 179fcf2..c2f237b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -23,9 +23,13 @@ Route::get('traffic', [TrafficController::class, 'free']); Route::post('traffic', [TrafficController::class, 'sign']); + Route::get('price', [TrafficController::class, 'price']); + Route::get('providers', [TrafficController::class, 'providers']); + Route::get('providers/{provider}/payments', [TrafficController::class, 'payments']); + Route::post('providers/{provider}/charge', [TrafficController::class, 'charge']); + }); -Route::prefix('application')->name('application.')->middleware('api_token')->group(function () { +Route::prefix('application')->name('application.')->middleware('whmcs_api')->group(function () { Route::post('users/{user:email}/traffic', [ApplicationUserController::class, 'addTraffic']); }); -