@extends('layouts.front') @section('meta') @endsection @section('title', 'License System — সম্পূর্ণ ডকুমেন্টেশন') @section('contents')
{{-- Permalink --}} {{-- Hero --}}

License System — সম্পূর্ণ ডকুমেন্টেশন

Admin লাইসেন্স ম্যানেজমেন্ট, User পেমেন্ট, Public verify-license API, cache-signal, kill_switch, HMAC security, stolen domain এবং deploy গাইড — সব একসাথে।

Backward Compatible HMAC Security Kill Switch Cache Signal Auto Block
API Base: {{ url('/') }}
{{-- TOC --}}
গুরুত্বপূর্ণ: POST /api/verify-license শুধু routes/web.php থেকে রেজিস্টার (CSRF exempt)। routes/api.php এ ডুপ্লিকেট রুট রাখবেন না — Laravel API ফাইল আগে লোড হলে পুরনো ক্লায়েন্ট ভুল কন্ট্রোলারে যায়।
Rate limit: পাবলিক API — throttle:60,1 (প্রতি IP প্রতি মিনিটে ৬০ রিকোয়েস্ট)। সীমা ছাড়ালে HTTP 429।
{{-- ========== OVERVIEW ========== --}}

সিস্টেম ওভারভিউ

🏢
Admin Panel
লাইসেন্স তৈরি, ম্যানেজ, ব্লক
👤
User Panel
পেমেন্ট করে লাইসেন্স ও নবায়ন
🌐
Public API
POST /api/verify-license
এন্ডপয়েন্টকন্ট্রোলারমেথড
POST /api/verify-licenseAdmin\LicenseControllerverifyLicense
POST /api/log-unauthorizedApi\LicenseSecurityApiControllerlogUnauthorized
GET /api/cache-signalApi\LicenseSecurityApiControllercacheSignal
POST /api/check-updateApi\CheckUpdateControllercheckUpdate (+ signature)
POST /api/download-updateApi\DownloadUpdateControllerdownload
InputNormalized Output
https://www.example.com/pathexample.com
http://example.com/example.com
www.EXAMPLE.COMexample.com
Example.Comexample.com

HMAC signature এই normalize করা স্ট্রিং দিয়েই গণনা করতে হবে।

{{-- ========== DATABASE ========== --}}

ডাটাবেস কাঠামো

📦 licenses — মূল লাইসেন্স ডেটা

ColumnTypeবিবরণ
user_idbigintলাইসেন্সের মালিক (users)
domain_namevarcharNormalized ডোমেইন (www/https ছাড়া, lowercase)
license_keyvarcharFormat: LIC-XXXX-XXXX-XXXX
expiry_datedatetimeমেয়াদ শেষের তারিখ
statusint1 = Active 0 = Inactive

🛡️ stolen_domains — চুরি লগ

Columnবিবরণ
domain_nameচেষ্টা করা ডোমেইন
ip_addressআক্রমণকারীর IP
cache_signalRandom token — পরিবর্তন হলে ক্লায়েন্ট cache মুছবে
is_blocked1 = Blocked 0 = Unblocked
status0 = Pending 1 = Resolved

☠️ unauthorized_logs — Kill Switch লগ

Columnবিবরণ
domainডোমেইন নাম
ipIP ঠিকানা
reasonকারণ
attempted_atসময়
kill_switch1 = wipe_out চালু 0 = বন্ধ

🔄 license_renewal_orders — নবায়ন অর্ডার

Columnবিবরণ
license_idকোন লাইসেন্সের জন্য
amountপেমেন্টের পরিমাণ
invoice_idUddoktaPay Invoice ID
statuspending / paid / cancelled
{{-- ========== VERIFY LICENSE ========== --}}

POST /api/verify-license

লাইসেন্স বৈধতা যাচাই। বডিতে signature থাকলে → HMAC মোড। না থাকলে → Legacy মোড। সব response এ action থাকে।

POST {{ url('/api/verify-license') }}
Legacy মোড (পুরনো ক্লায়েন্ট)
  • signature ফিল্ড নেই বা খালি
  • Invalid হলে stolen_domains এ auto-block
  • Valid হলে is_blocked = 0 (auto-unblock)
  • Response এ cache_signal ও secure_token
  • HTTP 200 (status JSON এ)
HMAC মোড (নতুন ক্লায়েন্ট)
  • signature = hash_hmac('sha256', domain, HMAC_SALT)
  • Invalid হলে stolen_domains block হয় না
  • Valid হলে is_blocked = 0 (auto-unblock)
  • Response এ cache_signal ও secure_token
  • Signature ব্যর্থ হলে HTTP 403
ফিল্ডটাইপনোট
domainstringRequired সার্ভার normalize করে।
license_keystringRequired যেমন LIC-XXXX-XXXX-XXXX
urlstringOptional Legacy মোডে stolen log এ সেভ হয়।
signaturestringOptional থাকলে HMAC মোড চালু হয়।
ফিল্ডমানবিবরণ
statusvalid / invalidলাইসেন্স বৈধ কিনা
blockedtrue / falseডোমেইন blocked কিনা
secure_tokenmd5 hashmd5(domain + TOKEN_SALT)
cache_signalrandom string / nullপরিবর্তন হলে ক্লায়েন্ট cache মুছবে
actionnone / wipe_outwipe_out হলে সাইট disable করতে হবে
200 — Valid (Legacy ও HMAC উভয়)
{
  "status": "valid",
  "blocked": false,
  "domain_name": "example.com",
  "license_key": "LIC-ABCD-EFGH-IJKL",
  "expiry_date": "2028-01-15 00:00:00",
  "secure_token": "a1b2c3d4e5f6...",
  "cache_signal": "xK9mP2rT...",
  "action": "none"
}
200 — Invalid (Legacy, auto-block হবে)
{
  "status": "invalid",
  "blocked": true,
  "message": "License key or domain is incorrect or has expired.",
  "action": "none"
}
403 — HMAC Signature ব্যর্থ
{
  "status": "invalid",
  "message": "Signature verification failed.",
  "action": "none"
}
curl -X POST "{{ url('/api/verify-license') }}" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","license_key":"LIC-YOUR-KEY","url":"https://example.com"}'
curl -X POST "{{ url('/api/verify-license') }}" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","license_key":"LIC-YOUR-KEY","signature":"HMAC_SHA256_HEX_HERE"}'
HMAC Salt: LicenseController এ — HMAC_SALT = 'your_secret_salt_key' এবং TOKEN_SALT = 'YOUR_SECRET_SALT_HERE_12345'। উৎপাদন ব্যবহারের আগে পরিবর্তন করুন।
{{-- ========== CACHE SIGNAL ========== --}}

GET /api/cache-signal

Background এ poll করুন (যেমন প্রতি ৩০ মিনিট)। signal বদলালে local cache মুছে পুনরায় verify করুন। প্রতিটি response এ action থাকে (kill_switch একই নিয়ম)।

GET {{ url('/api/cache-signal') }}?domain=example.com
domain নেই
{ "signal": null, "action": "none" }
রেকর্ড নেই বা cache_signal খালি
{ "signal": "default", "blocked": false, "action": "none" }
Blocked ডোমেইন
{ "signal": "…token…", "blocked": true, "action": "wipe_out" }
Signal আছে, blocked নেই
{ "signal": "xK9mP2rT...", "blocked": false, "action": "none" }
Admin Cache Clear: Admin → Stolen Reports → ⚡ বাটন → stolen_domains.cache_signal নতুন random token পায়। পরের বার ক্লায়েন্ট poll করলে signal পরিবর্তিত দেখে cache মুছবে।
{{-- ========== CHECK UPDATE ========== --}}

POST /api/check-update

নতুন লাইসেন্স: license_key + signature (LICENSE_SIGNATURE) বাধ্যতামূলক। বৈধ হলে script_updates থেকে version_compare।

POST {{ url('/api/check-update') }}
ফিল্ডবাধ্যতাবিবরণ
domainRequiredNormalize হয়।
license_keyRequiredLICENSE_KEY
signatureRequiredLICENSE_SIGNATURE — DB signature_key
script_nameRequiredAdmin আপলোডের নামের সাথে হুবহু মিল।
current_versionOptionalDefault 1.0.0।

লাইসেন্স/সিগনেচার ব্যর্থ → HTTP 403। আপডেট থাকলে download_url = POST /api/download-update।

→ সম্পূর্ণ Script Update গাইড

POST /api/download-update

ZIP ডাউনলোড — একই domain, license_key, signature, script_name।

POST {{ url('/api/download-update') }}

সফল → ZIP স্ট্রিম। Rate limit: 30/min।

{{-- ========== LOG UNAUTHORIZED ========== --}}

POST /api/log-unauthorized

বডিতে reason ও attempted_at উভয়ই থাকলে নতুন ফরম্যাট (unauthorized_logs)। নইলে Legacy (stolen_domains updateOrCreate)।

POST {{ url('/api/log-unauthorized') }}
ফিল্ডবাধ্যতা
domainRequired
ipOptional
urlOptional
Response 200
{ "status": "success" }
ফিল্ডবাধ্যতা
domainRequired
reasonRequired
attempted_atRequired
ipOptional
urlOptional
Response 201
{ "status": "ok", "message": "লগ সংরক্ষিত হয়েছে।" }
{{-- ========== KILL SWITCH ========== --}}

Kill Switch — action: wipe_out

UnauthorizedLog::killSwitchWantsWipeOut($domain) — unauthorized_logs এ ওই ডোমেইনের kill_switch = true সারি থাকলে সব API response এ "action": "wipe_out"।

APIkill_switch চেক হয়
POST /api/verify-licenseহ্যাঁ — সব মোডে
GET /api/cache-signalহ্যাঁ — সব response এ

Stolen Reports → ☠️ Kill Switch বাটন → Toggle on/off।

→ Admin Stolen Reports খুলুন
⚠️ সতর্কতা: action: wipe_out পেলে ক্লায়েন্ট সাইট নিজেকে disable করবে। এটি অপরিবর্তনীয় ক্ষতি করতে পারে। শুধু বিশ্বস্ত পরিস্থিতিতে চালু করুন।
{{-- ========== STOLEN DOMAIN ========== --}}

Stolen Domain সিস্টেম

Invalid লাইসেন্স দিয়ে প্রবেশের চেষ্টা স্বয়ংক্রিয়ভাবে log হয় এবং ডোমেইন block হয়।

  • ক্লায়েন্ট verify করে → invalid license_key বা domain
  • stolen_domains এ log → is_blocked = 1, নতুন cache_signal
  • Admin দেখতে পান → Stolen Reports প্যানেলে
  • Admin Block/Unblock করেন → domain এর is_blocked toggle
  • Valid verify হলে → is_blocked = 0 (auto-unblock)
বাটনকাজ
⚡ Cache Clearনতুন cache_signal পাঠানো — ক্লায়েন্ট ৩০ মিনিটের মধ্যে cache মুছবে
🔒 Blockis_blocked = 1 → ক্লায়েন্ট সাইট বন্ধ
🔓 Unblockis_blocked = 0 → ক্লায়েন্ট সাইট চালু
☠️ Kill Switchunauthorized_logs.kill_switch toggle → action: wipe_out
🔄 StatusPending ↔ Resolved
🗑️ Deleteরিপোর্ট মুছা
সতর্কতা: ভুল key দিয়ে বারবার চেষ্টা করলে domain auto-block হয়। ক্লায়েন্টে প্রথমেই সঠিক key ও domain নিশ্চিত করুন।
{{-- ========== ADMIN PANEL ========== --}}

Admin প্যানেল

নতুন লাইসেন্স তৈরি

URL: /admin/licenses — ইউজার + ডোমেইন + মেয়াদ (১/২/৩ বছর) → LIC-XXXX-XXXX-XXXX তৈরি হয়।

রিনিউয়াল অর্ডার ম্যানেজমেন্ট

URL: /admin/license-renewal-orders

Actionফলাফল
Mark as Paidলাইসেন্স +১ বছর extend, status = paid
Mark as Unpaidপূর্বের অবস্থায় ফেরানো

User কে বিনামূল্যে লাইসেন্স অনুমতি দেওয়া

-- phpMyAdmin এ:
UPDATE users SET free_license_generation = 1 WHERE id = USER_ID;
{{-- ========== USER PANEL ========== --}}

User প্যানেল

ক্লায়েন্ট নিজে লাইসেন্স কিনতে ও নবায়ন করতে পারে।

URLকাজ
/my-licensesসকল লাইসেন্স দেখা
/license/generateনতুন লাইসেন্স কেনা (UddoktaPay বা বিনামূল্যে)
/license/renew/{id}মেয়াদ শেষে নবায়ন করা
/license/update/{id}ডোমেইন পরিবর্তন
/license/delete/{id}লাইসেন্স মুছা
মূল্য হিসাব: license_generation_price × মেয়াদ (বছর)। উদাহরণ: ১০০০ টাকা × ২ বছর = ২০০০ টাকা।
{{-- ========== DEPLOY ========== --}}

Server Deploy — phpMyAdmin SQL

SSH না থাকলে নিচের SQL গুলো phpMyAdmin → SQL ট্যাবে paste করে Execute করুন।

-- ১. stolen_domains এ নতুন column
ALTER TABLE `stolen_domains`
  ADD COLUMN IF NOT EXISTS `cache_signal` varchar(255) DEFAULT NULL,
  ADD COLUMN IF NOT EXISTS `is_blocked` tinyint(1) NOT NULL DEFAULT 0;

-- ২. users এ free_license_generation
ALTER TABLE `users`
  ADD COLUMN IF NOT EXISTS `free_license_generation` tinyint(1) NOT NULL DEFAULT 0;

-- ৩. generalsettings এ license price
ALTER TABLE `generalsettings`
  ADD COLUMN IF NOT EXISTS `license_generation_price` decimal(10,2) DEFAULT 1000.00;

-- ৪. unauthorized_logs টেবিল
CREATE TABLE IF NOT EXISTS `unauthorized_logs` (
  `id`           bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `domain`       varchar(255) NOT NULL,
  `ip`           varchar(255) DEFAULT NULL,
  `reason`       text NOT NULL,
  `url`          text DEFAULT NULL,
  `attempted_at` timestamp NOT NULL,
  `kill_switch`  tinyint(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ৫. license_renewal_orders টেবিল
CREATE TABLE IF NOT EXISTS `license_renewal_orders` (
  `id`         bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id`    bigint(20) UNSIGNED NOT NULL,
  `license_id` bigint(20) UNSIGNED NOT NULL,
  `amount`     decimal(10,2) NOT NULL DEFAULT 1000.00,
  `invoice_id` varchar(255) DEFAULT NULL,
  `status`     varchar(255) NOT NULL DEFAULT 'pending',
  `paid_at`    timestamp NULL DEFAULT NULL,
  `notes`      text DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
php artisan migrate
php artisan config:cache
php artisan route:cache
php artisan cache:clear
{{-- ========== CLIENT CODE ========== --}}

Client Site Integration

Legacy পদ্ধতি (পুরনো সাইট)

<?php
function verifyLicense($domain, $licenseKey, $serverUrl) {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $serverUrl . '/api/verify-license',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => http_build_query([
            'domain'      => $domain,
            'license_key' => $licenseKey,
            'url'         => (isset($_SERVER['HTTPS']) ? 'https' : 'http')
                             . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
        ]),
        CURLOPT_TIMEOUT => 10,
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

$result = verifyLicense('example.com', 'LIC-ABCD-EFGH-IJKL', '{{ url('/') }}');

if ($result['status'] === 'valid') {
    if ($result['action'] === 'wipe_out') {
        die('Site disabled by administrator.');
    }
    // cache_signal চেক
    $saved = @file_get_contents('license_signal.txt');
    if ($saved !== ($result['cache_signal'] ?? '')) {
        // local cache মুছুন
        file_put_contents('license_signal.txt', $result['cache_signal'] ?? '');
    }
} else {
    die('License invalid or domain blocked.');
}

HMAC পদ্ধতি (নতুন সাইট)

<?php
define('LICENSE_SERVER', '{{ url('/') }}');
define('LICENSE_DOMAIN',  'example.com');
define('LICENSE_KEY',     'LIC-ABCD-EFGH-IJKL');
define('HMAC_SALT',       'your_secret_salt_key'); // server এর HMAC_SALT

function verifyLicenseHmac() {
    $domain    = LICENSE_DOMAIN;
    $signature = hash_hmac('sha256', $domain, HMAC_SALT);

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => LICENSE_SERVER . '/api/verify-license',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => http_build_query([
            'domain'      => $domain,
            'license_key' => LICENSE_KEY,
            'signature'   => $signature,
        ]),
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_SSL_VERIFYPEER => false,
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

$result = verifyLicenseHmac();

if (!$result || $result['status'] !== 'valid') {
    die('License verification failed.');
}

if ($result['action'] === 'wipe_out') {
    die('This installation has been remotely deactivated.');
}

// Cache signal চেক (background polling)
$cacheFile   = __DIR__ . '/.license_signal';
$savedSignal = file_exists($cacheFile) ? trim(file_get_contents($cacheFile)) : '';
if (!empty($result['cache_signal']) && $savedSignal !== $result['cache_signal']) {
    if (function_exists('opcache_reset')) opcache_reset();
    file_put_contents($cacheFile, $result['cache_signal']);
}

// ✅ License valid
{{-- ========== TROUBLESHOOT ========== --}}

Troubleshooting

সমস্যা ১: পুরনো সাইট HTTP 500 দিচ্ছে

কারণ: unauthorized_logs টেবিল নেই। উপরের Deploy SQL এ ৪ নম্বর SQL রান করুন।

সমস্যা ২: সাইট blocked কিন্তু license valid

UPDATE stolen_domains SET is_blocked = 0 WHERE domain_name = 'example.com';

অথবা Admin → Stolen Reports → 🔓 Unblock।

সমস্যা ৩: Kill Switch বন্ধ করা যাচ্ছে না

UPDATE unauthorized_logs SET kill_switch = 0 WHERE domain = 'example.com';

সমস্যা ৪: kill_switch column নেই

ALTER TABLE `unauthorized_logs` ADD COLUMN `kill_switch` tinyint(1) NOT NULL DEFAULT 0;

সমস্যা ৫: Domain normalize মিলছে না

License edit করে domain আবার Save করুন — স্বয়ংক্রিয়ভাবে normalize হবে।

{{-- ========== ROUTES ========== --}}

সব Routes রেফারেন্স

MethodURLকাজ
POST/api/verify-licenseলাইসেন্স ভেরিফিকেশন
GET/api/cache-signalCache Signal চেক
POST/api/log-unauthorizedUnauthorized লগ
POST/api/check-updateআপডেট চেক (license + signature)
POST/api/download-updateআপডেট ZIP
MethodURLকাজ
GET/admin/licensesলাইসেন্স লিস্ট
POST/admin/licenses/storeনতুন লাইসেন্স তৈরি
POST/admin/licenses/update/{id}লাইসেন্স আপডেট
GET/admin/licenses/delete/{id}লাইসেন্স মুছা
GET/admin/license-renewal-ordersরিনিউয়াল অর্ডার লিস্ট
POST/admin/license-renewal-orders/{id}/mark-paidপেইড মার্ক
GET/admin/stolen-reportsচুরির রিপোর্ট
POST/admin/stolen-reports/clear-cache/{id}Cache Signal আপডেট
POST/admin/stolen-reports/block/{id}Block/Unblock টগল
POST/admin/stolen-reports/kill-switch/{id}Kill Switch টগল
MethodURLকাজ
GET/my-licensesআমার লাইসেন্সসমূহ
GET/license/generateনতুন লাইসেন্স ফর্ম
POST/license/generateলাইসেন্স জেনারেট
GET/license/renew/{id}রিনিউ পেজ
POST/license/update/{id}লাইসেন্স আপডেট
POST/license/delete/{id}লাইসেন্স মুছা
POST/license/renew/webhookUddoktaPay Webhook
{{-- Integration Checklist --}}

Integration চেকলিস্ট

  1. Install/load এ POST /api/verify-license — পুরনো: শুধু domain + license_key। নতুন: normalize করা domain এ HMAC + signature।
  2. প্রতিটি response এ action পড়ুন — wipe_out হলে নীতি প্রয়োগ করুন।
  3. GET /api/cache-signal?domain=… poll (৩০ মিনিট) — signal বদলালে local cache মুছে পুনরায় verify।
  4. সন্দেহজনক ব্যবহার: POST /api/log-unauthorized — legacy বা নতুন ফরম্যাটে।
  5. আপডেট: POST /api/check-update — LICENSE_KEY + LICENSE_SIGNATURE পাঠান; গাইড: Script Update System।
  6. LICENSE_REMOTE_BASE_URL = লাইসেন্স সার্ভারের root URL।
@endsection