diff --git a/README.md b/README.md
index 6dcd87f83..00dce7845 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,7 @@ journalctl -u lscpd -f
## Recent fixes
+* **02.02.2026** — Plugin updates: premiumPlugin & paypalPremiumPlugin unified verification (Plugin Grants, activation key, Patreon, PayPal, AES-256-CBC encryption). Installed Plugins UI: bulk activate/deactivate, freshness badges, removed Patreon messaging from front.
* **15.11.2025** — Hardened MySQL password rotation: `mysqlUtilities.changePassword` now auto-resolves the backing MySQL account (user + host) even when `DBUsers` metadata is missing, preventing the historical `[mysqlUtilities.changePassword] can only concatenate str (not "int")` error. Regression tests live under `Test/mysqlUtilities/`, and you should restart `lscpd` after deploying the patch so the helper reloads.
---
diff --git a/paypalPremiumPlugin/README.md b/paypalPremiumPlugin/README.md
new file mode 100644
index 000000000..922bffe2e
--- /dev/null
+++ b/paypalPremiumPlugin/README.md
@@ -0,0 +1,58 @@
+# Premium Plugin Example
+
+An example paid plugin for CyberPanel that demonstrates how to implement Patreon subscription-based plugin access.
+
+## Features
+
+- Requires Patreon subscription to "CyberPanel Paid Plugin" tier
+- Users can install the plugin without subscription
+- Plugin functionality is locked until subscription is verified
+- Shows subscription required page when accessed without subscription
+
+## Installation
+
+1. Upload the plugin ZIP file to CyberPanel
+2. Install the plugin from the plugin manager
+3. The plugin will appear in the installed plugins list
+
+## Usage
+
+### For Users Without Subscription
+
+- Plugin can be installed
+- When accessing the plugin, a subscription required page is shown
+- Link to Patreon subscription page is provided
+
+### For Users With Subscription
+
+- Plugin works normally
+- All features are accessible
+- Settings page is available
+
+## Configuration
+
+The plugin checks for Patreon membership via the Patreon API. Make sure to configure:
+
+1. Patreon Client ID
+2. Patreon Client Secret
+3. Patreon Creator ID
+
+These should be set in CyberPanel environment variables or settings.
+
+## Meta.xml Structure
+
+The plugin uses the following meta.xml structure for paid plugins:
+
+```xml
+true
+CyberPanel Paid Plugin
+https://www.patreon.com/c/newstargeted/membership
+```
+
+## Author
+
+master3395
+
+## License
+
+MIT
diff --git a/paypalPremiumPlugin/__init__.py b/paypalPremiumPlugin/__init__.py
new file mode 100644
index 000000000..f80c90067
--- /dev/null
+++ b/paypalPremiumPlugin/__init__.py
@@ -0,0 +1,4 @@
+# PayPal Premium Plugin Example
+# This is a paid plugin that requires PayPal payment
+
+default_app_config = 'paypalPremiumPlugin.apps.PaypalpremiumpluginConfig'
diff --git a/paypalPremiumPlugin/api_encryption.py b/paypalPremiumPlugin/api_encryption.py
new file mode 100644
index 000000000..8f2820ab2
--- /dev/null
+++ b/paypalPremiumPlugin/api_encryption.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+"""
+AES-256-CBC encryption for plugin <-> api.newstargeted.com communication.
+Key must match PLUGIN_VERIFICATION_CIPHER_KEY in config.php on the API server.
+"""
+import json
+import base64
+import os
+
+CIPHER_KEY_B64 = '1VLPEKTmLGUbIxHUFEtsuVM2MPN1tl8HPFtyJc4dr58='
+ENCRYPTION_ENABLED = True
+
+_ENCRYPTION_CIPHER_KEY = None
+
+
+def _get_key():
+ global _ENCRYPTION_CIPHER_KEY
+ if _ENCRYPTION_CIPHER_KEY is not None:
+ return _ENCRYPTION_CIPHER_KEY
+ try:
+ key = base64.b64decode(CIPHER_KEY_B64)
+ if len(key) == 32:
+ _ENCRYPTION_CIPHER_KEY = key
+ return key
+ except Exception:
+ pass
+ return None
+
+
+def encrypt_payload(data):
+ if not ENCRYPTION_ENABLED or not _get_key():
+ body = json.dumps(data, separators=(',', ':')).encode('utf-8')
+ return body, {'Content-Type': 'application/json'}
+ try:
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.primitives import padding
+ from cryptography.hazmat.backends import default_backend
+ key = _get_key()
+ plain = json.dumps(data, separators=(',', ':')).encode('utf-8')
+ padder = padding.PKCS7(128).padder()
+ padded = padder.update(plain) + padder.finalize()
+ iv = os.urandom(16)
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
+ encryptor = cipher.encryptor()
+ ciphertext = encryptor.update(padded) + encryptor.finalize()
+ payload = base64.b64encode(iv).decode('ascii') + '.' + base64.b64encode(ciphertext).decode('ascii')
+ return payload.encode('utf-8'), {'Content-Type': 'text/plain', 'X-Encrypted': '1'}
+ except Exception:
+ body = json.dumps(data, separators=(',', ':')).encode('utf-8')
+ return body, {'Content-Type': 'application/json'}
+
+
+def decrypt_response(body_bytes, content_type='', expect_encrypted=False):
+ try:
+ body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes)
+ is_encrypted = (
+ expect_encrypted or
+ ('text/plain' in content_type and '.' in body_str) or
+ ('.' in body_str and body_str.strip() and body_str.strip()[0] not in '{[')
+ )
+ parts = body_str.strip().split('.', 1)
+ if is_encrypted and len(parts) == 2 and ENCRYPTION_ENABLED and _get_key():
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.primitives import padding
+ from cryptography.hazmat.backends import default_backend
+ iv = base64.b64decode(parts[0])
+ ciphertext = base64.b64decode(parts[1])
+ key = _get_key()
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
+ decryptor = cipher.decryptor()
+ padded = decryptor.update(ciphertext) + decryptor.finalize()
+ unpadder = padding.PKCS7(128).unpadder()
+ plain = unpadder.update(padded) + unpadder.finalize()
+ return json.loads(plain.decode('utf-8'))
+ return json.loads(body_str)
+ except Exception:
+ try:
+ return json.loads(body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else body_bytes)
+ except Exception:
+ return {}
diff --git a/paypalPremiumPlugin/apps.py b/paypalPremiumPlugin/apps.py
new file mode 100644
index 000000000..0f2389e23
--- /dev/null
+++ b/paypalPremiumPlugin/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+class PaypalpremiumpluginConfig(AppConfig):
+ name = 'paypalPremiumPlugin'
+ verbose_name = 'PayPal Premium Plugin Example'
diff --git a/paypalPremiumPlugin/meta.xml b/paypalPremiumPlugin/meta.xml
new file mode 100644
index 000000000..6535e1409
--- /dev/null
+++ b/paypalPremiumPlugin/meta.xml
@@ -0,0 +1,30 @@
+
+
+ PayPal Premium Plugin Example
+ Utility
+ 1.0.2
+ An example paid plugin that requires PayPal payment. Users can install it but cannot run it without payment. Supports PayPal.me links and PayPal Payment Links/Buttons.
+ master3395
+ https://github.com/master3395/cyberpanel-plugins
+ MIT
+
+ 3.6+
+ 2.2+
+ 2.5.5+
+
+
+ 2.5.5
+ 3.0.0
+
+
+ true
+ false
+
+ true
+ CyberPanel Paid Plugin
+ https://www.patreon.com/membership/27789984
+ https://paypal.me/KimBS?locale.x=en_US&country.x=NO
+
+ /plugins/paypalPremiumPlugin/
+ /plugins/paypalPremiumPlugin/settings/
+
diff --git a/paypalPremiumPlugin/migrations/0001_initial.py b/paypalPremiumPlugin/migrations/0001_initial.py
new file mode 100644
index 000000000..97b0f3fa8
--- /dev/null
+++ b/paypalPremiumPlugin/migrations/0001_initial.py
@@ -0,0 +1,27 @@
+# Generated migration for PaypalPremiumPluginConfig
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = []
+
+ operations = [
+ migrations.CreateModel(
+ name='PaypalPremiumPluginConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('payment_method', models.CharField(choices=[('patreon', 'Patreon Subscription'), ('paypal', 'PayPal Payment'), ('both', 'Check Both (Patreon or PayPal)')], default='both', help_text='Choose which payment method to use for verification.', max_length=10)),
+ ('activation_key', models.CharField(blank=True, default='', help_text='Validated activation key - grants access without re-entering.', max_length=64)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'PayPal Premium Plugin Configuration',
+ 'verbose_name_plural': 'PayPal Premium Plugin Configurations',
+ },
+ ),
+ ]
diff --git a/paypalPremiumPlugin/migrations/__init__.py b/paypalPremiumPlugin/migrations/__init__.py
new file mode 100644
index 000000000..2a0ee1c3e
--- /dev/null
+++ b/paypalPremiumPlugin/migrations/__init__.py
@@ -0,0 +1 @@
+# PayPal Premium Plugin migrations
diff --git a/paypalPremiumPlugin/models.py b/paypalPremiumPlugin/models.py
new file mode 100644
index 000000000..b56e0c6e4
--- /dev/null
+++ b/paypalPremiumPlugin/models.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+from django.db import models
+
+
+class PaypalPremiumPluginConfig(models.Model):
+ PAYMENT_METHOD_CHOICES = [
+ ('patreon', 'Patreon Subscription'),
+ ('paypal', 'PayPal Payment'),
+ ('both', 'Check Both (Patreon or PayPal)'),
+ ]
+ payment_method = models.CharField(
+ max_length=10,
+ choices=PAYMENT_METHOD_CHOICES,
+ default='both',
+ help_text="Choose which payment method to use for verification."
+ )
+ activation_key = models.CharField(
+ max_length=64,
+ blank=True,
+ default='',
+ help_text="Validated activation key - grants access without re-entering."
+ )
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ verbose_name = "PayPal Premium Plugin Configuration"
+ verbose_name_plural = "PayPal Premium Plugin Configurations"
+
+ def __str__(self):
+ return "PayPal Premium Plugin Configuration"
+
+ @classmethod
+ def get_config(cls):
+ config, _ = cls.objects.get_or_create(pk=1)
+ return config
+
+ def save(self, *args, **kwargs):
+ self.pk = 1
+ super().save(*args, **kwargs)
diff --git a/paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html b/paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html
new file mode 100644
index 000000000..ac5f39dbc
--- /dev/null
+++ b/paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html
@@ -0,0 +1,96 @@
+{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "PayPal Premium Plugin Example - CyberPanel" %}{% endblock %}
+
+{% block header_scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
{% trans "Premium Features" %}
+
+ {% for feature in features %}
+ - {{ feature }}
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html b/paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html
new file mode 100644
index 000000000..f7c9c39c6
--- /dev/null
+++ b/paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html
@@ -0,0 +1,331 @@
+{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "PayPal Premium Plugin Settings - CyberPanel" %}{% endblock %}
+
+{% block header_scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html b/paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html
new file mode 100644
index 000000000..cf67af087
--- /dev/null
+++ b/paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html
@@ -0,0 +1,139 @@
+{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Payment Required - PayPal Premium Plugin" %}{% endblock %}
+
+{% block header_scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
{% trans "Premium Plugin Access Required" %}
+
{% trans "This plugin requires payment or subscription to access premium features." %}
+
+
+
+
{% trans "Activate Premium Access" %}
+
{% trans "If you received an activation key, enter it below." %}
+
+
+
+
+ {% if payment_method == 'both' %}
+
+ {% trans "Current Payment Method:" %} {% trans "Check Both (Patreon or PayPal)" %}
+
+ {% elif payment_method == 'patreon' %}
+
+ {% trans "Current Payment Method:" %} {% trans "Patreon Subscription Only" %}
+
+ {% elif payment_method == 'paypal' %}
+
+ {% trans "Current Payment Method:" %} {% trans "PayPal Payment Only" %}
+
+ {% endif %}
+
+
+
+
+
{% trans "How it works:" %}
+
+ - {% trans "Install the plugin (already done)" %}
+ - {% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}
+ - {% trans "The plugin will automatically unlock" %}
+
+
+
+ {% if error %}
+
+ {% trans "Error:" %} {{ error }}
+
+ {% endif %}
+
+
+
+
+{% endblock %}
diff --git a/paypalPremiumPlugin/urls.py b/paypalPremiumPlugin/urls.py
new file mode 100644
index 000000000..997fe20f5
--- /dev/null
+++ b/paypalPremiumPlugin/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path, re_path
+from . import views
+
+app_name = 'paypalPremiumPlugin'
+
+urlpatterns = [
+ path('', views.main_view, name='main'),
+ path('settings/', views.settings_view, name='settings'),
+ re_path(r'^activate-key/$', views.activate_key, name='activate_key'),
+ path('save-payment-method/', views.save_payment_method, name='save_payment_method'),
+ path('api/status/', views.api_status_view, name='api_status'),
+]
diff --git a/paypalPremiumPlugin/views.py b/paypalPremiumPlugin/views.py
index c16bc0337..c1f96f129 100644
--- a/paypalPremiumPlugin/views.py
+++ b/paypalPremiumPlugin/views.py
@@ -1,496 +1,387 @@
# -*- coding: utf-8 -*-
"""
-PayPal Premium Plugin Views - Enhanced Security Version
-This version uses remote server verification with multiple security layers
-SECURITY: All PayPal verification happens on YOUR server, not user's server
+PayPal Premium Plugin Views - Unified Verification (same as contaboAutoSnapshot)
+Supports: Plugin Grants, Activation Key, Patreon, PayPal, AES encryption
"""
from django.shortcuts import render, redirect
-from django.http import JsonResponse
+from django.http import JsonResponse, HttpResponse
+from django.views.decorators.http import require_http_methods
from plogical.mailUtilities import mailUtilities
from plogical.httpProc import httpProc
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from functools import wraps
-import sys
-import os
import urllib.request
import urllib.error
import json
-import hashlib
-import socket
-import platform
-import subprocess
import time
-import uuid
-# Remote verification server (YOUR server, not user's server)
-REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-paypal-payment'
-PLUGIN_NAME = 'paypalPremiumPlugin' # PayPal Premium Plugin Example
-PLUGIN_VERSION = '1.0.0'
+from .models import PaypalPremiumPluginConfig
+from . import api_encryption
-# PayPal configuration
+PLUGIN_NAME = 'paypalPremiumPlugin'
+PLUGIN_VERSION = '1.0.2'
+
+REMOTE_VERIFICATION_PATREON_URL = 'https://api.newstargeted.com/api/verify-patreon-membership.php'
+REMOTE_VERIFICATION_PAYPAL_URL = 'https://api.newstargeted.com/api/verify-paypal-payment.php'
+REMOTE_VERIFICATION_PLUGIN_GRANT_URL = 'https://api.newstargeted.com/api/verify-plugin-grant.php'
+REMOTE_ACTIVATION_KEY_URL = 'https://api.newstargeted.com/api/activate-plugin-key.php'
+
+PATREON_TIER = 'CyberPanel Paid Plugin'
+PATREON_URL = 'https://www.patreon.com/membership/27789984'
PAYPAL_ME_URL = 'https://paypal.me/KimBS?locale.x=en_US&country.x=NO'
-PAYPAL_PAYMENT_LINK = '' # Can be set to a PayPal Payment Link URL
+PAYPAL_PAYMENT_LINK = ''
-# Security configuration
-CACHE_FILE = '/tmp/.paypalPremiumPlugin_license_cache'
-CACHE_DURATION = 3600 # 1 hour
-
-# File integrity hashes (generated after plugin finalization)
-# To regenerate: python3 -c "import hashlib; print(hashlib.sha256(open('views.py', 'rb').read()).hexdigest())"
-PLUGIN_FILE_HASHES = {
- 'views.py': '4899d70dde220b38d691a5cefdc4fd77b6d3e250ac1c7e12fa280d6f4ad31eb1', # Updated with security features
- 'urls.py': '92433d401c358cd33ffd1926881920fd1867bb6d7dad1c3c2ed1e7d3b0abc2c6',
-}
-
-def get_server_fingerprint():
- """
- Generate unique server fingerprint
- Ties license to specific server hardware/configuration
- """
- fingerprint_data = []
-
- try:
- # Server hostname
- fingerprint_data.append(socket.gethostname())
-
- # Primary IP
- fingerprint_data.append(socket.gethostbyname(socket.gethostname()))
-
- # System information
- fingerprint_data.append(platform.node())
- fingerprint_data.append(platform.machine())
- fingerprint_data.append(platform.processor())
-
- # MAC address
- fingerprint_data.append(str(uuid.getnode()))
-
- # Disk information (if available)
- try:
- result = subprocess.run(['df', '-h', '/'], capture_output=True, text=True, timeout=2)
- fingerprint_data.append(result.stdout[:100])
- except:
- pass
-
- # Create hash
- fingerprint_string = '|'.join(str(x) for x in fingerprint_data)
- return hashlib.sha256(fingerprint_string.encode()).hexdigest()
- except Exception as e:
- # Fallback fingerprint
- return hashlib.sha256(f"{socket.gethostname()}|{platform.node()}".encode()).hexdigest()
-
-def verify_code_integrity():
- """
- Verify plugin files haven't been tampered with
- Returns: (is_valid, error_message)
- """
- plugin_dir = os.path.dirname(os.path.abspath(__file__))
-
- for filename, expected_hash in PLUGIN_FILE_HASHES.items():
- if not expected_hash:
- continue # Skip if hash not set
-
- filepath = os.path.join(plugin_dir, filename)
- if os.path.exists(filepath):
- try:
- with open(filepath, 'rb') as f:
- file_content = f.read()
- file_hash = hashlib.sha256(file_content).hexdigest()
-
- if file_hash != expected_hash:
- return False, f"File {filename} has been modified (integrity check failed)"
- except Exception as e:
- return False, f"Error checking {filename}: {str(e)}"
-
- return True, None
-
-def get_cached_verification():
- """Get cached verification result"""
- if os.path.exists(CACHE_FILE):
- try:
- with open(CACHE_FILE, 'r') as f:
- cache_data = json.load(f)
- cache_time = cache_data.get('timestamp', 0)
- server_fp = cache_data.get('server_fingerprint')
-
- # Verify server fingerprint matches
- current_fp = get_server_fingerprint()
- if server_fp != current_fp:
- return None # Server changed, invalidate cache
-
- # Check if cache is still valid
- if time.time() - cache_time < CACHE_DURATION:
- return cache_data.get('has_access', False)
- except:
- pass
- return None
-
-def cache_verification_result(has_access, server_fp):
- """Cache verification result"""
- try:
- with open(CACHE_FILE, 'w') as f:
- json.dump({
- 'has_access': has_access,
- 'server_fingerprint': server_fp,
- 'timestamp': time.time()
- }, f)
- os.chmod(CACHE_FILE, 0o600) # Secure permissions (owner read/write only)
- except Exception as e:
- pass # Silently fail caching
def cyberpanel_login_required(view_func):
- """
- Custom decorator that checks for CyberPanel session userID
- """
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
- userID = request.session['userID']
- # User is authenticated via CyberPanel session
+ if not request.session.get('userID'):
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
return view_func(request, *args, **kwargs)
except KeyError:
- # Not logged in, redirect to login
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return _wrapped_view
-def secure_verification_required(view_func):
- """
- Enhanced decorator with multiple security checks
- """
- @wraps(view_func)
- def _wrapped_view(request, *args, **kwargs):
- # Check 1: Login required
- try:
- userID = request.session['userID']
- except KeyError:
- from loginSystem.views import loadLoginPage
- return redirect(loadLoginPage)
-
- # Check 2: Code integrity
- is_valid, integrity_error = verify_code_integrity()
- if not is_valid:
- # Log security violation
- logging.writeToFile(f"SECURITY VIOLATION: {integrity_error} - User: {request.session.get('userID')}")
-
- # Show error (don't reveal details)
- context = {
- 'error': 'Plugin integrity check failed. Please reinstall the plugin.',
- 'security_violation': True
- }
- proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin')
- return proc.render()
-
- # Check 3: Remote verification
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- domain = request.get_host()
- user_ip = request.META.get('REMOTE_ADDR', '')
-
- verification_result = check_remote_payment_secure(
- user_email,
- user_ip,
- domain
- )
-
- if not verification_result.get('has_access', False):
- # Show payment required page
- context = {
- 'plugin_name': 'PayPal Premium Plugin Example',
- 'is_paid': True,
- 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
- 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
- 'message': verification_result.get('message', 'PayPal payment required'),
- 'error': verification_result.get('error')
- }
- proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin')
- return proc.render()
-
- # All checks passed - proceed
- return view_func(request, *args, **kwargs)
-
- return _wrapped_view
-def remote_verification_required(view_func):
- """
- Decorator that checks PayPal payment via remote server
- No secrets stored in plugin - all verification happens on your server
- """
- @wraps(view_func)
- def _wrapped_view(request, *args, **kwargs):
- # First check login
- try:
- userID = request.session['userID']
- except KeyError:
- from loginSystem.views import loadLoginPage
- return redirect(loadLoginPage)
-
- # Get user email
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- # Try to get from session or username
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- # Check payment via remote server
- verification_result = check_remote_payment_secure(
- user_email,
- request.META.get('REMOTE_ADDR', ''),
- request.get_host()
- )
-
- if not verification_result.get('has_access', False):
- # User doesn't have payment - show payment required page
- context = {
- 'plugin_name': 'PayPal Premium Plugin Example',
- 'is_paid': True,
- 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
- 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
- 'message': verification_result.get('message', 'PayPal payment required'),
- 'error': verification_result.get('error')
- }
- proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin')
- return proc.render()
-
- # User has access - proceed with view
- return view_func(request, *args, **kwargs)
-
- return _wrapped_view
-
-def check_remote_payment_secure(user_email, user_ip='', domain=''):
- """
- Enhanced remote payment verification with multiple security layers
-
- Args:
- user_email: User's email address
- user_ip: User's IP address (for logging/security)
- domain: Current domain (for domain binding)
-
- Returns:
- dict: {
- 'has_access': bool,
- 'paypal_me_url': str,
- 'paypal_payment_link': str,
- 'message': str,
- 'error': str or None
- }
- """
- # Layer 1: Code integrity check
- is_valid, integrity_error = verify_code_integrity()
- if not is_valid:
- return {
- 'has_access': False,
- 'paypal_me_url': PAYPAL_ME_URL,
- 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
- 'message': 'Plugin integrity check failed',
- 'error': integrity_error,
- 'security_violation': True
- }
-
- # Layer 2: Check cache
- cached_result = get_cached_verification()
- if cached_result is not None:
- return {
- 'has_access': cached_result,
- 'paypal_me_url': PAYPAL_ME_URL,
- 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
- 'message': 'Access granted' if cached_result else 'PayPal payment required'
- }
-
- # Layer 3: Server fingerprinting
- server_fp = get_server_fingerprint()
-
- # Layer 4: Prepare secure request
- request_data = {
- 'user_email': user_email,
- 'plugin_name': PLUGIN_NAME,
- 'plugin_version': PLUGIN_VERSION,
- 'server_fingerprint': server_fp,
- 'domain': domain,
- 'user_ip': user_ip,
- 'timestamp': int(time.time())
- }
-
+def _api_request(url, data, timeout=10):
try:
- # Make request to remote verification server
- req = urllib.request.Request(
- REMOTE_VERIFICATION_URL,
- data=json.dumps(request_data).encode('utf-8'),
- headers={
- 'Content-Type': 'application/json',
- 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
- 'X-Plugin-Name': PLUGIN_NAME,
- 'X-Timestamp': str(request_data['timestamp'])
- }
- )
-
- # Send request with timeout
- try:
- with urllib.request.urlopen(req, timeout=10) as response:
- response_data = json.loads(response.read().decode('utf-8'))
-
- if response_data.get('success', False):
- has_access = response_data.get('has_access', False)
-
- # Cache result
- cache_verification_result(has_access, server_fp)
-
- return {
- 'has_access': has_access,
- 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL),
- 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
- 'message': response_data.get('message', 'Access granted' if has_access else 'PayPal payment required'),
- 'error': None
- }
- else:
- return {
- 'has_access': False,
- 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL),
- 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
- 'message': response_data.get('message', 'PayPal payment required'),
- 'error': response_data.get('error')
- }
- except urllib.error.HTTPError as e:
- # Server returned error
- error_body = e.read().decode('utf-8') if e.fp else 'Unknown error'
- return {
- 'has_access': False,
- 'paypal_me_url': PAYPAL_ME_URL,
- 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
- 'message': 'Unable to verify payment. Please try again later.',
- 'error': f'HTTP {e.code}: {error_body}'
- }
- except urllib.error.URLError as e:
- # Network error
- return {
- 'has_access': False,
- 'paypal_me_url': PAYPAL_ME_URL,
- 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
- 'message': 'Unable to connect to verification server. Please check your internet connection.',
- 'error': str(e.reason) if hasattr(e, 'reason') else str(e)
- }
- except Exception as e:
- # Other errors
- return {
- 'has_access': False,
- 'paypal_me_url': PAYPAL_ME_URL,
- 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
- 'message': 'Verification error occurred. Please try again later.',
- 'error': str(e)
- }
-
+ body, extra_headers = api_encryption.encrypt_payload(data)
+ headers = {
+ 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
+ 'X-Plugin-Name': PLUGIN_NAME
+ }
+ headers.update(extra_headers)
+ req = urllib.request.Request(url, data=body, headers=headers)
+ with urllib.request.urlopen(req, timeout=timeout) as response:
+ raw = response.read()
+ ct = response.headers.get('Content-Type', '')
+ expect_enc = extra_headers.get('X-Encrypted') == '1'
+ return api_encryption.decrypt_response(raw, ct, expect_encrypted=expect_enc)
except Exception as e:
- logging.writeToFile(f"Error in remote payment check: {str(e)}")
+ logging.writeToFile(f"PayPal Premium Plugin: API request error to {url}: {str(e)}")
+ return {}
+
+
+def check_plugin_grant(user_email, user_ip='', domain=''):
+ try:
+ request_data = {
+ 'user_email': user_email or '',
+ 'plugin_name': PLUGIN_NAME,
+ 'user_ip': user_ip,
+ 'domain': domain,
+ }
+ data = _api_request(REMOTE_VERIFICATION_PLUGIN_GRANT_URL, request_data)
+ if data.get('success') and data.get('has_access'):
+ return {'has_access': True, 'message': data.get('message', 'Access granted via Plugin Grants')}
+ return {'has_access': False, 'message': data.get('message', '')}
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Plugin grant check error: {str(e)}")
+ return {'has_access': False, 'message': ''}
+
+
+def check_patreon_membership(user_email, user_ip='', domain=''):
+ try:
+ request_data = {
+ 'user_email': user_email,
+ 'plugin_name': PLUGIN_NAME,
+ 'plugin_version': PLUGIN_VERSION,
+ 'user_ip': user_ip,
+ 'domain': domain,
+ 'tier_id': '27789984'
+ }
+ response_data = _api_request(REMOTE_VERIFICATION_PATREON_URL, request_data)
+ if response_data.get('success', False):
+ return {
+ 'has_access': response_data.get('has_access', False),
+ 'patreon_tier': response_data.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': response_data.get('patreon_url', PATREON_URL),
+ 'message': response_data.get('message', 'Access granted'),
+ 'error': None
+ }
+ return {
+ 'has_access': False,
+ 'patreon_tier': PATREON_TIER,
+ 'patreon_url': PATREON_URL,
+ 'message': response_data.get('message', 'Patreon subscription required'),
+ 'error': response_data.get('error')
+ }
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Patreon check error: {str(e)}")
+ return {'has_access': False, 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, 'message': 'Unable to verify Patreon.', 'error': str(e)}
+
+
+def check_paypal_payment(user_email, user_ip='', domain=''):
+ try:
+ request_data = {
+ 'user_email': user_email,
+ 'plugin_name': PLUGIN_NAME,
+ 'plugin_version': PLUGIN_VERSION,
+ 'user_ip': user_ip,
+ 'domain': domain,
+ 'timestamp': int(time.time()),
+ }
+ response_data = _api_request(REMOTE_VERIFICATION_PAYPAL_URL, request_data)
+ if response_data.get('success', False):
+ return {
+ 'has_access': response_data.get('has_access', False),
+ 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': response_data.get('message', 'Access granted'),
+ 'error': None
+ }
return {
'has_access': False,
'paypal_me_url': PAYPAL_ME_URL,
'paypal_payment_link': PAYPAL_PAYMENT_LINK,
- 'message': 'Verification error occurred. Please try again later.',
- 'error': str(e)
+ 'message': response_data.get('message', 'PayPal payment required'),
+ 'error': response_data.get('error')
}
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: PayPal check error: {str(e)}")
+ return {'has_access': False, 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, 'message': 'Unable to verify PayPal.', 'error': str(e)}
+
+
+def unified_verification_required(view_func):
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ try:
+ if not request.session.get('userID'):
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
+
+ user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') or getattr(request.user, 'username', '')
+
+ try:
+ config = PaypalPremiumPluginConfig.get_config()
+ payment_method = config.payment_method
+ except Exception:
+ payment_method = 'both'
+
+ has_access = False
+ verification_result = {}
+
+ activation_key = request.GET.get('activation_key') or request.POST.get('activation_key')
+ if not activation_key:
+ try:
+ config = PaypalPremiumPluginConfig.get_config()
+ activation_key = getattr(config, 'activation_key', '') or ''
+ except Exception:
+ activation_key = ''
+
+ if activation_key:
+ try:
+ request_data = {'activation_key': activation_key.strip(), 'plugin_name': PLUGIN_NAME, 'user_email': user_email}
+ response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
+ if response_data.get('success', False) and response_data.get('has_access', False):
+ has_access = True
+ verification_result = {'method': 'activation_key', 'has_access': True, 'message': response_data.get('message', 'Access activated via key')}
+ try:
+ config = PaypalPremiumPluginConfig.get_config()
+ config.activation_key = activation_key.strip()
+ config.save(update_fields=['activation_key', 'updated_at'])
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Could not persist activation key: {str(e)}")
+ elif not response_data.get('success') and activation_key:
+ try:
+ config = PaypalPremiumPluginConfig.get_config()
+ if getattr(config, 'activation_key', '') == activation_key.strip():
+ config.activation_key = ''
+ config.save(update_fields=['activation_key', 'updated_at'])
+ except Exception:
+ pass
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Activation key check error: {str(e)}")
+
+ if not has_access:
+ grant_result = check_plugin_grant(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ if grant_result.get('has_access'):
+ has_access = True
+ verification_result = {'method': 'plugin_grant', 'has_access': True, 'message': grant_result.get('message', 'Access granted via Plugin Grants')}
+
+ if not has_access:
+ try:
+ if payment_method == 'patreon':
+ result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ has_access = result.get('has_access', False)
+ verification_result = {
+ 'method': 'patreon', 'has_access': has_access,
+ 'patreon_tier': result.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': result.get('patreon_url', PATREON_URL),
+ 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'message': result.get('message', 'Patreon subscription required'),
+ 'error': result.get('error')
+ }
+ elif payment_method == 'paypal':
+ result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ has_access = result.get('has_access', False)
+ verification_result = {
+ 'method': 'paypal', 'has_access': has_access,
+ 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
+ 'paypal_me_url': result.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': result.get('message', 'PayPal payment required'),
+ 'error': result.get('error')
+ }
+ else:
+ patreon_result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ paypal_result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ has_access = patreon_result.get('has_access', False) or paypal_result.get('has_access', False)
+ verification_result = {
+ 'method': 'both', 'has_access': has_access,
+ 'patreon_tier': patreon_result.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': patreon_result.get('patreon_url', PATREON_URL),
+ 'paypal_me_url': paypal_result.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': paypal_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': 'Payment or subscription required' if not has_access else 'Access granted'
+ }
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Verification error: {str(e)}")
+ has_access = False
+ verification_result = {
+ 'method': payment_method, 'has_access': False,
+ 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
+ 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'message': 'Unable to verify access.', 'error': str(e)
+ }
+
+ if not has_access:
+ context = {
+ 'plugin_name': 'PayPal Premium Plugin Example',
+ 'is_paid': True,
+ 'payment_method': payment_method,
+ 'verification_result': verification_result,
+ 'patreon_tier': verification_result.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': verification_result.get('patreon_url', PATREON_URL),
+ 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': verification_result.get('message', 'Payment or subscription required'),
+ 'error': verification_result.get('error')
+ }
+ proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin')
+ return proc.render()
+
+ if has_access and verification_result:
+ request.session['paypal_premium_access_via'] = verification_result.get('method', '')
+
+ return view_func(request, *args, **kwargs)
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Decorator error: {str(e)}")
+ return HttpResponse(f"")
+ return _wrapped_view
-def check_remote_payment(user_email, user_ip=''):
- """
- Legacy function for backward compatibility
- """
- return check_remote_payment_secure(user_email, user_ip, '')
@cyberpanel_login_required
def main_view(request):
- """
- Main view for PayPal premium plugin
- Shows plugin information and features if paid, or payment required message if not
- """
mailUtilities.checkHome()
-
- # Get user email for verification
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- # Check payment status (but don't block access)
- verification_result = check_remote_payment_secure(
- user_email,
- request.META.get('REMOTE_ADDR', ''),
- request.get_host()
- )
- has_access = verification_result.get('has_access', False)
-
- # Determine plugin status
- plugin_status = 'Active' if has_access else 'Payment Required'
-
- context = {
- 'plugin_name': 'PayPal Premium Plugin Example',
- 'version': PLUGIN_VERSION,
- 'status': plugin_status,
- 'has_access': has_access,
- 'description': 'This is an example paid plugin that requires PayPal payment.' if not has_access else 'This is an example paid plugin. You have access because payment has been verified!',
- 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
- 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
- 'features': [
- 'Premium Feature 1',
- 'Premium Feature 2',
- 'Premium Feature 3',
- 'Advanced Configuration',
- 'Priority Support'
- ] if has_access else []
- }
-
- proc = httpProc(request, 'paypalPremiumPlugin/index.html', context, 'admin')
- return proc.render()
+ return redirect('paypalPremiumPlugin:settings')
+
@cyberpanel_login_required
+@unified_verification_required
def settings_view(request):
- """
- Settings page for PayPal premium plugin
- Shows settings but disables them if user doesn't have PayPal payment
- """
mailUtilities.checkHome()
-
- # Get user email for verification
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- # Check payment status (but don't block access)
- verification_result = check_remote_payment_secure(
- user_email,
- request.META.get('REMOTE_ADDR', ''),
- request.get_host()
- )
- has_access = verification_result.get('has_access', False)
-
- # Determine plugin status
- plugin_status = 'Active' if has_access else 'Payment Required'
-
+ try:
+ config = PaypalPremiumPluginConfig.get_config()
+ except Exception:
+ from django.core.management import call_command
+ try:
+ call_command('migrate', 'paypalPremiumPlugin', verbosity=0, interactive=False)
+ config = PaypalPremiumPluginConfig.get_config()
+ except Exception as e:
+ return HttpResponse(f"")
+
+ access_via = request.session.get('paypal_premium_access_via', '')
+ show_payment_ui = access_via not in ('plugin_grant', 'activation_key')
+
context = {
'plugin_name': 'PayPal Premium Plugin Example',
'version': PLUGIN_VERSION,
- 'plugin_status': plugin_status,
- 'status': plugin_status, # Keep both for compatibility
- 'description': 'Configure your premium plugin settings',
- 'has_access': has_access,
- 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
- 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
- 'verification_message': verification_result.get('message', '')
+ 'status': 'Active',
+ 'config': config,
+ 'has_access': True,
+ 'show_payment_ui': show_payment_ui,
+ 'access_via_grant_or_key': not show_payment_ui,
+ 'patreon_tier': PATREON_TIER,
+ 'patreon_url': PATREON_URL,
+ 'paypal_me_url': PAYPAL_ME_URL,
+ 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'description': 'Configure your PayPal premium plugin settings.',
}
-
proc = httpProc(request, 'paypalPremiumPlugin/settings.html', context, 'admin')
return proc.render()
+
@cyberpanel_login_required
-@secure_verification_required
+@require_http_methods(["POST"])
+def activate_key(request):
+ try:
+ if request.content_type == 'application/json':
+ data = json.loads(request.body)
+ else:
+ data = request.POST
+
+ activation_key = data.get('activation_key', '').strip()
+ user_email = data.get('user_email', '').strip()
+ if not user_email:
+ user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '')
+
+ if not activation_key:
+ return JsonResponse({'success': False, 'message': 'Activation key is required'}, status=400)
+
+ request_data = {'activation_key': activation_key, 'plugin_name': PLUGIN_NAME, 'user_email': user_email}
+ response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
+
+ if response_data.get('success', False) and response_data.get('has_access', False):
+ try:
+ config = PaypalPremiumPluginConfig.get_config()
+ config.activation_key = activation_key
+ config.save(update_fields=['activation_key', 'updated_at'])
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: Could not persist activation key: {str(e)}")
+
+ return JsonResponse({
+ 'success': True,
+ 'has_access': True,
+ 'message': response_data.get('message', 'Access activated successfully')
+ })
+
+ return JsonResponse({
+ 'success': False,
+ 'has_access': False,
+ 'message': response_data.get('message', 'Invalid activation key')
+ })
+
+ except Exception as e:
+ logging.writeToFile(f"PayPal Premium Plugin: activate_key error: {str(e)}")
+ return JsonResponse({'success': False, 'message': str(e)}, status=500)
+
+
+@cyberpanel_login_required
+@require_http_methods(["POST"])
+def save_payment_method(request):
+ try:
+ payment_method = request.POST.get('payment_method', 'both')
+ if payment_method not in ('patreon', 'paypal', 'both'):
+ payment_method = 'both'
+ config = PaypalPremiumPluginConfig.get_config()
+ config.payment_method = payment_method
+ config.save(update_fields=['payment_method', 'updated_at'])
+ return JsonResponse({'success': True, 'message': 'Payment method saved'})
+ except Exception as e:
+ return JsonResponse({'success': False, 'message': str(e)}, status=500)
+
+
+@cyberpanel_login_required
+@unified_verification_required
def api_status_view(request):
- """
- API endpoint for plugin status
- Only accessible with PayPal payment (verified remotely with enhanced security)
- """
return JsonResponse({
'plugin_name': 'PayPal Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'active',
'payment': 'verified',
- 'description': 'Premium plugin is active and accessible',
- 'verification_method': 'remote_secure'
+ 'verification_method': 'unified'
})
diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html
index 1f74dd360..78e158354 100644
--- a/pluginHolder/templates/pluginHolder/plugins.html
+++ b/pluginHolder/templates/pluginHolder/plugins.html
@@ -1091,6 +1091,7 @@
{% endblock %}
{% block content %}
+
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
diff --git a/premiumPlugin/.gitignore b/premiumPlugin/.gitignore
new file mode 100644
index 000000000..2ef72c88d
--- /dev/null
+++ b/premiumPlugin/.gitignore
@@ -0,0 +1,6 @@
+# Security - Never commit secrets
+*.secret
+*_secret*
+patreon_config.py
+.env.patreon
+patreon_secrets.env
diff --git a/premiumPlugin/README-REMOTE-VERIFICATION.md b/premiumPlugin/README-REMOTE-VERIFICATION.md
new file mode 100644
index 000000000..09decfcd6
--- /dev/null
+++ b/premiumPlugin/README-REMOTE-VERIFICATION.md
@@ -0,0 +1,81 @@
+# Remote Verification Setup
+
+## Overview
+
+This version of the plugin uses **remote verification** - all Patreon API calls happen on YOUR server, not the user's server.
+
+## Benefits
+
+✅ **No secrets in plugin** - Users can see all plugin code, but no credentials
+✅ **Secure** - All Patreon API credentials stay on your server
+✅ **Centralized** - You control access, can revoke, update logic, etc.
+✅ **Public code** - Plugin code can be open source
+
+## Architecture
+
+```
+User's Server Your Server Patreon API
+ | | |
+ |-- Verify Request ------------> | |
+ | |-- Check Membership --> |
+ | |<-- Membership Status - |
+ |<-- Access Granted/Denied ----- | |
+```
+
+## Setup
+
+### 1. Deploy Verification API
+
+Deploy the verification endpoint to your server:
+- File: `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php`
+- URL: `https://api.newstargeted.com/api/verify-patreon-membership`
+
+### 2. Configure Your Server
+
+Add Patreon credentials to your server's `config.php`:
+
+```php
+define('PATREON_CLIENT_ID', 'your_client_id');
+define('PATREON_CLIENT_SECRET', 'your_client_secret');
+define('PATREON_CREATOR_ACCESS_TOKEN', 'your_access_token');
+```
+
+### 3. Update Plugin
+
+Replace `views.py` with `views_remote.py`:
+
+```bash
+mv views.py views_local.py # Backup local version
+mv views_remote.py views.py # Use remote version
+```
+
+### 4. Configure Plugin URL
+
+Update `REMOTE_VERIFICATION_URL` in `views.py` to point to your server:
+
+```python
+REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership'
+```
+
+## Security Features
+
+- **Rate limiting** - Prevents abuse (60 requests/hour per IP)
+- **HTTPS only** - All communication encrypted
+- **No secrets** - Plugin only makes API calls
+- **Caching** - Reduces Patreon API calls (5 min cache)
+
+## Testing
+
+1. Install plugin on user's server
+2. Try accessing plugin (should show subscription required)
+3. Subscribe to Patreon tier
+4. Access plugin again (should work)
+
+## Migration from Local Verification
+
+If you were using local verification:
+
+1. Keep `views_local.py` as backup
+2. Use `views_remote.py` as `views.py`
+3. Deploy verification API to your server
+4. Update plugin URL in code
diff --git a/premiumPlugin/README.md b/premiumPlugin/README.md
new file mode 100644
index 000000000..922bffe2e
--- /dev/null
+++ b/premiumPlugin/README.md
@@ -0,0 +1,58 @@
+# Premium Plugin Example
+
+An example paid plugin for CyberPanel that demonstrates how to implement Patreon subscription-based plugin access.
+
+## Features
+
+- Requires Patreon subscription to "CyberPanel Paid Plugin" tier
+- Users can install the plugin without subscription
+- Plugin functionality is locked until subscription is verified
+- Shows subscription required page when accessed without subscription
+
+## Installation
+
+1. Upload the plugin ZIP file to CyberPanel
+2. Install the plugin from the plugin manager
+3. The plugin will appear in the installed plugins list
+
+## Usage
+
+### For Users Without Subscription
+
+- Plugin can be installed
+- When accessing the plugin, a subscription required page is shown
+- Link to Patreon subscription page is provided
+
+### For Users With Subscription
+
+- Plugin works normally
+- All features are accessible
+- Settings page is available
+
+## Configuration
+
+The plugin checks for Patreon membership via the Patreon API. Make sure to configure:
+
+1. Patreon Client ID
+2. Patreon Client Secret
+3. Patreon Creator ID
+
+These should be set in CyberPanel environment variables or settings.
+
+## Meta.xml Structure
+
+The plugin uses the following meta.xml structure for paid plugins:
+
+```xml
+true
+CyberPanel Paid Plugin
+https://www.patreon.com/c/newstargeted/membership
+```
+
+## Author
+
+master3395
+
+## License
+
+MIT
diff --git a/premiumPlugin/SECURITY.md b/premiumPlugin/SECURITY.md
new file mode 100644
index 000000000..b2df3648e
--- /dev/null
+++ b/premiumPlugin/SECURITY.md
@@ -0,0 +1,57 @@
+# Security Guidelines for Premium Plugin
+
+## ⚠️ IMPORTANT: Never Expose Secrets
+
+This plugin is designed to be **publicly shareable**. It contains **NO secrets** and is safe to publish.
+
+## What's Safe to Share
+
+✅ **Safe to commit:**
+- Plugin code (views.py, urls.py, etc.)
+- Templates (HTML files)
+- meta.xml (no secrets, only tier name and URL)
+- README.md
+- Documentation
+
+❌ **Never commit:**
+- Patreon Client Secret
+- Patreon Access Tokens
+- Patreon Refresh Tokens
+- Any hardcoded credentials
+
+## Configuration
+
+All Patreon credentials are configured on the **server side** via:
+- Environment variables
+- Django settings (from environment)
+- Secure config files (not in repository)
+
+## For Your Own Setup
+
+When setting up this plugin on your server:
+
+1. **Do NOT** modify plugin files with your secrets
+2. **Do** configure environment variables on the server
+3. **Do** use Django settings.py (with environment variable fallbacks)
+4. **Do** add any secret config files to .gitignore
+
+## Example Secure Configuration
+
+```python
+# In settings.py (safe to commit)
+PATREON_CLIENT_ID = os.environ.get('PATREON_CLIENT_ID', '')
+PATREON_CLIENT_SECRET = os.environ.get('PATREON_CLIENT_SECRET', '')
+
+# On server (NOT in repo)
+export PATREON_CLIENT_ID="your_actual_secret"
+export PATREON_CLIENT_SECRET="your_actual_secret"
+```
+
+## Verification
+
+Before publishing, verify:
+- [ ] No secrets in plugin files
+- [ ] No secrets in meta.xml
+- [ ] No secrets in README
+- [ ] All credentials use environment variables
+- [ ] .gitignore excludes secret files
diff --git a/premiumPlugin/__init__.py b/premiumPlugin/__init__.py
new file mode 100644
index 000000000..deac71067
--- /dev/null
+++ b/premiumPlugin/__init__.py
@@ -0,0 +1,4 @@
+# Premium Plugin Example
+# This is a paid plugin that requires Patreon subscription
+
+default_app_config = 'premiumPlugin.apps.PremiumPluginConfig'
diff --git a/premiumPlugin/api_encryption.py b/premiumPlugin/api_encryption.py
new file mode 100644
index 000000000..50f8b3d10
--- /dev/null
+++ b/premiumPlugin/api_encryption.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+"""
+AES-256-CBC encryption for plugin <-> api.newstargeted.com communication.
+Key must match PLUGIN_VERIFICATION_CIPHER_KEY in config.php on the API server.
+"""
+import json
+import base64
+import os
+
+CIPHER_KEY_B64 = '1VLPEKTmLGUbIxHUFEtsuVM2MPN1tl8HPFtyJc4dr58='
+ENCRYPTION_ENABLED = True
+
+_ENCRYPTION_CIPHER_KEY = None
+
+
+def _get_key():
+ """Get 32-byte AES key from base64."""
+ global _ENCRYPTION_CIPHER_KEY
+ if _ENCRYPTION_CIPHER_KEY is not None:
+ return _ENCRYPTION_CIPHER_KEY
+ try:
+ key = base64.b64decode(CIPHER_KEY_B64)
+ if len(key) == 32:
+ _ENCRYPTION_CIPHER_KEY = key
+ return key
+ except Exception:
+ pass
+ return None
+
+
+def encrypt_payload(data):
+ """Encrypt JSON payload for API request. Returns (body_bytes, headers_dict)."""
+ if not ENCRYPTION_ENABLED or not _get_key():
+ body = json.dumps(data, separators=(',', ':')).encode('utf-8')
+ return body, {'Content-Type': 'application/json'}
+ try:
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.primitives import padding
+ from cryptography.hazmat.backends import default_backend
+ key = _get_key()
+ plain = json.dumps(data, separators=(',', ':')).encode('utf-8')
+ padder = padding.PKCS7(128).padder()
+ padded = padder.update(plain) + padder.finalize()
+ iv = os.urandom(16)
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
+ encryptor = cipher.encryptor()
+ ciphertext = encryptor.update(padded) + encryptor.finalize()
+ payload = base64.b64encode(iv).decode('ascii') + '.' + base64.b64encode(ciphertext).decode('ascii')
+ return payload.encode('utf-8'), {'Content-Type': 'text/plain', 'X-Encrypted': '1'}
+ except Exception:
+ body = json.dumps(data, separators=(',', ':')).encode('utf-8')
+ return body, {'Content-Type': 'application/json'}
+
+
+def decrypt_response(body_bytes, content_type='', expect_encrypted=False):
+ """Decrypt API response. Handles both encrypted and plain JSON."""
+ try:
+ body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes)
+ is_encrypted = (
+ expect_encrypted or
+ ('text/plain' in content_type and '.' in body_str) or
+ ('.' in body_str and body_str.strip() and body_str.strip()[0] not in '{[')
+ )
+ parts = body_str.strip().split('.', 1)
+ if is_encrypted and len(parts) == 2 and ENCRYPTION_ENABLED and _get_key():
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.primitives import padding
+ from cryptography.hazmat.backends import default_backend
+ iv = base64.b64decode(parts[0])
+ ciphertext = base64.b64decode(parts[1])
+ key = _get_key()
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
+ decryptor = cipher.decryptor()
+ padded = decryptor.update(ciphertext) + decryptor.finalize()
+ unpadder = padding.PKCS7(128).unpadder()
+ plain = unpadder.update(padded) + unpadder.finalize()
+ return json.loads(plain.decode('utf-8'))
+ return json.loads(body_str)
+ except Exception:
+ try:
+ return json.loads(body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else body_bytes)
+ except Exception:
+ return {}
diff --git a/premiumPlugin/apps.py b/premiumPlugin/apps.py
new file mode 100644
index 000000000..261eb7905
--- /dev/null
+++ b/premiumPlugin/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+class PremiumPluginConfig(AppConfig):
+ name = 'premiumPlugin'
+ verbose_name = 'Premium Plugin Example'
diff --git a/premiumPlugin/meta.xml b/premiumPlugin/meta.xml
new file mode 100644
index 000000000..a986da653
--- /dev/null
+++ b/premiumPlugin/meta.xml
@@ -0,0 +1,30 @@
+
+
+ Premium Plugin Example
+ Utility
+ 1.0.2
+ An example paid plugin that requires Patreon subscription to "CyberPanel Paid Plugin" tier. Users can install it but cannot run it without subscription.
+ master3395
+ https://github.com/master3395/cyberpanel-plugins
+ MIT
+
+ 3.6+
+ 2.2+
+ 2.5.5+
+
+
+ 2.5.5
+ 3.0.0
+
+
+ true
+ false
+
+ true
+ CyberPanel Paid Plugin
+ https://www.patreon.com/membership/27789984
+ https://paypal.me/KimBS?locale.x=en_US&country.x=NO
+
+ /plugins/premiumPlugin/
+ /plugins/premiumPlugin/settings/
+
diff --git a/premiumPlugin/migrations/0001_initial.py b/premiumPlugin/migrations/0001_initial.py
new file mode 100644
index 000000000..e403a7fbd
--- /dev/null
+++ b/premiumPlugin/migrations/0001_initial.py
@@ -0,0 +1,27 @@
+# Generated migration for PremiumPluginConfig
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = []
+
+ operations = [
+ migrations.CreateModel(
+ name='PremiumPluginConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('payment_method', models.CharField(choices=[('patreon', 'Patreon Subscription'), ('paypal', 'PayPal Payment'), ('both', 'Check Both (Patreon or PayPal)')], default='both', help_text='Choose which payment method to use for verification.', max_length=10)),
+ ('activation_key', models.CharField(blank=True, default='', help_text='Validated activation key - grants access without re-entering.', max_length=64)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'Premium Plugin Configuration',
+ 'verbose_name_plural': 'Premium Plugin Configurations',
+ },
+ ),
+ ]
diff --git a/premiumPlugin/migrations/__init__.py b/premiumPlugin/migrations/__init__.py
new file mode 100644
index 000000000..80dc97892
--- /dev/null
+++ b/premiumPlugin/migrations/__init__.py
@@ -0,0 +1 @@
+# Premium Plugin migrations
diff --git a/premiumPlugin/models.py b/premiumPlugin/models.py
new file mode 100644
index 000000000..a4396a32a
--- /dev/null
+++ b/premiumPlugin/models.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+from django.db import models
+
+
+class PremiumPluginConfig(models.Model):
+ """Config for Premium Plugin - activation key and payment preference."""
+ PAYMENT_METHOD_CHOICES = [
+ ('patreon', 'Patreon Subscription'),
+ ('paypal', 'PayPal Payment'),
+ ('both', 'Check Both (Patreon or PayPal)'),
+ ]
+ payment_method = models.CharField(
+ max_length=10,
+ choices=PAYMENT_METHOD_CHOICES,
+ default='both',
+ help_text="Choose which payment method to use for verification."
+ )
+ activation_key = models.CharField(
+ max_length=64,
+ blank=True,
+ default='',
+ help_text="Validated activation key - grants access without re-entering."
+ )
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ verbose_name = "Premium Plugin Configuration"
+ verbose_name_plural = "Premium Plugin Configurations"
+
+ def __str__(self):
+ return "Premium Plugin Configuration"
+
+ @classmethod
+ def get_config(cls):
+ """Get or create the singleton config instance."""
+ config, _ = cls.objects.get_or_create(pk=1)
+ return config
+
+ def save(self, *args, **kwargs):
+ self.pk = 1
+ super().save(*args, **kwargs)
diff --git a/premiumPlugin/templates/premiumPlugin/index.html b/premiumPlugin/templates/premiumPlugin/index.html
new file mode 100644
index 000000000..f7ab4e200
--- /dev/null
+++ b/premiumPlugin/templates/premiumPlugin/index.html
@@ -0,0 +1,96 @@
+{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Premium Plugin Example - CyberPanel" %}{% endblock %}
+
+{% block header_scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
{% trans "Premium Features" %}
+
+ {% for feature in features %}
+ - {{ feature }}
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/premiumPlugin/templates/premiumPlugin/settings.html b/premiumPlugin/templates/premiumPlugin/settings.html
new file mode 100644
index 000000000..59a83d132
--- /dev/null
+++ b/premiumPlugin/templates/premiumPlugin/settings.html
@@ -0,0 +1,315 @@
+{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Premium Plugin Settings - CyberPanel" %}{% endblock %}
+
+{% block header_scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/premiumPlugin/templates/premiumPlugin/subscription_required.html b/premiumPlugin/templates/premiumPlugin/subscription_required.html
new file mode 100644
index 000000000..cfc11a35a
--- /dev/null
+++ b/premiumPlugin/templates/premiumPlugin/subscription_required.html
@@ -0,0 +1,139 @@
+{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Payment Required - Premium Plugin" %}{% endblock %}
+
+{% block header_scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
{% trans "Premium Plugin Access Required" %}
+
{% trans "This plugin requires payment or subscription to access premium features." %}
+
+
+
+
{% trans "Activate Premium Access" %}
+
{% trans "If you received an activation key, enter it below." %}
+
+
+
+
+ {% if payment_method == 'both' %}
+
+ {% trans "Current Payment Method:" %} {% trans "Check Both (Patreon or PayPal)" %}
+
+ {% elif payment_method == 'patreon' %}
+
+ {% trans "Current Payment Method:" %} {% trans "Patreon Subscription Only" %}
+
+ {% elif payment_method == 'paypal' %}
+
+ {% trans "Current Payment Method:" %} {% trans "PayPal Payment Only" %}
+
+ {% endif %}
+
+
+
+
+
{% trans "How it works:" %}
+
+ - {% trans "Install the plugin (already done)" %}
+ - {% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}
+ - {% trans "The plugin will automatically unlock" %}
+
+
+
+ {% if error %}
+
+ {% trans "Error:" %} {{ error }}
+
+ {% endif %}
+
+
+
+
+{% endblock %}
diff --git a/premiumPlugin/urls.py b/premiumPlugin/urls.py
new file mode 100644
index 000000000..a9c206dbe
--- /dev/null
+++ b/premiumPlugin/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path, re_path
+from . import views
+
+app_name = 'premiumPlugin'
+
+urlpatterns = [
+ path('', views.main_view, name='main'),
+ path('settings/', views.settings_view, name='settings'),
+ re_path(r'^activate-key/$', views.activate_key, name='activate_key'),
+ path('save-payment-method/', views.save_payment_method, name='save_payment_method'),
+ path('api/status/', views.api_status_view, name='api_status'),
+]
diff --git a/premiumPlugin/views.py b/premiumPlugin/views.py
index e620f97c5..fb887850f 100644
--- a/premiumPlugin/views.py
+++ b/premiumPlugin/views.py
@@ -1,269 +1,406 @@
# -*- coding: utf-8 -*-
"""
-Premium Plugin Views - Remote Verification Version
-This version uses remote server verification (no secrets in plugin)
-SECURITY: All Patreon API calls happen on YOUR server, not user's server
+Premium Plugin Views - Unified Verification (same as contaboAutoSnapshot)
+Supports: Plugin Grants, Activation Key, Patreon, PayPal, AES encryption
"""
from django.shortcuts import render, redirect
-from django.http import JsonResponse
+from django.http import JsonResponse, HttpResponse
+from django.views.decorators.http import require_http_methods
from plogical.mailUtilities import mailUtilities
from plogical.httpProc import httpProc
+from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from functools import wraps
-import sys
-import os
import urllib.request
import urllib.error
import json
-# Remote verification server (YOUR server, not user's server)
-REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership'
-PLUGIN_NAME = 'premiumPlugin' # Patreon Premium Plugin Example
-PLUGIN_VERSION = '1.0.0'
+from .models import PremiumPluginConfig
+from . import api_encryption
+
+PLUGIN_NAME = 'premiumPlugin'
+PLUGIN_VERSION = '1.0.2'
+
+REMOTE_VERIFICATION_PATREON_URL = 'https://api.newstargeted.com/api/verify-patreon-membership.php'
+REMOTE_VERIFICATION_PAYPAL_URL = 'https://api.newstargeted.com/api/verify-paypal-payment.php'
+REMOTE_VERIFICATION_PLUGIN_GRANT_URL = 'https://api.newstargeted.com/api/verify-plugin-grant.php'
+REMOTE_ACTIVATION_KEY_URL = 'https://api.newstargeted.com/api/activate-plugin-key.php'
+
+PATREON_TIER = 'CyberPanel Paid Plugin'
+PATREON_URL = 'https://www.patreon.com/membership/27789984'
+PAYPAL_ME_URL = 'https://paypal.me/KimBS?locale.x=en_US&country.x=NO'
+PAYPAL_PAYMENT_LINK = ''
+
def cyberpanel_login_required(view_func):
- """
- Custom decorator that checks for CyberPanel session userID
- """
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
- userID = request.session['userID']
- # User is authenticated via CyberPanel session
+ if not request.session.get('userID'):
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
return view_func(request, *args, **kwargs)
except KeyError:
- # Not logged in, redirect to login
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return _wrapped_view
-def remote_verification_required(view_func):
- """
- Decorator that checks Patreon membership via remote server
- No secrets stored in plugin - all verification happens on your server
- """
- @wraps(view_func)
- def _wrapped_view(request, *args, **kwargs):
- # First check login
- try:
- userID = request.session['userID']
- except KeyError:
- from loginSystem.views import loadLoginPage
- return redirect(loadLoginPage)
-
- # Get user email
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- # Try to get from session or username
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- # Check membership via remote server
- verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', ''))
-
- if not verification_result.get('has_access', False):
- # User doesn't have subscription - show subscription required page
- context = {
- 'plugin_name': 'Patreon Premium Plugin Example',
- 'is_paid': True,
- 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'),
- 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
- 'message': verification_result.get('message', 'Patreon subscription required'),
- 'error': verification_result.get('error')
- }
- proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin')
- return proc.render()
-
- # User has access - proceed with view
- return view_func(request, *args, **kwargs)
-
- return _wrapped_view
-def check_remote_membership(user_email, user_ip=''):
- """
- Check Patreon membership via remote verification server
-
- Args:
- user_email: User's email address
- user_ip: User's IP address (for logging/security)
-
- Returns:
- dict: {
- 'has_access': bool,
- 'patreon_tier': str,
- 'patreon_url': str,
- 'message': str,
- 'error': str or None
- }
- """
+def _api_request(url, data, timeout=10):
+ """Send encrypted API request and return decoded response dict."""
+ try:
+ body, extra_headers = api_encryption.encrypt_payload(data)
+ headers = {
+ 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
+ 'X-Plugin-Name': PLUGIN_NAME
+ }
+ headers.update(extra_headers)
+ req = urllib.request.Request(url, data=body, headers=headers)
+ with urllib.request.urlopen(req, timeout=timeout) as response:
+ raw = response.read()
+ ct = response.headers.get('Content-Type', '')
+ expect_enc = extra_headers.get('X-Encrypted') == '1'
+ return api_encryption.decrypt_response(raw, ct, expect_encrypted=expect_enc)
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: API request error to {url}: {str(e)}")
+ return {}
+
+
+def check_plugin_grant(user_email, user_ip='', domain=''):
+ try:
+ request_data = {
+ 'user_email': user_email or '',
+ 'plugin_name': PLUGIN_NAME,
+ 'user_ip': user_ip,
+ 'domain': domain,
+ }
+ data = _api_request(REMOTE_VERIFICATION_PLUGIN_GRANT_URL, request_data)
+ if data.get('success') and data.get('has_access'):
+ return {'has_access': True, 'message': data.get('message', 'Access granted via Plugin Grants')}
+ return {'has_access': False, 'message': data.get('message', '')}
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Plugin grant check error: {str(e)}")
+ return {'has_access': False, 'message': ''}
+
+
+def check_patreon_membership(user_email, user_ip='', domain=''):
try:
- # Prepare request data
request_data = {
'user_email': user_email,
'plugin_name': PLUGIN_NAME,
'plugin_version': PLUGIN_VERSION,
'user_ip': user_ip,
- 'tier_id': '27789984' # CyberPanel Paid Plugin tier ID
+ 'domain': domain,
+ 'tier_id': '27789984'
}
-
- # Make request to remote verification server
- req = urllib.request.Request(
- REMOTE_VERIFICATION_URL,
- data=json.dumps(request_data).encode('utf-8'),
- headers={
- 'Content-Type': 'application/json',
- 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
- 'X-Plugin-Name': PLUGIN_NAME
- }
- )
-
- # Send request with timeout
- try:
- with urllib.request.urlopen(req, timeout=10) as response:
- response_data = json.loads(response.read().decode('utf-8'))
-
- if response_data.get('success', False):
- return {
- 'has_access': response_data.get('has_access', False),
- 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'),
- 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
- 'message': response_data.get('message', 'Access granted'),
- 'error': None
- }
- else:
- return {
- 'has_access': False,
- 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'),
- 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
- 'message': response_data.get('message', 'Patreon subscription required'),
- 'error': response_data.get('error')
- }
- except urllib.error.HTTPError as e:
- # Server returned error
- error_body = e.read().decode('utf-8') if e.fp else 'Unknown error'
+ response_data = _api_request(REMOTE_VERIFICATION_PATREON_URL, request_data)
+ if response_data.get('success', False):
return {
- 'has_access': False,
- 'patreon_tier': 'CyberPanel Paid Plugin',
- 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
- 'message': 'Unable to verify subscription. Please try again later.',
- 'error': f'HTTP {e.code}: {error_body}'
+ 'has_access': response_data.get('has_access', False),
+ 'patreon_tier': response_data.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': response_data.get('patreon_url', PATREON_URL),
+ 'message': response_data.get('message', 'Access granted'),
+ 'error': None
}
- except urllib.error.URLError as e:
- # Network error
- return {
- 'has_access': False,
- 'patreon_tier': 'CyberPanel Paid Plugin',
- 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
- 'message': 'Unable to connect to verification server. Please check your internet connection.',
- 'error': str(e.reason) if hasattr(e, 'reason') else str(e)
- }
- except Exception as e:
- # Other errors
- return {
- 'has_access': False,
- 'patreon_tier': 'CyberPanel Paid Plugin',
- 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
- 'message': 'Verification error occurred. Please try again later.',
- 'error': str(e)
- }
-
- except Exception as e:
- import logging
- logging.writeToFile(f"Error in remote membership check: {str(e)}")
return {
'has_access': False,
- 'patreon_tier': 'CyberPanel Paid Plugin',
- 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
- 'message': 'Verification error occurred. Please try again later.',
+ 'patreon_tier': PATREON_TIER,
+ 'patreon_url': PATREON_URL,
+ 'message': response_data.get('message', 'Patreon subscription required'),
+ 'error': response_data.get('error')
+ }
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Patreon check error: {str(e)}")
+ return {
+ 'has_access': False,
+ 'patreon_tier': PATREON_TIER,
+ 'patreon_url': PATREON_URL,
+ 'message': 'Unable to verify Patreon membership.',
'error': str(e)
}
-@cyberpanel_login_required
-def main_view(request):
- """
- Main view for premium plugin
- Shows plugin information and features if subscribed, or subscription required message if not
- """
- mailUtilities.checkHome()
-
- # Get user email for verification
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- # Check membership status (but don't block access)
- verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', ''))
- has_access = verification_result.get('has_access', False)
-
- # Determine plugin status
- plugin_status = 'Active' if has_access else 'Subscription Required'
-
- context = {
- 'plugin_name': 'Patreon Premium Plugin Example',
- 'version': PLUGIN_VERSION,
- 'status': plugin_status,
- 'has_access': has_access,
- 'description': 'This is an example paid plugin that requires Patreon subscription.' if not has_access else 'This is an example paid plugin. You have access because you are subscribed to Patreon!',
- 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'),
- 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/membership/27789984'),
- 'features': [
- 'Premium Feature 1',
- 'Premium Feature 2',
- 'Premium Feature 3',
- 'Advanced Configuration',
- 'Priority Support'
- ] if has_access else []
- }
-
- proc = httpProc(request, 'premiumPlugin/index.html', context, 'admin')
- return proc.render()
+
+def check_paypal_payment(user_email, user_ip='', domain=''):
+ try:
+ request_data = {
+ 'user_email': user_email,
+ 'plugin_name': PLUGIN_NAME,
+ 'plugin_version': PLUGIN_VERSION,
+ 'user_ip': user_ip,
+ 'domain': domain,
+ 'timestamp': 0,
+ }
+ import time
+ request_data['timestamp'] = int(time.time())
+ response_data = _api_request(REMOTE_VERIFICATION_PAYPAL_URL, request_data)
+ if response_data.get('success', False):
+ return {
+ 'has_access': response_data.get('has_access', False),
+ 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': response_data.get('message', 'Access granted'),
+ 'error': None
+ }
+ return {
+ 'has_access': False,
+ 'paypal_me_url': PAYPAL_ME_URL,
+ 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'message': response_data.get('message', 'PayPal payment required'),
+ 'error': response_data.get('error')
+ }
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: PayPal check error: {str(e)}")
+ return {
+ 'has_access': False,
+ 'paypal_me_url': PAYPAL_ME_URL,
+ 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'message': 'Unable to verify PayPal payment.',
+ 'error': str(e)
+ }
+
+
+def unified_verification_required(view_func):
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ try:
+ if not request.session.get('userID'):
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
+
+ user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') or getattr(request.user, 'username', '')
+
+ try:
+ config = PremiumPluginConfig.get_config()
+ payment_method = config.payment_method
+ except Exception:
+ payment_method = 'both'
+
+ has_access = False
+ verification_result = {}
+
+ activation_key = request.GET.get('activation_key') or request.POST.get('activation_key')
+ if not activation_key:
+ try:
+ config = PremiumPluginConfig.get_config()
+ activation_key = getattr(config, 'activation_key', '') or ''
+ except Exception:
+ activation_key = ''
+
+ if activation_key:
+ try:
+ request_data = {
+ 'activation_key': activation_key.strip(),
+ 'plugin_name': PLUGIN_NAME,
+ 'user_email': user_email
+ }
+ response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
+ if response_data.get('success', False) and response_data.get('has_access', False):
+ has_access = True
+ verification_result = {'method': 'activation_key', 'has_access': True, 'message': response_data.get('message', 'Access activated via key')}
+ try:
+ config = PremiumPluginConfig.get_config()
+ config.activation_key = activation_key.strip()
+ config.save(update_fields=['activation_key', 'updated_at'])
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Could not persist activation key: {str(e)}")
+ elif not response_data.get('success') and activation_key:
+ try:
+ config = PremiumPluginConfig.get_config()
+ if getattr(config, 'activation_key', '') == activation_key.strip():
+ config.activation_key = ''
+ config.save(update_fields=['activation_key', 'updated_at'])
+ except Exception:
+ pass
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Activation key check error: {str(e)}")
+
+ if not has_access:
+ grant_result = check_plugin_grant(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ if grant_result.get('has_access'):
+ has_access = True
+ verification_result = {'method': 'plugin_grant', 'has_access': True, 'message': grant_result.get('message', 'Access granted via Plugin Grants')}
+
+ if not has_access:
+ try:
+ if payment_method == 'patreon':
+ result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ has_access = result.get('has_access', False)
+ verification_result = {
+ 'method': 'patreon', 'has_access': has_access,
+ 'patreon_tier': result.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': result.get('patreon_url', PATREON_URL),
+ 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'message': result.get('message', 'Patreon subscription required'),
+ 'error': result.get('error')
+ }
+ elif payment_method == 'paypal':
+ result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ has_access = result.get('has_access', False)
+ verification_result = {
+ 'method': 'paypal', 'has_access': has_access,
+ 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
+ 'paypal_me_url': result.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': result.get('message', 'PayPal payment required'),
+ 'error': result.get('error')
+ }
+ else:
+ patreon_result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ paypal_result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
+ has_access = patreon_result.get('has_access', False) or paypal_result.get('has_access', False)
+ verification_result = {
+ 'method': 'both', 'has_access': has_access,
+ 'patreon_tier': patreon_result.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': patreon_result.get('patreon_url', PATREON_URL),
+ 'paypal_me_url': paypal_result.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': paypal_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': 'Payment or subscription required' if not has_access else 'Access granted'
+ }
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Verification error: {str(e)}")
+ has_access = False
+ verification_result = {
+ 'method': payment_method, 'has_access': False,
+ 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
+ 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'message': 'Unable to verify access.',
+ 'error': str(e)
+ }
+
+ if not has_access:
+ context = {
+ 'plugin_name': 'Premium Plugin Example',
+ 'is_paid': True,
+ 'payment_method': payment_method,
+ 'verification_result': verification_result,
+ 'patreon_tier': verification_result.get('patreon_tier', PATREON_TIER),
+ 'patreon_url': verification_result.get('patreon_url', PATREON_URL),
+ 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
+ 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
+ 'message': verification_result.get('message', 'Payment or subscription required'),
+ 'error': verification_result.get('error')
+ }
+ proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin')
+ return proc.render()
+
+ if has_access and verification_result:
+ request.session['premium_plugin_access_via'] = verification_result.get('method', '')
+
+ return view_func(request, *args, **kwargs)
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Decorator error: {str(e)}")
+ return HttpResponse(f"")
+ return _wrapped_view
+
@cyberpanel_login_required
-def settings_view(request):
- """
- Settings page for premium plugin
- Shows settings but disables them if user doesn't have Patreon subscription
- """
+def main_view(request):
mailUtilities.checkHome()
-
- # Get user email for verification
- user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
- if not user_email:
- user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
-
- # Check membership status (but don't block access)
- verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', ''))
- has_access = verification_result.get('has_access', False)
-
- # Determine plugin status
- plugin_status = 'Active' if has_access else 'Subscription Required'
-
+ return redirect('premiumPlugin:settings')
+
+
+@cyberpanel_login_required
+@unified_verification_required
+def settings_view(request):
+ mailUtilities.checkHome()
+ try:
+ config = PremiumPluginConfig.get_config()
+ except Exception:
+ from django.core.management import call_command
+ try:
+ call_command('migrate', 'premiumPlugin', verbosity=0, interactive=False)
+ config = PremiumPluginConfig.get_config()
+ except Exception as e:
+ return HttpResponse(f"")
+
+ access_via = request.session.get('premium_plugin_access_via', '')
+ show_payment_ui = access_via not in ('plugin_grant', 'activation_key')
+
context = {
- 'plugin_name': 'Patreon Premium Plugin Example',
+ 'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
- 'plugin_status': plugin_status,
- 'status': plugin_status, # Keep both for compatibility
- 'description': 'Configure your premium plugin settings',
- 'has_access': has_access,
- 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'),
- 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/membership/27789984'),
- 'verification_message': verification_result.get('message', '')
+ 'status': 'Active',
+ 'config': config,
+ 'has_access': True,
+ 'show_payment_ui': show_payment_ui,
+ 'access_via_grant_or_key': not show_payment_ui,
+ 'patreon_tier': PATREON_TIER,
+ 'patreon_url': PATREON_URL,
+ 'paypal_me_url': PAYPAL_ME_URL,
+ 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
+ 'description': 'Configure your premium plugin settings.',
}
-
proc = httpProc(request, 'premiumPlugin/settings.html', context, 'admin')
return proc.render()
+
@cyberpanel_login_required
-@remote_verification_required
+@require_http_methods(["POST"])
+def activate_key(request):
+ try:
+ if request.content_type == 'application/json':
+ data = json.loads(request.body)
+ else:
+ data = request.POST
+
+ activation_key = data.get('activation_key', '').strip()
+ user_email = data.get('user_email', '').strip()
+ if not user_email:
+ user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '')
+
+ if not activation_key:
+ return JsonResponse({'success': False, 'message': 'Activation key is required'}, status=400)
+
+ request_data = {'activation_key': activation_key, 'plugin_name': PLUGIN_NAME, 'user_email': user_email}
+ response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
+
+ if response_data.get('success', False) and response_data.get('has_access', False):
+ try:
+ config = PremiumPluginConfig.get_config()
+ config.activation_key = activation_key
+ config.save(update_fields=['activation_key', 'updated_at'])
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: Could not persist activation key: {str(e)}")
+
+ return JsonResponse({
+ 'success': True,
+ 'has_access': True,
+ 'message': response_data.get('message', 'Access activated successfully')
+ })
+
+ return JsonResponse({
+ 'success': False,
+ 'has_access': False,
+ 'message': response_data.get('message', 'Invalid activation key')
+ })
+
+ except Exception as e:
+ logging.writeToFile(f"Premium Plugin: activate_key error: {str(e)}")
+ return JsonResponse({'success': False, 'message': str(e)}, status=500)
+
+
+@cyberpanel_login_required
+@require_http_methods(["POST"])
+def save_payment_method(request):
+ try:
+ payment_method = request.POST.get('payment_method', 'both')
+ if payment_method not in ('patreon', 'paypal', 'both'):
+ payment_method = 'both'
+ config = PremiumPluginConfig.get_config()
+ config.payment_method = payment_method
+ config.save(update_fields=['payment_method', 'updated_at'])
+ return JsonResponse({'success': True, 'message': 'Payment method saved'})
+ except Exception as e:
+ return JsonResponse({'success': False, 'message': str(e)}, status=500)
+
+
+@cyberpanel_login_required
+@unified_verification_required
def api_status_view(request):
- """
- API endpoint for plugin status
- Only accessible with Patreon subscription (verified remotely)
- """
return JsonResponse({
- 'plugin_name': 'Patreon Premium Plugin Example',
+ 'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'active',
'subscription': 'active',
- 'description': 'Premium plugin is active and accessible',
- 'verification_method': 'remote'
+ 'verification_method': 'unified'
})
diff --git a/premiumPlugin/views_remote.py b/premiumPlugin/views_remote.py
new file mode 100644
index 000000000..a4adfaeeb
--- /dev/null
+++ b/premiumPlugin/views_remote.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+"""
+Premium Plugin Views - Remote Verification Version
+This version uses remote server verification (no secrets in plugin)
+"""
+
+from django.shortcuts import render, redirect
+from django.http import JsonResponse
+from plogical.mailUtilities import mailUtilities
+from plogical.httpProc import httpProc
+from functools import wraps
+import sys
+import os
+import urllib.request
+import urllib.error
+import json
+
+# Remote verification server (YOUR server, not user's server)
+REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership'
+PLUGIN_NAME = 'premiumPlugin'
+PLUGIN_VERSION = '1.0.0'
+
+def cyberpanel_login_required(view_func):
+ """
+ Custom decorator that checks for CyberPanel session userID
+ """
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ try:
+ userID = request.session['userID']
+ # User is authenticated via CyberPanel session
+ return view_func(request, *args, **kwargs)
+ except KeyError:
+ # Not logged in, redirect to login
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
+ return _wrapped_view
+
+def remote_verification_required(view_func):
+ """
+ Decorator that checks Patreon membership via remote server
+ No secrets stored in plugin - all verification happens on your server
+ """
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ # First check login
+ try:
+ userID = request.session['userID']
+ except KeyError:
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
+
+ # Get user email
+ user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
+ if not user_email:
+ # Try to get from session or username
+ user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
+
+ # Check membership via remote server
+ verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', ''))
+
+ if not verification_result.get('has_access', False):
+ # User doesn't have subscription - show subscription required page
+ context = {
+ 'plugin_name': 'Premium Plugin Example',
+ 'is_paid': True,
+ 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'),
+ 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
+ 'message': verification_result.get('message', 'Patreon subscription required'),
+ 'error': verification_result.get('error')
+ }
+ proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin')
+ return proc.render()
+
+ # User has access - proceed with view
+ return view_func(request, *args, **kwargs)
+
+ return _wrapped_view
+
+def check_remote_membership(user_email, user_ip=''):
+ """
+ Check Patreon membership via remote verification server
+
+ Args:
+ user_email: User's email address
+ user_ip: User's IP address (for logging/security)
+
+ Returns:
+ dict: {
+ 'has_access': bool,
+ 'patreon_tier': str,
+ 'patreon_url': str,
+ 'message': str,
+ 'error': str or None
+ }
+ """
+ try:
+ # Prepare request data
+ request_data = {
+ 'user_email': user_email,
+ 'plugin_name': PLUGIN_NAME,
+ 'plugin_version': PLUGIN_VERSION,
+ 'user_ip': user_ip,
+ 'tier_id': '27789984' # CyberPanel Paid Plugin tier ID
+ }
+
+ # Make request to remote verification server
+ req = urllib.request.Request(
+ REMOTE_VERIFICATION_URL,
+ data=json.dumps(request_data).encode('utf-8'),
+ headers={
+ 'Content-Type': 'application/json',
+ 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
+ 'X-Plugin-Name': PLUGIN_NAME
+ }
+ )
+
+ # Send request with timeout
+ try:
+ with urllib.request.urlopen(req, timeout=10) as response:
+ response_data = json.loads(response.read().decode('utf-8'))
+
+ if response_data.get('success', False):
+ return {
+ 'has_access': response_data.get('has_access', False),
+ 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'),
+ 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
+ 'message': response_data.get('message', 'Access granted'),
+ 'error': None
+ }
+ else:
+ return {
+ 'has_access': False,
+ 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'),
+ 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
+ 'message': response_data.get('message', 'Patreon subscription required'),
+ 'error': response_data.get('error')
+ }
+ except urllib.error.HTTPError as e:
+ # Server returned error
+ error_body = e.read().decode('utf-8') if e.fp else 'Unknown error'
+ return {
+ 'has_access': False,
+ 'patreon_tier': 'CyberPanel Paid Plugin',
+ 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
+ 'message': 'Unable to verify subscription. Please try again later.',
+ 'error': f'HTTP {e.code}: {error_body}'
+ }
+ except urllib.error.URLError as e:
+ # Network error
+ return {
+ 'has_access': False,
+ 'patreon_tier': 'CyberPanel Paid Plugin',
+ 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
+ 'message': 'Unable to connect to verification server. Please check your internet connection.',
+ 'error': str(e.reason) if hasattr(e, 'reason') else str(e)
+ }
+ except Exception as e:
+ # Other errors
+ return {
+ 'has_access': False,
+ 'patreon_tier': 'CyberPanel Paid Plugin',
+ 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
+ 'message': 'Verification error occurred. Please try again later.',
+ 'error': str(e)
+ }
+
+ except Exception as e:
+ import logging
+ logging.writeToFile(f"Error in remote membership check: {str(e)}")
+ return {
+ 'has_access': False,
+ 'patreon_tier': 'CyberPanel Paid Plugin',
+ 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
+ 'message': 'Verification error occurred. Please try again later.',
+ 'error': str(e)
+ }
+
+@cyberpanel_login_required
+@remote_verification_required
+def main_view(request):
+ """
+ Main view for premium plugin
+ Only accessible with Patreon subscription (verified remotely)
+ """
+ mailUtilities.checkHome()
+
+ context = {
+ 'plugin_name': 'Premium Plugin Example',
+ 'version': PLUGIN_VERSION,
+ 'description': 'This is an example paid plugin. You have access because you are subscribed to Patreon!',
+ 'features': [
+ 'Premium Feature 1',
+ 'Premium Feature 2',
+ 'Premium Feature 3',
+ 'Advanced Configuration',
+ 'Priority Support'
+ ]
+ }
+
+ proc = httpProc(request, 'premiumPlugin/index.html', context, 'admin')
+ return proc.render()
+
+@cyberpanel_login_required
+@remote_verification_required
+def settings_view(request):
+ """
+ Settings page for premium plugin
+ Only accessible with Patreon subscription (verified remotely)
+ """
+ mailUtilities.checkHome()
+
+ context = {
+ 'plugin_name': 'Premium Plugin Example',
+ 'version': PLUGIN_VERSION,
+ 'description': 'Configure your premium plugin settings'
+ }
+
+ proc = httpProc(request, 'premiumPlugin/settings.html', context, 'admin')
+ return proc.render()
+
+@cyberpanel_login_required
+@remote_verification_required
+def api_status_view(request):
+ """
+ API endpoint for plugin status
+ Only accessible with Patreon subscription (verified remotely)
+ """
+ return JsonResponse({
+ 'plugin_name': 'Premium Plugin Example',
+ 'version': PLUGIN_VERSION,
+ 'status': 'active',
+ 'subscription': 'active',
+ 'description': 'Premium plugin is active and accessible',
+ 'verification_method': 'remote'
+ })