mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 21:07:43 +02:00
Plugin updates: premiumPlugin & paypalPremiumPlugin unified verification, Installed Plugins UI improvements
This commit is contained in:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
58
paypalPremiumPlugin/README.md
Normal file
58
paypalPremiumPlugin/README.md
Normal file
@@ -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
|
||||
<paid>true</paid>
|
||||
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
|
||||
<patreon_url>https://www.patreon.com/c/newstargeted/membership</patreon_url>
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
master3395
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
4
paypalPremiumPlugin/__init__.py
Normal file
4
paypalPremiumPlugin/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# PayPal Premium Plugin Example
|
||||
# This is a paid plugin that requires PayPal payment
|
||||
|
||||
default_app_config = 'paypalPremiumPlugin.apps.PaypalpremiumpluginConfig'
|
||||
80
paypalPremiumPlugin/api_encryption.py
Normal file
80
paypalPremiumPlugin/api_encryption.py
Normal file
@@ -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 {}
|
||||
5
paypalPremiumPlugin/apps.py
Normal file
5
paypalPremiumPlugin/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class PaypalpremiumpluginConfig(AppConfig):
|
||||
name = 'paypalPremiumPlugin'
|
||||
verbose_name = 'PayPal Premium Plugin Example'
|
||||
30
paypalPremiumPlugin/meta.xml
Normal file
30
paypalPremiumPlugin/meta.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plugin>
|
||||
<name>PayPal Premium Plugin Example</name>
|
||||
<type>Utility</type>
|
||||
<version>1.0.2</version>
|
||||
<description>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.</description>
|
||||
<author>master3395</author>
|
||||
<website>https://github.com/master3395/cyberpanel-plugins</website>
|
||||
<license>MIT</license>
|
||||
<dependencies>
|
||||
<python>3.6+</python>
|
||||
<django>2.2+</django>
|
||||
<cyberpanel>2.5.5+</cyberpanel>
|
||||
</dependencies>
|
||||
<compatibility>
|
||||
<min_version>2.5.5</min_version>
|
||||
<max_version>3.0.0</max_version>
|
||||
</compatibility>
|
||||
<permissions>
|
||||
<admin>true</admin>
|
||||
<user>false</user>
|
||||
</permissions>
|
||||
<paid>true</paid>
|
||||
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
|
||||
<patreon_url>https://www.patreon.com/membership/27789984</patreon_url>
|
||||
<paypal_me_url>https://paypal.me/KimBS?locale.x=en_US&country.x=NO</paypal_me_url>
|
||||
<paypal_payment_link></paypal_payment_link>
|
||||
<url>/plugins/paypalPremiumPlugin/</url>
|
||||
<settings_url>/plugins/paypalPremiumPlugin/settings/</settings_url>
|
||||
</plugin>
|
||||
27
paypalPremiumPlugin/migrations/0001_initial.py
Normal file
27
paypalPremiumPlugin/migrations/0001_initial.py
Normal file
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
1
paypalPremiumPlugin/migrations/__init__.py
Normal file
1
paypalPremiumPlugin/migrations/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# PayPal Premium Plugin migrations
|
||||
40
paypalPremiumPlugin/models.py
Normal file
40
paypalPremiumPlugin/models.py
Normal file
@@ -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)
|
||||
96
paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html
Normal file
96
paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "PayPal Premium Plugin Example - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.premium-plugin-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.premium-badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.plugin-header {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.plugin-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.features-list h2 {
|
||||
margin-top: 0;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.features-list ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.features-list li {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e8e9ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.features-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.features-list li::before {
|
||||
content: "✓";
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="premium-plugin-container">
|
||||
<div class="plugin-header">
|
||||
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
|
||||
<h1>{{ plugin_name }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
<p><strong>{% trans "Version:" %}</strong> {{ version }}</p>
|
||||
</div>
|
||||
|
||||
<div class="features-list">
|
||||
<h2>{% trans "Premium Features" %}</h2>
|
||||
<ul>
|
||||
{% for feature in features %}
|
||||
<li>{{ feature }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
331
paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html
Normal file
331
paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html
Normal file
@@ -0,0 +1,331 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "PayPal Premium Plugin Settings - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.premium-plugin-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.premium-badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.payment-warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 8px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.payment-warning-content {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.payment-warning-title {
|
||||
font-weight: 600;
|
||||
color: #856404;
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.payment-warning-text {
|
||||
color: #856404;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.paypal-button {
|
||||
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
white-space: nowrap;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.paypal-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 112, 186, 0.4);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.paypal-me-button {
|
||||
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
|
||||
}
|
||||
|
||||
.paypal-payment-link-button {
|
||||
background: linear-gradient(135deg, #009cde 0%, #0070ba 100%);
|
||||
}
|
||||
|
||||
.settings-disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-disabled::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #5856d6;
|
||||
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
|
||||
}
|
||||
|
||||
.form-control:disabled {
|
||||
background-color: #f5f5f5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #5856d6 0%, #4a90e2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(88, 86, 214, 0.4);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Plugin Information Section - Ensure visibility */
|
||||
.plugin-info-section {
|
||||
background: #d1ecf1 !important;
|
||||
border: 1px solid #bee5eb !important;
|
||||
border-radius: 8px !important;
|
||||
padding: 15px 20px !important;
|
||||
margin-bottom: 25px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: relative !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
.plugin-info-section ul {
|
||||
list-style: none !important;
|
||||
padding-left: 0 !important;
|
||||
margin-top: 10px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.plugin-info-section li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.plugin-info-section li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.payment-buttons-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="premium-plugin-container">
|
||||
<div class="settings-form">
|
||||
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
|
||||
<h1>{% trans "PayPal Premium Plugin Settings" %}</h1>
|
||||
|
||||
{% if show_payment_ui %}
|
||||
<!-- Activate Premium Access -->
|
||||
<div style="background: #e6f7ff; border: 2px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
|
||||
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
|
||||
<p style="color: #495057;">{% trans "If you have an activation key, enter it below." %}</p>
|
||||
<div id="activation-msg" style="display: none; margin-bottom: 10px; padding: 10px; border-radius: 6px;"></div>
|
||||
<form id="activation-form" style="display: flex; gap: 10px; align-items: flex-end;">
|
||||
{% csrf_token %}
|
||||
<input type="text" id="activation-key" name="activation_key" placeholder="PAYPALPREMIUMPLUGIN-XXXX-XXXX-XXXX" style="flex: 1; padding: 10px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" />
|
||||
<button type="submit" class="btn btn-primary" id="activate-btn"><i class="fas fa-check-circle"></i> {% trans "Activate" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Payment Method Preference -->
|
||||
<div style="margin-bottom: 25px;">
|
||||
<label style="font-weight: 600;">{% trans "Payment Method Preference" %}</label>
|
||||
<form id="payment-method-form" method="post" action="{% url 'paypalPremiumPlugin:save_payment_method' %}" style="display: inline-block; margin-left: 10px;">
|
||||
{% csrf_token %}
|
||||
<select name="payment_method" onchange="this.form.submit()" style="padding: 8px 12px; border-radius: 6px;">
|
||||
<option value="both" {% if config.payment_method == 'both' %}selected{% endif %}>{% trans "Check Both (Patreon or PayPal)" %}</option>
|
||||
<option value="patreon" {% if config.payment_method == 'patreon' %}selected{% endif %}>{% trans "Patreon Only" %}</option>
|
||||
<option value="paypal" {% if config.payment_method == 'paypal' %}selected{% endif %}>{% trans "PayPal Only" %}</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="background: #d4edda; border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
|
||||
<i class="fas fa-check-circle" style="color: #28a745;"></i> <strong>{% trans "Premium Access Active" %}</strong> — {% trans "Access granted via Plugin Grants or activation key." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Plugin Information Section - Always Visible -->
|
||||
<div class="plugin-info-section" style="background: #d1ecf1 !important; border: 1px solid #bee5eb !important; border-radius: 8px !important; padding: 15px 20px !important; margin-bottom: 25px !important; display: block !important; visibility: visible !important; opacity: 1 !important;">
|
||||
<i class="fas fa-info-circle" style="color: #0c5460; margin-right: 8px;"></i>
|
||||
<strong style="color: #0c5460; font-weight: 600;">{% trans "Plugin Information" %}</strong>
|
||||
<ul style="list-style: none !important; padding-left: 0 !important; margin-top: 10px !important; margin-bottom: 0 !important;">
|
||||
<li style="margin-bottom: 8px;"><strong>{% trans "Name" %}:</strong> {{ plugin_name|default:"PayPal Premium Plugin Example" }}</li>
|
||||
<li style="margin-bottom: 8px;"><strong>{% trans "Version" %}:</strong> {{ version|default:"1.0.0" }}</li>
|
||||
<li style="margin-bottom: 0;">
|
||||
<strong>{% trans "Status" %}:</strong>
|
||||
<span class="badge" style="background: {% if has_access %}#28a745{% else %}#6c757d{% endif %} !important; color: white !important; padding: 4px 10px !important; border-radius: 4px !important; font-size: 12px !important; font-weight: 600 !important; display: inline-block !important;">
|
||||
{{ plugin_status|default:status|default:"Active" }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>{{ description }}</p>
|
||||
|
||||
<form method="post" id="settings-form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label>{% trans "Setting 1" %}</label>
|
||||
<input type="text" class="form-control" name="setting1" value="" placeholder="{% trans 'Enter setting value' %}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{% trans "Setting 2" %}</label>
|
||||
<input type="text" class="form-control" name="setting2" value="" placeholder="{% trans 'Enter setting value' %}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{% trans "Setting 3 (Advanced)" %}</label>
|
||||
<textarea class="form-control" name="setting3" rows="4" placeholder="{% trans 'Enter advanced configuration' %}"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="enable_feature">
|
||||
{% trans "Enable Premium Feature" %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> {% trans "Save Settings" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if show_payment_ui %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('activation-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const key = document.getElementById('activation-key').value.trim().toUpperCase();
|
||||
if (!key) return;
|
||||
const btn = document.getElementById('activate-btn');
|
||||
const msg = document.getElementById('activation-msg');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
|
||||
try {
|
||||
const res = await fetch('/plugins/paypalPremiumPlugin/activate-key/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value },
|
||||
body: JSON.stringify({ activation_key: key })
|
||||
});
|
||||
const data = await res.json();
|
||||
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
|
||||
msg.style.color = data.success ? '#155724' : '#721c24';
|
||||
if (data.success) { document.getElementById('activation-key').value = ''; setTimeout(function() { location.reload(); }, 2000); }
|
||||
} catch (err) {
|
||||
msg.textContent = '❌ ' + err.message;
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = '#f8d7da';
|
||||
msg.style.color = '#721c24';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,139 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Payment Required - PayPal Premium Plugin" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.subscription-required-container { padding: 40px 20px; max-width: 900px; margin: 0 auto; text-align: center; }
|
||||
.subscription-card { background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
|
||||
.premium-icon { font-size: 64px; color: #0070ba; margin-bottom: 20px; }
|
||||
.subscription-card h1 { color: #2f3640; margin-bottom: 15px; }
|
||||
.subscription-card p { color: #64748b; font-size: 16px; line-height: 1.6; margin-bottom: 30px; }
|
||||
.payment-methods { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
|
||||
.payment-method-card { flex: 1; min-width: 250px; max-width: 350px; background: #f8f9ff; border: 2px solid #e2e8f0; border-radius: 12px; padding: 25px; text-align: center; }
|
||||
.payment-method-card.patreon { border-color: #FF424D; }
|
||||
.payment-method-card.paypal { border-color: #0070ba; }
|
||||
.payment-method-card h3 { margin-top: 0; color: #2f3640; font-size: 1.3rem; }
|
||||
.patreon-button, .paypal-button { display: inline-block; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; transition: all 0.3s ease; width: 100%; box-sizing: border-box; margin-bottom: 10px; }
|
||||
.patreon-button { background: #FF424D; }
|
||||
.patreon-button:hover { background: #E62E37; color: white; text-decoration: none; }
|
||||
.paypal-button { background: #0070ba; }
|
||||
.paypal-button:hover { background: #003087; color: white; text-decoration: none; }
|
||||
.info-box { background: #f8f9ff; border-left: 4px solid #0070ba; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: left; }
|
||||
.payment-method-selector { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: left; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="subscription-required-container">
|
||||
<div class="subscription-card">
|
||||
<div class="premium-icon"><i class="fab fa-paypal"></i></div>
|
||||
<h1>{% trans "Premium Plugin Access Required" %}</h1>
|
||||
<p>{% trans "This plugin requires payment or subscription to access premium features." %}</p>
|
||||
|
||||
<!-- Activation Key Section -->
|
||||
<div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); border: 2px solid #17a2b8; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: left;">
|
||||
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
|
||||
<p style="color: #495057; margin-bottom: 20px;">{% trans "If you received an activation key, enter it below." %}</p>
|
||||
<div id="activation-message" style="display: none; margin-bottom: 15px; padding: 12px; border-radius: 6px;"></div>
|
||||
<form id="activation-key-form" style="display: flex; gap: 10px; align-items: flex-end; max-width: 600px;">
|
||||
{% csrf_token %}
|
||||
<div style="flex: 1;">
|
||||
<label for="activation-key-input" style="font-weight: 600; display: block; margin-bottom: 8px;">{% trans "Activation Key" %}</label>
|
||||
<input type="text" id="activation-key-input" name="activation_key" placeholder="PAYPALPREMIUMPLUGIN-XXXX-XXXX-XXXX" style="width: 100%; padding: 12px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" required />
|
||||
</div>
|
||||
<button type="submit" style="background: #17a2b8; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 600; cursor: pointer;" id="activate-btn">
|
||||
<i class="fas fa-check-circle"></i> {% trans "Activate" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if payment_method == 'both' %}
|
||||
<div class="payment-method-selector">
|
||||
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Check Both (Patreon or PayPal)" %}
|
||||
</div>
|
||||
{% elif payment_method == 'patreon' %}
|
||||
<div class="payment-method-selector">
|
||||
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Patreon Subscription Only" %}
|
||||
</div>
|
||||
{% elif payment_method == 'paypal' %}
|
||||
<div class="payment-method-selector">
|
||||
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "PayPal Payment Only" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="payment-methods">
|
||||
<div class="payment-method-card patreon">
|
||||
<h3><i class="fab fa-patreon"></i> {% trans "Patreon Subscription" %}</h3>
|
||||
<p>{% trans "Subscribe to" %} <strong>"{{ patreon_tier }}"</strong></p>
|
||||
<a href="{{ patreon_url }}" target="_blank" rel="noopener noreferrer" class="patreon-button">
|
||||
<i class="fab fa-patreon"></i> {% trans "Subscribe on Patreon" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="payment-method-card paypal">
|
||||
<h3><i class="fab fa-paypal"></i> {% trans "PayPal Payment" %}</h3>
|
||||
<p>{% trans "Complete one-time payment via PayPal" %}</p>
|
||||
{% if paypal_me_url %}
|
||||
<a href="{{ paypal_me_url }}" target="_blank" rel="noopener noreferrer" class="paypal-button">
|
||||
<i class="fab fa-paypal"></i> {% trans "Pay with PayPal.me" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>{% trans "How it works:" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "Install the plugin (already done)" %}</li>
|
||||
<li>{% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}</li>
|
||||
<li>{% trans "The plugin will automatically unlock" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div style="background: #fee; border: 1px solid #fcc; border-radius: 8px; padding: 15px; margin-top: 20px; color: #c33;">
|
||||
<strong>{% trans "Error:" %}</strong> {{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('activation-key-form');
|
||||
const input = document.getElementById('activation-key-input');
|
||||
const btn = document.getElementById('activate-btn');
|
||||
const msg = document.getElementById('activation-message');
|
||||
if (form) {
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const key = input.value.trim().toUpperCase();
|
||||
if (!key) return;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
|
||||
try {
|
||||
const res = await fetch('/plugins/paypalPremiumPlugin/activate-key/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('input[name=csrfmiddlewaretoken]') ? document.querySelector('input[name=csrfmiddlewaretoken]').value : '' },
|
||||
body: JSON.stringify({ activation_key: key })
|
||||
});
|
||||
const data = await res.json();
|
||||
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
|
||||
msg.style.color = data.success ? '#155724' : '#721c24';
|
||||
if (data.success) { input.value = ''; setTimeout(function() { location.reload(); }, 2000); }
|
||||
} catch (err) {
|
||||
msg.textContent = '❌ ' + err.message;
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = '#f8d7da';
|
||||
msg.style.color = '#721c24';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
12
paypalPremiumPlugin/urls.py
Normal file
12
paypalPremiumPlugin/urls.py
Normal file
@@ -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'),
|
||||
]
|
||||
@@ -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"<div style='padding: 20px;'><h2>Plugin Error</h2><p>{str(e)}</p></div>")
|
||||
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"<div style='padding: 20px;'><h2>Database Error</h2><p>{str(e)}</p></div>")
|
||||
|
||||
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'
|
||||
})
|
||||
|
||||
@@ -1091,6 +1091,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- plugins-v2026-02-02-no-patreon-front -->
|
||||
{% load static %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
|
||||
6
premiumPlugin/.gitignore
vendored
Normal file
6
premiumPlugin/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Security - Never commit secrets
|
||||
*.secret
|
||||
*_secret*
|
||||
patreon_config.py
|
||||
.env.patreon
|
||||
patreon_secrets.env
|
||||
81
premiumPlugin/README-REMOTE-VERIFICATION.md
Normal file
81
premiumPlugin/README-REMOTE-VERIFICATION.md
Normal file
@@ -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
|
||||
58
premiumPlugin/README.md
Normal file
58
premiumPlugin/README.md
Normal file
@@ -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
|
||||
<paid>true</paid>
|
||||
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
|
||||
<patreon_url>https://www.patreon.com/c/newstargeted/membership</patreon_url>
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
master3395
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
57
premiumPlugin/SECURITY.md
Normal file
57
premiumPlugin/SECURITY.md
Normal file
@@ -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
|
||||
4
premiumPlugin/__init__.py
Normal file
4
premiumPlugin/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Premium Plugin Example
|
||||
# This is a paid plugin that requires Patreon subscription
|
||||
|
||||
default_app_config = 'premiumPlugin.apps.PremiumPluginConfig'
|
||||
83
premiumPlugin/api_encryption.py
Normal file
83
premiumPlugin/api_encryption.py
Normal file
@@ -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 {}
|
||||
5
premiumPlugin/apps.py
Normal file
5
premiumPlugin/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class PremiumPluginConfig(AppConfig):
|
||||
name = 'premiumPlugin'
|
||||
verbose_name = 'Premium Plugin Example'
|
||||
30
premiumPlugin/meta.xml
Normal file
30
premiumPlugin/meta.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plugin>
|
||||
<name>Premium Plugin Example</name>
|
||||
<type>Utility</type>
|
||||
<version>1.0.2</version>
|
||||
<description>An example paid plugin that requires Patreon subscription to "CyberPanel Paid Plugin" tier. Users can install it but cannot run it without subscription.</description>
|
||||
<author>master3395</author>
|
||||
<website>https://github.com/master3395/cyberpanel-plugins</website>
|
||||
<license>MIT</license>
|
||||
<dependencies>
|
||||
<python>3.6+</python>
|
||||
<django>2.2+</django>
|
||||
<cyberpanel>2.5.5+</cyberpanel>
|
||||
</dependencies>
|
||||
<compatibility>
|
||||
<min_version>2.5.5</min_version>
|
||||
<max_version>3.0.0</max_version>
|
||||
</compatibility>
|
||||
<permissions>
|
||||
<admin>true</admin>
|
||||
<user>false</user>
|
||||
</permissions>
|
||||
<paid>true</paid>
|
||||
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
|
||||
<patreon_url>https://www.patreon.com/membership/27789984</patreon_url>
|
||||
<paypal_me_url>https://paypal.me/KimBS?locale.x=en_US&country.x=NO</paypal_me_url>
|
||||
<paypal_payment_link></paypal_payment_link>
|
||||
<url>/plugins/premiumPlugin/</url>
|
||||
<settings_url>/plugins/premiumPlugin/settings/</settings_url>
|
||||
</plugin>
|
||||
27
premiumPlugin/migrations/0001_initial.py
Normal file
27
premiumPlugin/migrations/0001_initial.py
Normal file
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
1
premiumPlugin/migrations/__init__.py
Normal file
1
premiumPlugin/migrations/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Premium Plugin migrations
|
||||
42
premiumPlugin/models.py
Normal file
42
premiumPlugin/models.py
Normal file
@@ -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)
|
||||
96
premiumPlugin/templates/premiumPlugin/index.html
Normal file
96
premiumPlugin/templates/premiumPlugin/index.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Premium Plugin Example - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.premium-plugin-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.premium-badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.plugin-header {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.plugin-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.features-list h2 {
|
||||
margin-top: 0;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.features-list ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.features-list li {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e8e9ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.features-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.features-list li::before {
|
||||
content: "✓";
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="premium-plugin-container">
|
||||
<div class="plugin-header">
|
||||
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
|
||||
<h1>{{ plugin_name }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
<p><strong>{% trans "Version:" %}</strong> {{ version }}</p>
|
||||
</div>
|
||||
|
||||
<div class="features-list">
|
||||
<h2>{% trans "Premium Features" %}</h2>
|
||||
<ul>
|
||||
{% for feature in features %}
|
||||
<li>{{ feature }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
315
premiumPlugin/templates/premiumPlugin/settings.html
Normal file
315
premiumPlugin/templates/premiumPlugin/settings.html
Normal file
@@ -0,0 +1,315 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Premium Plugin Settings - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.premium-plugin-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.premium-badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.subscription-warning {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 8px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.subscription-warning-content {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.subscription-warning-title {
|
||||
font-weight: 600;
|
||||
color: #856404;
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subscription-warning-text {
|
||||
color: #856404;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.subscription-warning-button {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.subscription-warning-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.settings-disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-disabled::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #5856d6;
|
||||
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
|
||||
}
|
||||
|
||||
.form-control:disabled {
|
||||
background-color: #f5f5f5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #5856d6 0%, #4a90e2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(88, 86, 214, 0.4);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Plugin Information Section - Ensure visibility */
|
||||
.plugin-info-section {
|
||||
background: #d1ecf1 !important;
|
||||
border: 1px solid #bee5eb !important;
|
||||
border-radius: 8px !important;
|
||||
padding: 15px 20px !important;
|
||||
margin-bottom: 25px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: relative !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
.plugin-info-section ul {
|
||||
list-style: none !important;
|
||||
padding-left: 0 !important;
|
||||
margin-top: 10px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.plugin-info-section li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.plugin-info-section li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="premium-plugin-container">
|
||||
<div class="settings-form">
|
||||
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
|
||||
<h1>{% trans "Premium Plugin Settings" %}</h1>
|
||||
|
||||
{% if show_payment_ui %}
|
||||
<!-- Activate Premium Access -->
|
||||
<div style="background: #e6f7ff; border: 2px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
|
||||
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
|
||||
<p style="color: #495057;">{% trans "If you have an activation key, enter it below." %}</p>
|
||||
<div id="activation-msg" style="display: none; margin-bottom: 10px; padding: 10px; border-radius: 6px;"></div>
|
||||
<form id="activation-form" style="display: flex; gap: 10px; align-items: flex-end;">
|
||||
{% csrf_token %}
|
||||
<input type="text" id="activation-key" name="activation_key" placeholder="PREMIUMPLUGIN-XXXX-XXXX-XXXX" style="flex: 1; padding: 10px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" />
|
||||
<button type="submit" class="btn btn-primary" id="activate-btn"><i class="fas fa-check-circle"></i> {% trans "Activate" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Payment Method Preference -->
|
||||
<div style="margin-bottom: 25px;">
|
||||
<label style="font-weight: 600;">{% trans "Payment Method Preference" %}</label>
|
||||
<form id="payment-method-form" method="post" action="{% url 'premiumPlugin:save_payment_method' %}" style="display: inline-block; margin-left: 10px;">
|
||||
{% csrf_token %}
|
||||
<select name="payment_method" onchange="this.form.submit()" style="padding: 8px 12px; border-radius: 6px;">
|
||||
<option value="both" {% if config.payment_method == 'both' %}selected{% endif %}>{% trans "Check Both (Patreon or PayPal)" %}</option>
|
||||
<option value="patreon" {% if config.payment_method == 'patreon' %}selected{% endif %}>{% trans "Patreon Only" %}</option>
|
||||
<option value="paypal" {% if config.payment_method == 'paypal' %}selected{% endif %}>{% trans "PayPal Only" %}</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="background: #d4edda; border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
|
||||
<i class="fas fa-check-circle" style="color: #28a745;"></i> <strong>{% trans "Premium Access Active" %}</strong> — {% trans "Access granted via Plugin Grants or activation key." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Plugin Information Section - Always Visible -->
|
||||
<div class="plugin-info-section" style="background: #d1ecf1 !important; border: 1px solid #bee5eb !important; border-radius: 8px !important; padding: 15px 20px !important; margin-bottom: 25px !important; display: block !important; visibility: visible !important; opacity: 1 !important;">
|
||||
<i class="fas fa-info-circle" style="color: #0c5460; margin-right: 8px;"></i>
|
||||
<strong style="color: #0c5460; font-weight: 600;">{% trans "Plugin Information" %}</strong>
|
||||
<ul style="list-style: none !important; padding-left: 0 !important; margin-top: 10px !important; margin-bottom: 0 !important;">
|
||||
<li style="margin-bottom: 8px;"><strong>{% trans "Name" %}:</strong> {{ plugin_name|default:"Premium Plugin Example" }}</li>
|
||||
<li style="margin-bottom: 8px;"><strong>{% trans "Version" %}:</strong> {{ version|default:"1.0.0" }}</li>
|
||||
<li style="margin-bottom: 0;">
|
||||
<strong>{% trans "Status" %}:</strong>
|
||||
<span class="badge" style="background: {% if has_access %}#28a745{% else %}#6c757d{% endif %} !important; color: white !important; padding: 4px 10px !important; border-radius: 4px !important; font-size: 12px !important; font-weight: 600 !important; display: inline-block !important;">
|
||||
{{ plugin_status|default:status|default:"Active" }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>{{ description }}</p>
|
||||
|
||||
<form method="post" id="settings-form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label>{% trans "Setting 1" %}</label>
|
||||
<input type="text" class="form-control" name="setting1" value="" placeholder="{% trans 'Enter setting value' %}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{% trans "Setting 2" %}</label>
|
||||
<input type="text" class="form-control" name="setting2" value="" placeholder="{% trans 'Enter setting value' %}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{% trans "Setting 3 (Advanced)" %}</label>
|
||||
<textarea class="form-control" name="setting3" rows="4" placeholder="{% trans 'Enter advanced configuration' %}"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="enable_feature">
|
||||
{% trans "Enable Premium Feature" %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> {% trans "Save Settings" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if show_payment_ui %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('activation-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const key = document.getElementById('activation-key').value.trim().toUpperCase();
|
||||
if (!key) return;
|
||||
const btn = document.getElementById('activate-btn');
|
||||
const msg = document.getElementById('activation-msg');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
|
||||
try {
|
||||
const res = await fetch('/plugins/premiumPlugin/activate-key/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value },
|
||||
body: JSON.stringify({ activation_key: key })
|
||||
});
|
||||
const data = await res.json();
|
||||
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
|
||||
msg.style.color = data.success ? '#155724' : '#721c24';
|
||||
if (data.success) { document.getElementById('activation-key').value = ''; setTimeout(function() { location.reload(); }, 2000); }
|
||||
} catch (err) {
|
||||
msg.textContent = '❌ ' + err.message;
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = '#f8d7da';
|
||||
msg.style.color = '#721c24';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
139
premiumPlugin/templates/premiumPlugin/subscription_required.html
Normal file
139
premiumPlugin/templates/premiumPlugin/subscription_required.html
Normal file
@@ -0,0 +1,139 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Payment Required - Premium Plugin" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.subscription-required-container { padding: 40px 20px; max-width: 900px; margin: 0 auto; text-align: center; }
|
||||
.subscription-card { background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
|
||||
.premium-icon { font-size: 64px; color: #FF6B35; margin-bottom: 20px; }
|
||||
.subscription-card h1 { color: #2f3640; margin-bottom: 15px; }
|
||||
.subscription-card p { color: #64748b; font-size: 16px; line-height: 1.6; margin-bottom: 30px; }
|
||||
.payment-methods { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
|
||||
.payment-method-card { flex: 1; min-width: 250px; max-width: 350px; background: #f8f9ff; border: 2px solid #e2e8f0; border-radius: 12px; padding: 25px; text-align: center; }
|
||||
.payment-method-card.patreon { border-color: #FF424D; }
|
||||
.payment-method-card.paypal { border-color: #0070ba; }
|
||||
.payment-method-card h3 { margin-top: 0; color: #2f3640; font-size: 1.3rem; }
|
||||
.patreon-button, .paypal-button { display: inline-block; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; transition: all 0.3s ease; width: 100%; box-sizing: border-box; margin-bottom: 10px; }
|
||||
.patreon-button { background: #FF424D; }
|
||||
.patreon-button:hover { background: #E62E37; color: white; text-decoration: none; }
|
||||
.paypal-button { background: #0070ba; }
|
||||
.paypal-button:hover { background: #003087; color: white; text-decoration: none; }
|
||||
.info-box { background: #f8f9ff; border-left: 4px solid #FF6B35; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: left; }
|
||||
.payment-method-selector { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: left; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="subscription-required-container">
|
||||
<div class="subscription-card">
|
||||
<div class="premium-icon"><i class="fas fa-crown"></i></div>
|
||||
<h1>{% trans "Premium Plugin Access Required" %}</h1>
|
||||
<p>{% trans "This plugin requires payment or subscription to access premium features." %}</p>
|
||||
|
||||
<!-- Activation Key Section -->
|
||||
<div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); border: 2px solid #17a2b8; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: left;">
|
||||
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
|
||||
<p style="color: #495057; margin-bottom: 20px;">{% trans "If you received an activation key, enter it below." %}</p>
|
||||
<div id="activation-message" style="display: none; margin-bottom: 15px; padding: 12px; border-radius: 6px;"></div>
|
||||
<form id="activation-key-form" style="display: flex; gap: 10px; align-items: flex-end; max-width: 600px;">
|
||||
{% csrf_token %}
|
||||
<div style="flex: 1;">
|
||||
<label for="activation-key-input" style="font-weight: 600; display: block; margin-bottom: 8px;">{% trans "Activation Key" %}</label>
|
||||
<input type="text" id="activation-key-input" name="activation_key" placeholder="PREMIUMPLUGIN-XXXX-XXXX-XXXX" style="width: 100%; padding: 12px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" required />
|
||||
</div>
|
||||
<button type="submit" style="background: #17a2b8; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 600; cursor: pointer;" id="activate-btn">
|
||||
<i class="fas fa-check-circle"></i> {% trans "Activate" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if payment_method == 'both' %}
|
||||
<div class="payment-method-selector">
|
||||
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Check Both (Patreon or PayPal)" %}
|
||||
</div>
|
||||
{% elif payment_method == 'patreon' %}
|
||||
<div class="payment-method-selector">
|
||||
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Patreon Subscription Only" %}
|
||||
</div>
|
||||
{% elif payment_method == 'paypal' %}
|
||||
<div class="payment-method-selector">
|
||||
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "PayPal Payment Only" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="payment-methods">
|
||||
<div class="payment-method-card patreon">
|
||||
<h3><i class="fab fa-patreon"></i> {% trans "Patreon Subscription" %}</h3>
|
||||
<p>{% trans "Subscribe to" %} <strong>"{{ patreon_tier }}"</strong></p>
|
||||
<a href="{{ patreon_url }}" target="_blank" rel="noopener noreferrer" class="patreon-button">
|
||||
<i class="fab fa-patreon"></i> {% trans "Subscribe on Patreon" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="payment-method-card paypal">
|
||||
<h3><i class="fab fa-paypal"></i> {% trans "PayPal Payment" %}</h3>
|
||||
<p>{% trans "Complete one-time payment via PayPal" %}</p>
|
||||
{% if paypal_me_url %}
|
||||
<a href="{{ paypal_me_url }}" target="_blank" rel="noopener noreferrer" class="paypal-button">
|
||||
<i class="fab fa-paypal"></i> {% trans "Pay with PayPal.me" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>{% trans "How it works:" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "Install the plugin (already done)" %}</li>
|
||||
<li>{% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}</li>
|
||||
<li>{% trans "The plugin will automatically unlock" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div style="background: #fee; border: 1px solid #fcc; border-radius: 8px; padding: 15px; margin-top: 20px; color: #c33;">
|
||||
<strong>{% trans "Error:" %}</strong> {{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('activation-key-form');
|
||||
const input = document.getElementById('activation-key-input');
|
||||
const btn = document.getElementById('activate-btn');
|
||||
const msg = document.getElementById('activation-message');
|
||||
if (form) {
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const key = input.value.trim().toUpperCase();
|
||||
if (!key) return;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
|
||||
try {
|
||||
const res = await fetch('/plugins/premiumPlugin/activate-key/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('input[name=csrfmiddlewaretoken]') ? document.querySelector('input[name=csrfmiddlewaretoken]').value : (document.cookie.match(/csrftoken=([^;]+)/) ? document.cookie.match(/csrftoken=([^;]+)/)[1].trim() : '') },
|
||||
body: JSON.stringify({ activation_key: key })
|
||||
});
|
||||
const data = await res.json();
|
||||
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
|
||||
msg.style.color = data.success ? '#155724' : '#721c24';
|
||||
if (data.success) { input.value = ''; setTimeout(() => location.reload(), 2000); }
|
||||
} catch (err) {
|
||||
msg.textContent = '❌ ' + err.message;
|
||||
msg.style.display = 'block';
|
||||
msg.style.background = '#f8d7da';
|
||||
msg.style.color = '#721c24';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
12
premiumPlugin/urls.py
Normal file
12
premiumPlugin/urls.py
Normal file
@@ -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'),
|
||||
]
|
||||
@@ -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"<div style='padding: 20px;'><h2>Plugin Error</h2><p>{str(e)}</p></div>")
|
||||
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"<div style='padding: 20px;'><h2>Database Error</h2><p>{str(e)}</p></div>")
|
||||
|
||||
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'
|
||||
})
|
||||
|
||||
236
premiumPlugin/views_remote.py
Normal file
236
premiumPlugin/views_remote.py
Normal file
@@ -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'
|
||||
})
|
||||
Reference in New Issue
Block a user