commit 2d902ff624b22ead49c4209c33dfc5605008ab7b Author: NekoMonci12 Date: Thu Feb 13 07:11:24 2025 +0700 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ae96fe7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 NekoMonci12 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f51c980 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ + +# LicenseGate WHMCS +An WHMCS Modules For Selling Software License Integrated With LicenseGate. + +## How To Install +1. Navigate To `WHMCS-ROOT/modules/servers/` and upload the `licensegate` folder there +``` +WHMCS-Root/ + └── modules/ + └── servers/ + └── licensegate/ + ├── licensegate.php + └── whmcs.json +``` +2. Navigate to `Your WHMCS Sites > System Settings > Servers` and Create New Servers +3. Fill The Configuration & Save Your Changes +``` +hostname: YOUR_LICENSEGATE_BACKEND_DOMAIN +password: YOUR_LICENSEGATE_USER_API_KEY +``` +4. Navigate To `Your WHMCS Sites > System Settings > Servers` and Create New Groups +5. Then Choose the created server and press the Add button +6. Navigate to `Your WHMCS Sites > System Settings > Products/Services > Products/Services` +7. Create your Product with the type of Other, Fill the configuration & save it +8. Navigate `Module` tab on your Product, choose for Module Name `LicenseGate` and for the Server Group the group you created in step 5 +9. The Product now is ready to use + +## Authors +- [@nekomonci12](https://www.github.com/nekomonci12) (Module Creator) +- [@DevLeoko](https://github.com/DevLeoko) [(LicenseGate Creator)](https://github.com/DevLeoko/license-gate) diff --git a/clientarea.tpl b/clientarea.tpl new file mode 100644 index 0000000..e359590 --- /dev/null +++ b/clientarea.tpl @@ -0,0 +1,21 @@ +
+ +
+ + diff --git a/licensegate.php b/licensegate.php new file mode 100644 index 0000000..5b29526 --- /dev/null +++ b/licensegate.php @@ -0,0 +1,525 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +**/ + +if(!defined("WHMCS")) { + die("This file cannot be accessed directly"); +} + +use Illuminate\Database\Capsule\Manager as Capsule; + +function licensegate_GetHostname(array $params) { + $hostname = $params['serverhostname']; + if ($hostname === '') throw new Exception('Could not find the panel\'s hostname - did you configure server group for the product?'); + + // For whatever reason, WHMCS converts some characters of the hostname to their literal meanings (- => dash, etc) in some cases + foreach([ + 'DOT' => '.', + 'DASH' => '-', + ] as $from => $to) { + $hostname = str_replace($from, $to, $hostname); + } + + if(ip2long($hostname) !== false) $hostname = 'http://' . $hostname; + else $hostname = ($params['serversecure'] ? 'https://' : 'http://') . $hostname; + + return rtrim($hostname, '/'); +} + +function licensegate_API(array $params, $endpoint, array $data = [], $method = "GET", $dontLog = false) { + $url = licensegate_GetHostname($params) . '/admin/licenses' . $endpoint; + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + curl_setopt($curl, CURLOPT_USERAGENT, "License-Gate"); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + + $headers = [ + "Authorization: " . $params['serverpassword'], + ]; + + if($method === 'POST' || $method === 'PATCH') { + $jsonData = json_encode($data); + curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonData); + if($method === 'POST') { + array_push($headers, "Content-Type: application/json"); + } + } + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + + $response = curl_exec($curl); + $responseData = json_decode($response, true); + $responseData['status_code'] = curl_getinfo($curl, CURLINFO_HTTP_CODE); + + if($responseData['status_code'] === 0 && !$dontLog) logModuleCall("License-Gate", "CURL ERROR", curl_error($curl), ""); + + curl_close($curl); + + if(!$dontLog) logModuleCall("License-Gate", $method . " - " . $url, + isset($data) ? json_encode($data) : "", + print_r($responseData, true)); + + return $responseData; +} + +function licensegate_Error($func, $params, Exception $err) { + logModuleCall("License-Gate", $func, $params, $err->getMessage(), $err->getTraceAsString()); +} + +function licensegate_MetaData() { + return [ + "DisplayName" => "License-Gate", + "APIVersion" => "1.1", + "RequiresServer" => true, + ]; +} + +function licensegate_ConfigOptions() { + return [ + "notes" => [ + "FriendlyName" => "License Notes", + "Description" => "", + "Type" => "text", + "Size" => 10, + ], + "vlimit" => [ + "FriendlyName" => "Max Tokens", + "Description" => "", + "Type" => "text", + "Size" => 10, + ], + "limit" => [ + "FriendlyName" => "IP Limit", + "Description" => "", + "Type" => "text", + "Default" => "1", + "Size" => 10, + ], + "vtokens" => [ + "FriendlyName" => "Validation Tokens", + "Description" => "", + "Type" => "text", + "Size" => 10, + ], + "scope" => [ + "FriendlyName" => "License Scope", + "Description" => "", + "Type" => "text", + "Size" => 10, + ], + "rinterval" => [ + "FriendlyName" => "Replenish Interval", + "Description" => "", + "Type" => "dropdown", + "Options" => [ + "TEN_SECONDS" => "10 Seconds", + "MINUTE" => "1 Minute", + "HOUR" => "Hourly", + "DAY" => "Daily", + ], + ], + ]; +} + +function licensegate_GetOption(array $params, $id, $default = NULL) { + $options = licensegate_ConfigOptions(); + + $friendlyName = $options[$id]['FriendlyName']; + if(isset($params['configoptions'][$friendlyName]) && $params['configoptions'][$friendlyName] !== '') { + return $params['configoptions'][$friendlyName]; + } else if(isset($params['configoptions'][$id]) && $params['configoptions'][$id] !== '') { + return $params['configoptions'][$id]; + } else if(isset($params['customfields'][$friendlyName]) && $params['customfields'][$friendlyName] !== '') { + return $params['customfields'][$friendlyName]; + } else if(isset($params['customfields'][$id]) && $params['customfields'][$id] !== '') { + return $params['customfields'][$id]; + } + + $found = false; + $i = 0; + foreach(licensegate_ConfigOptions() as $key => $value) { + $i++; + if($key === $id) { + $found = true; + break; + } + } + + if($found && isset($params['configoption' . $i]) && $params['configoption' . $i] !== '') { + return $params['configoption' . $i]; + } + + return $default; +} + +function licensegate_TestConnection(array $params) { + $solutions = [ + 0 => "Check module debug log for more detailed error.", + 401 => "Authorization header either missing or not provided.", + 403 => "Double check the password (which should be the API Key).", + 404 => "Result not found.", + 422 => "Validation error.", + 500 => "Panel errored, check panel logs.", + ]; + + $err = ""; + try { + $response = licensegate_API($params, '/key/WHMCS', [], 'GET'); + + if($response['status_code'] !== 200) { + $status_code = $response['status_code']; + $err = "Invalid status_code received: " . $status_code . ". Possible solutions: " + . (isset($solutions[$status_code]) ? $solutions[$status_code] : "None."); + } else { + $err = "Authentication successful"; + } + } catch(Exception $e) { + licensegate_Error(__FUNCTION__, $params, $e); + $err = $e->getMessage(); + } + + return [ + "success" => $err === "", + "error" => $err, + ]; +} + +function licensegate_GetKey($licenseKey) { + $target = '/key/' . $licenseKey; + $err = ""; + + try { + $response = licensegate_API($params, $target, [], 'GET'); + + if ($response['status_code'] !== 200) { + $status_code = $response['status_code']; + $solutions = [ + 400 => "Bad request - Check the input data.", + 401 => "Unauthorized - Check authentication credentials.", + 404 => "Not found - Ensure the licenseKey exists.", + 500 => "Server error - Try again later." + ]; + $err = "Invalid status_code received: " . $status_code . ". Possible solutions: " + . ($solutions[$status_code] ?? "None."); + } elseif ($response['meta']['pagination']['count'] === 0) { + $err = "Authentication successful, but no nodes are available."; + } + } catch (Exception $e) { + licensegate_Error(__FUNCTION__, [], $e); + $err = $e->getMessage(); + } + $jsonData = json_decode($response, true); + return [ + "success" => $err === "", + "error" => $err, + "id" => $jsonData['id'] ?? null, + ]; +} + +function licensegate_GenerateKey($inputString) { + $licenseHashed = md5($inputString); + $licenseObfuscated = substr($licenseHashed, 0, 12) + . substr(strrev($licenseHashed), 10, 4) + . strrev(substr($licenseHashed, 20, 12)); + $licenseFormatted = substr($licenseObfuscated, 0, 5) . '-' . + substr($licenseObfuscated, 5, 5) . '-' . + substr($licenseObfuscated, 10, 4) . '-' . + substr($licenseObfuscated, 14, 7) . '-' . + substr($licenseObfuscated, 21, 7); + return strtoupper($licenseFormatted); +} + +function licensegate_CreateAccount(array $params) +{ + try { + $name = $params['clientsdetails']['firstname'] . ' ' . $params['clientsdetails']['lastname']; + $active = true; + $notes = licensegate_GetOption($params, 'notes', 'Created From WHMCS'); + $limit = licensegate_GetOption($params, 'limit'); + $scope = licensegate_GetOption($params, 'scope'); + $vtokens = licensegate_GetOption($params, 'vtokens', $limit * 3); + $vlimit = licensegate_GetOption($params, 'vlimit', $vtokens * 3); + $rinterval = licensegate_GetOption($params, 'rinterval', 'HOUR'); + $endpoint = ""; + + $inputString = $params['serviceid'] . '-' . $params['username']; + $licenseKey = licensegate_GenerateKey($inputString); + + $data = [ + "active" => $active, + "name" => $name, + "notes" => $notes, + "ipLimit" => $limit, + "licenseScope" => $scope, + "expirationDate" => "9999-12-31T23:59:59", + "validationPoints" => $vtokens, + "validationLimit" => $vlimit, + "replenishAmount" => $vtokens, + "replenishInterval" => $rinterval, + "licenseKey" => $licenseKey, + ]; + + $response = licensegate_API($params, $endpoint, $data, "POST"); + + if ($response['status_code'] !== 201) { + throw new Exception("Failed to execute command. Status code: {$response['status_code']}"); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_SuspendAccount(array $params) +{ + try { + $inputString = $params['serviceid'] . '-' . $params['username']; + $keyResponse = licensegate_GetKey(licensegate_GenerateKey($inputString)); + if ($keyResponse['success']) { + $endpoint = '/' . $keyResponse['id']; + } else { + throw new Exception("Failed to check account. Status code: {$response['status_code']}"); + } + + $data = [ + "active" => false, + ]; + + $response = licensegate_API($params, $endpoint, $data, "PATCH"); + + if ($response['status_code'] !== 200) { + throw new Exception("Failed to suspend account. Status code: {$response['status_code']}"); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_UnsuspendAccount(array $params) +{ + try { + $inputString = $params['serviceid'] . '-' . $params['username']; + $keyResponse = licensegate_GetKey(licensegate_GenerateKey($inputString)); + if ($keyResponse['success']) { + $endpoint = '/' . $keyResponse['id']; + } else { + throw new Exception("Failed to check account. Status code: {$response['status_code']}"); + } + + $data = [ + "active" => true, + ]; + + $response = licensegate_API($params, $endpoint, $data, "PATCH"); + + if ($response['status_code'] !== 200) { + throw new Exception("Failed to unsuspend account. Status code: {$response['status_code']}"); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_TerminateAccount(array $params) +{ + try { + $inputString = $params['serviceid'] . '-' . $params['username']; + $keyResponse = licensegate_GetKey(licensegate_GenerateKey($inputString)); + if ($keyResponse['success']) { + $endpoint = '/' . $keyResponse['id']; + } else { + throw new Exception("Failed to check account. Status code: {$response['status_code']}"); + } + + $response = licensegate_API($params, $endpoint, [], "DELETE"); + + if ($response['status_code'] !== 200) { + throw new Exception("Failed to terminate account. Status code: {$response['status_code']}"); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_ChangePassword(array $params) +{ + try { + if($params['password'] === '') throw new Exception('The password cannot be empty.'); + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_ChangePackage(array $params) +{ + try { + $active = true; + $notes = licensegate_GetOption($params, 'notes', 'Created From WHMCS'); + $limit = licensegate_GetOption($params, 'limit'); + $scope = licensegate_GetOption($params, 'scope'); + $vtokens = licensegate_GetOption($params, 'vtokens', $limit * 3); + $vlimit = licensegate_GetOption($params, 'vlimit', $vtokens * 3); + $rinterval = licensegate_GetOption($params, 'rinterval', 'HOUR'); + + $inputString = $params['serviceid'] . '-' . $params['username']; + $keyResponse = licensegate_GetKey(licensegate_GenerateKey($inputString)); + if ($keyResponse['success']) { + $endpoint = '/' . $keyResponse['id']; + } else { + throw new Exception("Failed to check account. Status code: {$response['status_code']}"); + } + + $data = [ + "active" => $active, + "notes" => $notes, + "ipLimit" => $limit, + "licenseScope" => $scope, + "expirationDate" => "9999-12-31T23:59:59", + "validationPoints" => $vtokens, + "validationLimit" => $vlimit, + "replenishAmount" => $vtokens, + "replenishInterval" => $rinterval, + ]; + + $response = licensegate_API($params, $endpoint, $data, "PATCH"); + + if ($response['status_code'] !== 200) { + throw new Exception("Failed to update account. Status code: {$response['status_code']}"); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_Renew(array $params) +{ + try { + $inputString = $params['serviceid'] . '-' . $params['username']; + if ($keyResponse['success']) { + $endpoint = '/' . $keyResponse['id']; + } else { + throw new Exception("Failed to check account. Status code: {$response['status_code']}"); + } + + $checker = licensegate_API($params, $endpoint, [], 'GET'); + + if($checker['status_code'] == 200) { + $active = true; + $notes = licensegate_GetOption($params, 'notes', 'Created From WHMCS'); + $limit = licensegate_GetOption($params, 'limit'); + $scope = licensegate_GetOption($params, 'scope'); + $vtokens = licensegate_GetOption($params, 'vtokens', $limit * 3); + $vlimit = licensegate_GetOption($params, 'vlimit', $vtokens * 3); + $rinterval = licensegate_GetOption($params, 'rinterval', 'HOUR'); + + $inputString = $params['serviceid'] . '-' . $params['username']; + $endpoint = licensegate_GenerateKey($inputString); + + $data = [ + "active" => $active, + "notes" => $notes, + "ipLimit" => $limit, + "licenseScope" => $scope, + "expirationDate" => "9999-12-31T23:59:59", + "validationPoints" => $vtokens, + "validationLimit" => $vlimit, + "replenishAmount" => $vtokens, + "replenishInterval" => $rinterval, + ]; + + $response = licensegate_API($params, $endpoint, $data, "PATCH"); + + if ($response['status_code'] !== 200) { + throw new Exception("Failed to execute command. Status code: {$response['status_code']}"); + } + } else { + $name = $params['clientsdetails']['firstname'] . ' ' . $params['clientsdetails']['lastname']; + $active = true; + $notes = licensegate_GetOption($params, 'notes', 'Created From WHMCS'); + $limit = licensegate_GetOption($params, 'limit'); + $scope = licensegate_GetOption($params, 'scope'); + $vtokens = licensegate_GetOption($params, 'vtokens', $limit * 3); + $vlimit = licensegate_GetOption($params, 'vlimit', $vtokens * 3); + $rinterval = licensegate_GetOption($params, 'rinterval', 'HOUR'); + $endpoint = ""; + + $inputString = $params['serviceid'] . '-' . $params['username']; + $licenseKey = licensegate_GenerateKey($inputString); + + $data = [ + "active" => $active, + "name" => $name, + "notes" => $notes, + "ipLimit" => $limit, + "licenseScope" => $scope, + "expirationDate" => "9999-12-31T23:59:59", + "validationPoints" => $vtokens, + "validationLimit" => $vlimit, + "replenishAmount" => $vtokens, + "replenishInterval" => $rinterval, + "licenseKey" => $licenseKey, + ]; + + $response = licensegate_API($params, $endpoint, $data, "POST"); + + if ($response['status_code'] !== 201) { + throw new Exception("Failed to execute command. Status code: {$response['status_code']}"); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return 'success'; +} + +function licensegate_ClientArea($params) { + $inputString = $params['serviceid'] . '-' . $params['username']; + $licenseKey = licensegate_GenerateKey($inputString); + + return array( + 'tabOverviewReplacementTemplate' => 'templates/clientarea.tpl', + 'templateVariables' => array( + 'licensesKey' => $licenseKey + ), + ); +} diff --git a/whmcs.json b/whmcs.json new file mode 100644 index 0000000..dc4e858 --- /dev/null +++ b/whmcs.json @@ -0,0 +1,21 @@ +{ + "schema": "1.0", + "type": "whmcs-servers", + "name": "licensegate", + "license": "proprietary", + "category": "provisioning", + "description": { + "name": "License Gate", + "tagline": "An LicenseGate WHMCS Module.", + "long": "" + }, + "support": { + "docs_url": "https://dsc.gg/yuemi" + }, + "authors": [ + { + "name": "YueMi-Development", + "homepage": "https:\/\/yuemi.org\/" + } + ] + } \ No newline at end of file