mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 01:39:24 +02:00
Refactor YPTWallet configuration: remove unused checks and add donation notification URL handling
https://github.com/WWBN/AVideo/issues/10138
This commit is contained in:
parent
bc240901ed
commit
c147b57f41
4 changed files with 548 additions and 83 deletions
|
@ -75,9 +75,6 @@ class YPTSocket extends PluginAbstract
|
||||||
'debugAllUsersSocket',
|
'debugAllUsersSocket',
|
||||||
'allow_self_signed',
|
'allow_self_signed',
|
||||||
'forceNonSecure',
|
'forceNonSecure',
|
||||||
'showTotalOnlineUsersPerVideo',
|
|
||||||
'showTotalOnlineUsersPerLive',
|
|
||||||
'showTotalOnlineUsersPerLiveLink',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -919,17 +919,6 @@ class YPTWallet extends PluginAbstract
|
||||||
public function getWalletConfigurationHTML($users_id, $wallet, $walletDataObject)
|
public function getWalletConfigurationHTML($users_id, $wallet, $walletDataObject)
|
||||||
{
|
{
|
||||||
global $global;
|
global $global;
|
||||||
if (empty($walletDataObject->CryptoWalletEnabled)) {
|
|
||||||
if (User::isAdmin()) {
|
|
||||||
YPTWallet::showAdminMessage();
|
|
||||||
echo '<div class="alert alert-warning" role="alert">
|
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
|
||||||
YPTWallet configuration will only appear if <strong>CryptoWalletEnabled</strong> is enabled in the plugin parameters.
|
|
||||||
<br>If you have an empty configuration menu, please hide this button by checking the <strong>hideConfiguration</strong> option in the YPTWallet parameters.
|
|
||||||
</div>';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
include_once $global['systemRootPath'] . 'plugin/YPTWallet/getWalletConfigurationHTML.php';
|
include_once $global['systemRootPath'] . 'plugin/YPTWallet/getWalletConfigurationHTML.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -985,4 +974,149 @@ class YPTWallet extends PluginAbstract
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function setDonationNotificationURL($users_id, $url)
|
||||||
|
{
|
||||||
|
// Sanitize the URL string for safe database storage
|
||||||
|
$url = trim($url);
|
||||||
|
|
||||||
|
// Remove any null bytes and control characters that could cause issues
|
||||||
|
$url = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $url);
|
||||||
|
|
||||||
|
// HTML encode any special characters to prevent XSS when displayed
|
||||||
|
$url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
|
// Limit length to prevent database issues
|
||||||
|
if (strlen($url) > 2048) {
|
||||||
|
_error_log("URL too long. Maximum 2048 characters allowed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = new User($users_id);
|
||||||
|
return $user->addExternalOptions('donation_notification_url', $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getDonationNotificationURL($users_id)
|
||||||
|
{
|
||||||
|
$user = new User($users_id);
|
||||||
|
return $user->getExternalOptions('donation_notification_url');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function afterDonation($from_users_id, $how_much, $videos_id, $users_id, $extraParameters)
|
||||||
|
{
|
||||||
|
$donation_notification_url = self::getDonationNotificationURL($users_id);
|
||||||
|
$webhookSecret = self::getDonationNotificationSecret($users_id); // Get user's secret
|
||||||
|
|
||||||
|
$obj = AVideoPlugin::getObjectData('YPTWallet');
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'from_users_id' => $from_users_id,
|
||||||
|
'from_users_name' => User::getNameIdentificationById($from_users_id),
|
||||||
|
'currency' => $obj->currency,
|
||||||
|
'how_much_human' => YPTWallet::formatCurrency($how_much),
|
||||||
|
'how_much' => $how_much,
|
||||||
|
'message' => $extraParameters['message'] ?? '',
|
||||||
|
'videos_id' => $videos_id,
|
||||||
|
'users_id' => $users_id,
|
||||||
|
'time' => time(),
|
||||||
|
'extraParameters' => $extraParameters
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($donation_notification_url) && isValidURL($donation_notification_url)) {
|
||||||
|
_error_log("Sending donation notification via POST to URL: {$donation_notification_url} for user ID: {$users_id}");
|
||||||
|
|
||||||
|
// Create POST data string
|
||||||
|
$postData = http_build_query($data);
|
||||||
|
|
||||||
|
// Generate signature using user's webhook secret
|
||||||
|
$signature = hash_hmac('sha256', $postData, $webhookSecret);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $donation_notification_url);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, getSelfUserAgent());
|
||||||
|
|
||||||
|
// Add signature to headers
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
||||||
|
'Content-Type: application/x-www-form-urlencoded',
|
||||||
|
'X-Webhook-Signature: sha256=' . $signature,
|
||||||
|
'X-Webhook-Timestamp: ' . $data['time']
|
||||||
|
));
|
||||||
|
|
||||||
|
// Silent execution
|
||||||
|
ob_start();
|
||||||
|
curl_exec($ch);
|
||||||
|
ob_end_clean();
|
||||||
|
curl_close($ch);
|
||||||
|
} else {
|
||||||
|
_error_log("Donation notification URL is not set or invalid for user ID: {$users_id} " . json_encode($data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a cryptographically secure random string.
|
||||||
|
* @param int $length
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function generateRandomString($length = 32)
|
||||||
|
{
|
||||||
|
if (function_exists('random_bytes')) {
|
||||||
|
return bin2hex(random_bytes($length / 2));
|
||||||
|
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||||
|
return bin2hex(openssl_random_pseudo_bytes($length / 2));
|
||||||
|
} else {
|
||||||
|
// fallback (not cryptographically secure)
|
||||||
|
return substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', $length)), 0, $length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function setDonationNotificationSecret($users_id, $secret = null)
|
||||||
|
{
|
||||||
|
// If no secret provided, generate a new one
|
||||||
|
if (empty($secret)) {
|
||||||
|
$secret = self::generateRandomString(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize the secret
|
||||||
|
$secret = trim($secret);
|
||||||
|
|
||||||
|
// Limit length for database safety
|
||||||
|
if (strlen($secret) > 255) {
|
||||||
|
_error_log("Webhook secret too long. Maximum 255 characters allowed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any dangerous characters
|
||||||
|
$secret = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $secret);
|
||||||
|
|
||||||
|
$user = new User($users_id);
|
||||||
|
return $user->addExternalOptions('donation_notification_secret', $secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getDonationNotificationSecret($users_id)
|
||||||
|
{
|
||||||
|
$user = new User($users_id);
|
||||||
|
$secret = $user->getExternalOptions('donation_notification_secret');
|
||||||
|
|
||||||
|
// If no secret exists, generate one
|
||||||
|
if (empty($secret)) {
|
||||||
|
$secret = self::generateRandomString(32);
|
||||||
|
self::setDonationNotificationSecret($users_id, $secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function regenerateDonationNotificationSecret($users_id)
|
||||||
|
{
|
||||||
|
$newSecret = self::generateRandomString(32);
|
||||||
|
return self::setDonationNotificationSecret($users_id, $newSecret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,358 @@
|
||||||
<?php
|
<?php
|
||||||
$myWallet = YPTWallet::getWallet(User::getId());
|
$myWallet = YPTWallet::getWallet(User::getId());
|
||||||
?>
|
?>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><?php echo __("Configurations"); ?></div>
|
<div class="panel-heading"><?php echo __("Configurations"); ?></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form id="form">
|
<form id="form">
|
||||||
<div class="form-group">
|
<div class="form-group" style="<?php echo $walletDataObject->CryptoWalletEnabled ? '' : 'display:none;' ?>">
|
||||||
<label for="CryptoWallet"><?php echo $walletDataObject->CryptoWalletName; ?>:</label>
|
<label for="CryptoWallet"><?php echo $walletDataObject->CryptoWalletName; ?>:</label>
|
||||||
<input type="text" class="form-control" name="CryptoWallet" value="<?php echo $myWallet->getCrypto_wallet_address(); ?>">
|
<input type="text" class="form-control" name="CryptoWallet" value="<?php echo $myWallet->getCrypto_wallet_address(); ?>">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-success"><i class="fas fa-save"></i> <?php echo __("Save"); ?></button>
|
<div class="form-group">
|
||||||
</form>
|
<label for="donation_notification_url">
|
||||||
</div>
|
<?php echo __('Donation Notification URL'); ?> (Webhook):
|
||||||
</div>
|
<button type="button" class="btn btn-xs btn-info" data-toggle="collapse" data-target="#webhookDocs" style="margin-left: 5px;">
|
||||||
<script>
|
<i class="fa fa-question-circle"></i> <?php echo __('Help'); ?>
|
||||||
$(document).ready(function () {
|
</button>
|
||||||
$("#form").submit(function (event) {
|
</label>
|
||||||
event.preventDefault();
|
<input type="url" class="form-control" name="donation_notification_url" value="<?php echo YPTWallet::getDonationNotificationUrl(User::getId()); ?>" placeholder="<?php echo __('Donation Notification URL'); ?>"
|
||||||
modal.showPleaseWait();
|
title="<?php echo __('This URL will be called when a donation is made.'); ?>">
|
||||||
$.ajax({
|
|
||||||
url: webSiteRootURL+'plugin/YPTWallet/view/saveConfiguration.php',
|
<div class="well well-sm" style="margin-top: 10px;">
|
||||||
data: $("#form").serialize(),
|
<h5><i class="fa fa-key"></i> <?php echo __('Your Webhook Secret Key:'); ?></h5>
|
||||||
type: 'post',
|
<div class="input-group">
|
||||||
success: function (response) {
|
<input type="text" class="form-control" id="webhookSecret" value="<?php echo YPTWallet::getDonationNotificationSecret(User::getId()); ?>" readonly>
|
||||||
if (!response.error) {
|
<span class="input-group-btn">
|
||||||
avideoAlert("<?php echo __("Congratulations!"); ?>", "<?php echo __("Configuration Saved"); ?>", "success");
|
<button class="btn btn-default" type="button" onclick="copyToClipboard(document.getElementById('webhookSecret'))">
|
||||||
} else {
|
<i class="fa fa-copy"></i> <?php echo __('Copy'); ?>
|
||||||
avideoAlert("<?php echo __("Sorry!"); ?>", response.msg, "error");
|
</button>
|
||||||
}
|
<button class="btn btn-warning" type="button" onclick="regenerateSecret()" title="<?php echo __('Generate new secret key'); ?>">
|
||||||
modal.hidePleaseWait();
|
<i class="fa fa-refresh"></i> <?php echo __('Regenerate'); ?>
|
||||||
console.log(response);
|
</button>
|
||||||
}
|
</span>
|
||||||
});
|
</div>
|
||||||
});
|
<small class="text-muted"><?php echo __('Use this secret to verify webhook signatures. Keep it safe and private!'); ?></small>
|
||||||
});
|
</div>
|
||||||
</script>
|
|
||||||
|
<!-- Webhook Documentation -->
|
||||||
|
<div class="collapse" id="webhookDocs" style="margin-top: 10px;">
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<i class="fa fa-info-circle"></i> <?php echo __('Webhook Documentation'); ?>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p><i class="fa fa-globe"></i> <strong><?php echo __('How it works:'); ?></strong></p>
|
||||||
|
<p><?php echo __('When someone makes a donation, AVideo will automatically send a POST request to your URL with the following parameters and security headers:'); ?></p>
|
||||||
|
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h5><i class="fa fa-shield"></i> <?php echo __('Security Headers:'); ?></h5>
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><i class="fa fa-tag"></i> <?php echo __('Header'); ?></th>
|
||||||
|
<th><i class="fa fa-info"></i> <?php echo __('Description'); ?></th>
|
||||||
|
<th><i class="fa fa-eye"></i> <?php echo __('Example'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>X-Webhook-Signature</code></td>
|
||||||
|
<td><?php echo __('HMAC SHA256 signature of the POST data for verification'); ?></td>
|
||||||
|
<td><code>sha256=abc123...</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>X-Webhook-Timestamp</code></td>
|
||||||
|
<td><?php echo __('Unix timestamp when the request was sent'); ?></td>
|
||||||
|
<td><code><?php echo time(); ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>Content-Type</code></td>
|
||||||
|
<td><?php echo __('Request content type'); ?></td>
|
||||||
|
<td><code>application/x-www-form-urlencoded</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>User-Agent</code></td>
|
||||||
|
<td><?php echo __('Identifies the request as coming from AVideo'); ?></td>
|
||||||
|
<td><code>AVideoStreamer_*</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5><i class="fa fa-list"></i> <?php echo __('Parameters explained:'); ?></h5>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-condensed table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><i class="fa fa-tag"></i> <?php echo __('Parameter'); ?></th>
|
||||||
|
<th><i class="fa fa-info"></i> <?php echo __('Description'); ?></th>
|
||||||
|
<th><i class="fa fa-eye"></i> <?php echo __('Example'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>from_users_id</code></td>
|
||||||
|
<td><?php echo __('ID of the user who made the donation'); ?></td>
|
||||||
|
<td><span class="label label-info">1</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>from_users_name</code></td>
|
||||||
|
<td><?php echo __('Name of the user who made the donation'); ?></td>
|
||||||
|
<td><span class="label label-info">John Doe</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>currency</code></td>
|
||||||
|
<td><?php echo __('Currency code configured in wallet'); ?></td>
|
||||||
|
<td><span class="label label-warning">USD</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>how_much</code></td>
|
||||||
|
<td><?php echo __('Raw amount donated (numeric value)'); ?></td>
|
||||||
|
<td><span class="label label-success">21</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>how_much_human</code></td>
|
||||||
|
<td><?php echo __('Formatted amount with currency symbol'); ?></td>
|
||||||
|
<td><span class="label label-success">$21.00</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>message</code></td>
|
||||||
|
<td><?php echo __('Message sent with the donation'); ?></td>
|
||||||
|
<td><span class="label label-default">Great content</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>videos_id</code></td>
|
||||||
|
<td><?php echo __('ID of the video (0 for live chat)'); ?></td>
|
||||||
|
<td><span class="label label-primary">0</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>users_id</code></td>
|
||||||
|
<td><?php echo __('ID of the user receiving the donation (You)'); ?></td>
|
||||||
|
<td><span class="label label-info">1</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>time</code></td>
|
||||||
|
<td><?php echo __('Unix timestamp when the donation was made'); ?></td>
|
||||||
|
<td><span class="label label-default"><?php echo time(); ?></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>extraParameters[superChat]</code></td>
|
||||||
|
<td><?php echo __('Super chat flag (1 if super chat)'); ?></td>
|
||||||
|
<td><span class="label label-warning">1</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>extraParameters[message]</code></td>
|
||||||
|
<td><?php echo __('Duplicate of message parameter for compatibility'); ?></td>
|
||||||
|
<td><span class="label label-default">Great content</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>extraParameters[live_transmitions_history_id]</code></td>
|
||||||
|
<td><?php echo __('Live transmission ID (if donation during live stream)'); ?></td>
|
||||||
|
<td><span class="label label-danger">32</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="fa fa-shield"></i>
|
||||||
|
<strong><?php echo __('Security Warning:'); ?></strong>
|
||||||
|
<?php echo __('ALWAYS verify the webhook signature before processing any data. Never trust webhook data without proper signature verification!'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h5><i class="fa fa-code"></i> <?php echo __('PHP Verification Example:'); ?></h5>
|
||||||
|
<pre><code><?php echo htmlspecialchars('<?php
|
||||||
|
// Step 1: Get headers and raw POST data
|
||||||
|
$signature = $_SERVER[\'HTTP_X_WEBHOOK_SIGNATURE\'] ?? \'\';
|
||||||
|
$timestamp = $_SERVER[\'HTTP_X_WEBHOOK_TIMESTAMP\'] ?? \'\';
|
||||||
|
$rawPostData = file_get_contents(\'php://input\');
|
||||||
|
|
||||||
|
// Step 2: Your webhook secret (copy from above)
|
||||||
|
$webhookSecret = \'YOUR_WEBHOOK_SECRET_FROM_ABOVE\';
|
||||||
|
|
||||||
|
// Step 3: Verify timestamp (optional but recommended)
|
||||||
|
$currentTime = time();
|
||||||
|
$timeDifference = $currentTime - intval($timestamp);
|
||||||
|
if ($timeDifference > 300) { // 5 minutes tolerance
|
||||||
|
http_response_code(400);
|
||||||
|
exit(\'Request expired\');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Calculate expected signature
|
||||||
|
$expectedSignature = \'sha256=\' . hash_hmac(\'sha256\', $rawPostData, $webhookSecret);
|
||||||
|
|
||||||
|
// Step 5: Verify signature using timing-safe comparison
|
||||||
|
if (!hash_equals($signature, $expectedSignature)) {
|
||||||
|
http_response_code(401);
|
||||||
|
exit(\'Invalid signature\');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Signature is valid, process the webhook data
|
||||||
|
parse_str($rawPostData, $donationData);
|
||||||
|
|
||||||
|
// Now you can safely use the donation data
|
||||||
|
$donorId = $donationData[\'from_users_id\'];
|
||||||
|
$donorName = $donationData[\'from_users_name\'];
|
||||||
|
$amount = $donationData[\'how_much\'];
|
||||||
|
$formattedAmount = $donationData[\'how_much_human\'];
|
||||||
|
$message = $donationData[\'message\'];
|
||||||
|
$videoId = $donationData[\'videos_id\'];
|
||||||
|
$receiverId = $donationData[\'users_id\'];
|
||||||
|
|
||||||
|
// Your processing logic here...
|
||||||
|
// Example: Save to database, send notifications, etc.
|
||||||
|
|
||||||
|
// Always respond with 200 OK
|
||||||
|
http_response_code(200);
|
||||||
|
echo \'Webhook processed successfully\';
|
||||||
|
?>'); ?></code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h5><i class="fa fa-code"></i> <?php echo __('Node.js/JavaScript Example:'); ?></h5>
|
||||||
|
<pre><code><?php echo htmlspecialchars('const crypto = require(\'crypto\');
|
||||||
|
const express = require(\'express\');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Middleware to get raw body
|
||||||
|
app.use(\'/webhook\', express.raw({type: \'application/x-www-form-urlencoded\'}));
|
||||||
|
|
||||||
|
app.post(\'/webhook\', (req, res) => {
|
||||||
|
const signature = req.headers[\'x-webhook-signature\'];
|
||||||
|
const timestamp = req.headers[\'x-webhook-timestamp\'];
|
||||||
|
const rawBody = req.body;
|
||||||
|
|
||||||
|
// Your webhook secret
|
||||||
|
const webhookSecret = \'YOUR_WEBHOOK_SECRET_FROM_ABOVE\';
|
||||||
|
|
||||||
|
// Verify timestamp
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
const timeDifference = currentTime - parseInt(timestamp);
|
||||||
|
if (timeDifference > 300) {
|
||||||
|
return res.status(400).send(\'Request expired\');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate expected signature
|
||||||
|
const expectedSignature = \'sha256=\' + crypto
|
||||||
|
.createHmac(\'sha256\', webhookSecret)
|
||||||
|
.update(rawBody)
|
||||||
|
.digest(\'hex\');
|
||||||
|
|
||||||
|
// Verify signature
|
||||||
|
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
|
||||||
|
return res.status(401).send(\'Invalid signature\');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse form data
|
||||||
|
const params = new URLSearchParams(rawBody.toString());
|
||||||
|
const donationData = Object.fromEntries(params);
|
||||||
|
|
||||||
|
// Process webhook data
|
||||||
|
console.log(\'Donation received:\', donationData);
|
||||||
|
|
||||||
|
res.status(200).send(\'OK\');
|
||||||
|
});'); ?></code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fa fa-key"></i>
|
||||||
|
<strong><?php echo __('Security Best Practices:'); ?></strong>
|
||||||
|
<ul class="list-unstyled" style="margin-top: 10px;">
|
||||||
|
<li><i class="fa fa-check text-success"></i> <?php echo __('Always use hash_equals() or crypto.timingSafeEqual() for signature comparison'); ?></li>
|
||||||
|
<li><i class="fa fa-check text-success"></i> <?php echo __('Verify timestamp to prevent replay attacks'); ?></li>
|
||||||
|
<li><i class="fa fa-check text-success"></i> <?php echo __('Use the raw POST body for signature calculation, not parsed data'); ?></li>
|
||||||
|
<li><i class="fa fa-check text-success"></i> <?php echo __('Keep your webhook secret private and secure'); ?></li>
|
||||||
|
<li><i class="fa fa-check text-success"></i> <?php echo __('Regenerate your webhook secret if compromised'); ?></li>
|
||||||
|
<li><i class="fa fa-check text-success"></i> <?php echo __('Always respond with HTTP 200 for valid requests'); ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
|
<strong><?php echo __('Important:'); ?></strong>
|
||||||
|
<?php echo __('Your webhook endpoint should respond with HTTP 200 status code for successful processing. The request timeout is 1 second, so ensure your endpoint responds quickly.'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fa fa-lightbulb-o"></i>
|
||||||
|
<strong><?php echo __('Use Cases:'); ?></strong>
|
||||||
|
<?php echo __('You can use this webhook to integrate with external systems, trigger notifications, update databases, send emails, integrate with Discord/Slack, or create custom donation alerts when donations are received.'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success btn-block"><i class="fas fa-save"></i> <?php echo __("Save"); ?></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function copyToClipboard(element) {
|
||||||
|
element.select();
|
||||||
|
element.setSelectionRange(0, 99999);
|
||||||
|
document.execCommand("copy");
|
||||||
|
avideoToast("<?php echo __('Copied to clipboard!'); ?>");
|
||||||
|
}
|
||||||
|
|
||||||
|
function regenerateSecret() {
|
||||||
|
avideoConfirmCallBack(
|
||||||
|
__('Are you sure you want to generate a new webhook secret? This will invalidate the current one and any existing integrations will need to be updated with the new secret.'),
|
||||||
|
function() {
|
||||||
|
// Confirm callback - user clicked confirm
|
||||||
|
modal.showPleaseWait();
|
||||||
|
$.ajax({
|
||||||
|
url: webSiteRootURL + 'plugin/YPTWallet/view/saveConfiguration.php',
|
||||||
|
data: {
|
||||||
|
regenerate_webhook_secret: 1
|
||||||
|
},
|
||||||
|
type: 'post',
|
||||||
|
success: function(response) {
|
||||||
|
if (!response.error && response.new_webhook_secret) {
|
||||||
|
$('#webhookSecret').val(response.webhook_secret);
|
||||||
|
avideoToastSuccess(__('New webhook secret generated successfully!'));
|
||||||
|
} else {
|
||||||
|
avideoAlertError(__('Error generating new secret'));
|
||||||
|
}
|
||||||
|
modal.hidePleaseWait();
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
avideoAlertError(__('Failed to regenerate webhook secret'));
|
||||||
|
modal.hidePleaseWait();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
// Cancel callback - user clicked cancel
|
||||||
|
console.log("User cancelled webhook secret regeneration");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("#form").submit(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
modal.showPleaseWait();
|
||||||
|
$.ajax({
|
||||||
|
url: webSiteRootURL + 'plugin/YPTWallet/view/saveConfiguration.php',
|
||||||
|
data: $("#form").serialize(),
|
||||||
|
type: 'post',
|
||||||
|
success: function(response) {
|
||||||
|
if (!response.error) {
|
||||||
|
avideoAlertSuccess(__("Configuration Saved"));
|
||||||
|
// Update webhook secret display if returned
|
||||||
|
if (response.webhook_secret) {
|
||||||
|
document.getElementById('webhookSecret').value = response.webhook_secret;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avideoAlertError(response.msg);
|
||||||
|
}
|
||||||
|
modal.hidePleaseWait();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,32 +1,45 @@
|
||||||
<?php
|
<?php
|
||||||
if (empty($global['systemRootPath'])) {
|
if (empty($global['systemRootPath'])) {
|
||||||
$global['systemRootPath'] = '../../../';
|
$global['systemRootPath'] = '../../../';
|
||||||
}
|
}
|
||||||
require_once $global['systemRootPath'] . 'videos/configuration.php';
|
require_once $global['systemRootPath'] . 'videos/configuration.php';
|
||||||
require_once $global['systemRootPath'] . 'objects/user.php';
|
require_once $global['systemRootPath'] . 'objects/user.php';
|
||||||
|
|
||||||
$obj = new stdClass();
|
$obj = new stdClass();
|
||||||
$obj->error = true;
|
$obj->error = true;
|
||||||
$obj->msg = "";
|
$obj->msg = "";
|
||||||
$obj->walletBalance = 0;
|
$obj->walletBalance = 0;
|
||||||
|
|
||||||
if (!User::isLogged()) {
|
if (!User::isLogged()) {
|
||||||
$obj->msg = ("Is not Loged");
|
$obj->msg = ("Is not Loged");
|
||||||
die(json_encode($obj));
|
die(json_encode($obj));
|
||||||
}
|
}
|
||||||
$plugin = AVideoPlugin::loadPluginIfEnabled("YPTWallet");
|
$plugin = AVideoPlugin::loadPluginIfEnabled("YPTWallet");
|
||||||
if(empty($plugin)){
|
if(empty($plugin)){
|
||||||
$obj->msg = ("Plugin not enabled");
|
$obj->msg = ("Plugin not enabled");
|
||||||
die(json_encode($obj));
|
die(json_encode($obj));
|
||||||
}
|
}
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$wallet = new Wallet(0);
|
$wallet = new Wallet(0);
|
||||||
$wallet->setUsers_id(User::getId());
|
$wallet->setUsers_id(User::getId());
|
||||||
$wallet->setCrypto_wallet_address($_POST['CryptoWallet']);
|
$wallet->setCrypto_wallet_address($_POST['CryptoWallet']);
|
||||||
if($wallet->save()){
|
if($wallet->save()){
|
||||||
$obj->error = false;
|
$obj->error = false;
|
||||||
}
|
}
|
||||||
$obj->walletBalance = $plugin->getBalanceFormated(User::getId());
|
|
||||||
|
if(isset($_REQUEST['donation_notification_url'])){
|
||||||
echo json_encode($obj);
|
$obj->donation_notification_url = YPTWallet::setDonationNotificationURL(User::getId(), $_REQUEST['donation_notification_url']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle webhook secret regeneration
|
||||||
|
if(isset($_REQUEST['regenerate_webhook_secret']) && $_REQUEST['regenerate_webhook_secret'] == '1'){
|
||||||
|
$obj->new_webhook_secret = YPTWallet::regenerateDonationNotificationSecret(User::getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always return current webhook secret
|
||||||
|
$obj->webhook_secret = YPTWallet::getDonationNotificationSecret(User::getId());
|
||||||
|
|
||||||
|
$obj->walletBalance = $plugin->getBalanceFormated(User::getId());
|
||||||
|
|
||||||
|
echo json_encode($obj);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue