feat: Add software licenses management page and update payment handling
- Introduced a new licenses management page with functionality to create, update, and view software licenses. - Updated payment return handling in softwaretool.php to check payment status from the database and display appropriate modals for success, pending, and failure states. - Enhanced webhook_mollie.php to log webhook calls, handle payment status updates directly in the database, and generate invoices based on payment status. - Improved CSS styles for better alignment of buttons and modal components. - Added JavaScript for modal interactions and bulk license creation functionality.
This commit is contained in:
@@ -459,132 +459,143 @@ function displaySoftwareOptions(options) {
|
||||
const isFree = price === 0;
|
||||
const isCurrent = option.is_current === true || option.is_current === 1;
|
||||
|
||||
// Create card
|
||||
// Create card with gradient background
|
||||
const card = document.createElement("div");
|
||||
card.style.cssText = `
|
||||
background: ${isCurrent ? '#f5f5f5' : 'white'};
|
||||
border: 2px solid ${isCurrent ? '#bbb' : (isFree ? '#e0e0e0' : '#e0e0e0')};
|
||||
background: ${isCurrent ? 'linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%)' : 'linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%)'};
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
transition: 0.3s;
|
||||
padding: 25px 20px;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
transform: translateY(0px);
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px;
|
||||
opacity: ${isCurrent ? '0.6' : '1'};
|
||||
box-shadow: ${isCurrent ? '0 4px 12px rgba(0,0,0,0.08)' : '0 8px 20px rgba(0,0,0,0.12)'};
|
||||
opacity: ${isCurrent ? '0.7' : '1'};
|
||||
pointer-events: ${isCurrent ? 'none' : 'auto'};
|
||||
min-height: 320px;
|
||||
`;
|
||||
|
||||
if (!isCurrent) {
|
||||
card.onmouseenter = () => {
|
||||
card.style.transform = 'translateY(-5px)';
|
||||
card.style.boxShadow = '0 8px 16px rgba(0,0,0,0.15)';
|
||||
card.style.transform = 'translateY(-8px) scale(1.02)';
|
||||
card.style.boxShadow = '0 12px 28px rgba(0,0,0,0.2)';
|
||||
card.style.borderColor = isFree ? '#038f5a' : '#FF4500';
|
||||
};
|
||||
card.onmouseleave = () => {
|
||||
card.style.transform = 'translateY(0)';
|
||||
card.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
|
||||
card.style.transform = 'translateY(0) scale(1)';
|
||||
card.style.boxShadow = '0 8px 20px rgba(0,0,0,0.12)';
|
||||
card.style.borderColor = isFree ? '#04AA6D' : '#FF6B35';
|
||||
};
|
||||
}
|
||||
|
||||
// Badge for current/free/paid
|
||||
// Badge for current/free/paid - VISIBLE
|
||||
const badge = document.createElement("div");
|
||||
badge.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: ${isCurrent ? '#6c757d' : '#04AA6D'};
|
||||
top: -10px;
|
||||
right: 20px;
|
||||
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
|
||||
color: white;
|
||||
padding: 5px 12px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
display:none;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
`;
|
||||
|
||||
if (isCurrent) {
|
||||
badge.textContent = "CURRENT VERSION";
|
||||
badge.textContent = "INSTALLED";
|
||||
} else if (isFree) {
|
||||
badge.textContent = "Included";
|
||||
badge.textContent = "FREE";
|
||||
} else {
|
||||
badge.textContent = "PREMIUM";
|
||||
}
|
||||
|
||||
if (isCurrent || isFree) {
|
||||
card.appendChild(badge);
|
||||
}
|
||||
card.appendChild(badge);
|
||||
|
||||
// Name
|
||||
// Name with icon
|
||||
const name = document.createElement("h4");
|
||||
name.style.cssText = `
|
||||
margin: 0 0 10px 0;
|
||||
margin: 0 0 12px 0;
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
name.textContent = option.name || "Software Update";
|
||||
name.innerHTML = `<i class="fa-solid fa-microchip" style="color: ${isFree ? '#04AA6D' : '#FF6B35'}; margin-right: 8px;"></i>${option.name || "Software Update"}`;
|
||||
card.appendChild(name);
|
||||
|
||||
// Version
|
||||
// Version with enhanced styling
|
||||
const version = document.createElement("div");
|
||||
version.style.cssText = `
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
`;
|
||||
version.innerHTML = `<i class="fa-solid fa-code-branch"></i> Version: <strong>${option.version || "N/A"}</strong>`;
|
||||
version.innerHTML = `<i class="fa-solid fa-code-branch" style="color: #999;"></i> <span style="font-weight: 500;">Version:</span> <strong>${option.version || "N/A"}</strong>`;
|
||||
card.appendChild(version);
|
||||
|
||||
// Description
|
||||
const desc = document.createElement("p");
|
||||
desc.style.cssText = `
|
||||
// Description with preserved newlines
|
||||
const descContainer = document.createElement("div");
|
||||
descContainer.style.cssText = `
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
margin: 0 0 20px 0;
|
||||
flex-grow: 1;
|
||||
white-space: pre-line;
|
||||
`;
|
||||
desc.textContent = option.description || "No description available";
|
||||
card.appendChild(desc);
|
||||
descContainer.textContent = option.description || "No description available";
|
||||
card.appendChild(descContainer);
|
||||
|
||||
// Price section
|
||||
const priceSection = document.createElement("div");
|
||||
priceSection.style.cssText = `
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 15px;
|
||||
border-top: 2px solid ${isFree ? '#04AA6D20' : '#FF6B3520'};
|
||||
padding-top: 20px;
|
||||
margin-top: auto;
|
||||
`;
|
||||
|
||||
const priceText = document.createElement("div");
|
||||
priceText.style.cssText = `
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#333')};
|
||||
font-size: ${isCurrent ? '18px' : '28px'};
|
||||
font-weight: ${isCurrent ? '600' : '800'};
|
||||
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')};
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
`;
|
||||
|
||||
if (isCurrent) {
|
||||
priceText.textContent = "INSTALLED";
|
||||
priceText.innerHTML = '<i class="fa-solid fa-check-circle"></i> INSTALLED';
|
||||
} else {
|
||||
priceText.textContent = isFree ? "Included" : `${option.currency || "€"} ${price.toFixed(2)}`;
|
||||
priceText.innerHTML = isFree
|
||||
? 'Free'
|
||||
: `${option.currency || "€"} ${price.toFixed(2)}`;
|
||||
}
|
||||
|
||||
priceSection.appendChild(priceText);
|
||||
|
||||
// Action button
|
||||
// Action button with gradient for paid
|
||||
const actionBtn = document.createElement("button");
|
||||
actionBtn.className = "btn";
|
||||
actionBtn.style.cssText = `
|
||||
width: 100%;
|
||||
background: ${isCurrent ? '#6c757d' : '#04AA6D'};
|
||||
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: ${isCurrent ? 'not-allowed' : 'pointer'};
|
||||
transition: background 0.3s ease;
|
||||
transition: all 0.3s ease;
|
||||
opacity: ${isCurrent ? '0.5' : '1'};
|
||||
box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'};
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
if (isCurrent) {
|
||||
@@ -593,13 +604,29 @@ function displaySoftwareOptions(options) {
|
||||
} else if (isFree) {
|
||||
actionBtn.innerHTML = '<i class="fa-solid fa-download"></i>';
|
||||
actionBtn.onclick = () => selectUpgrade(option);
|
||||
actionBtn.onmouseenter = () => actionBtn.style.background = '#038f5a';
|
||||
actionBtn.onmouseleave = () => actionBtn.style.background = '#04AA6D';
|
||||
actionBtn.onmouseenter = () => {
|
||||
actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)';
|
||||
actionBtn.style.transform = 'translateY(-2px)';
|
||||
actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
|
||||
};
|
||||
actionBtn.onmouseleave = () => {
|
||||
actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)';
|
||||
actionBtn.style.transform = 'translateY(0)';
|
||||
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
||||
};
|
||||
} else {
|
||||
actionBtn.innerHTML = '<i class="fa-solid fa-shopping-cart"></i>';
|
||||
actionBtn.onclick = () => selectUpgrade(option);
|
||||
actionBtn.onmouseenter = () => actionBtn.style.background = '#038f5a';
|
||||
actionBtn.onmouseleave = () => actionBtn.style.background = '#04AA6D';
|
||||
actionBtn.onmouseenter = () => {
|
||||
actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)';
|
||||
actionBtn.style.transform = 'translateY(-2px)';
|
||||
actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)';
|
||||
};
|
||||
actionBtn.onmouseleave = () => {
|
||||
actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)';
|
||||
actionBtn.style.transform = 'translateY(0)';
|
||||
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
||||
};
|
||||
}
|
||||
|
||||
priceSection.appendChild(actionBtn);
|
||||
@@ -980,10 +1007,19 @@ async function processPayment(paymentData, option, modal) {
|
||||
user_data: paymentData // name, email, address only
|
||||
};
|
||||
|
||||
// Debug logging
|
||||
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
||||
console.log("=== DEBUG: Payment Request ===");
|
||||
console.log("Serial Number:", deviceSerialNumber);
|
||||
console.log("Version ID:", option.version_id);
|
||||
console.log("User Data:", paymentData);
|
||||
console.log("Request payload:", paymentRequest);
|
||||
}
|
||||
|
||||
await logCommunication(`Payment initiated for version ${option.version_id}`, 'sent');
|
||||
|
||||
// Call payment API to create Mollie payment
|
||||
const response = await fetch(link + "/v2/post/payment", {
|
||||
const response = await fetch(link + "/v2/payment", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -994,13 +1030,27 @@ async function processPayment(paymentData, option, modal) {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
||||
console.error("DEBUG: Payment API error:", errorData);
|
||||
}
|
||||
throw new Error(errorData.error || "Failed to create payment");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
||||
console.log("=== DEBUG: Payment Response ===");
|
||||
console.log("Result:", result);
|
||||
console.log("Checkout URL:", result.checkout_url);
|
||||
console.log("Payment ID:", result.payment_id);
|
||||
}
|
||||
|
||||
if (result.checkout_url) {
|
||||
await logCommunication(`Redirecting to payment provider`, 'sent');
|
||||
await logCommunication(`Redirecting to Mollie payment: ${result.payment_id}`, 'sent');
|
||||
|
||||
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
||||
console.log("DEBUG: Redirecting to Mollie checkout...");
|
||||
}
|
||||
|
||||
// Close modal before redirect
|
||||
document.body.removeChild(modal);
|
||||
@@ -1012,6 +1062,9 @@ async function processPayment(paymentData, option, modal) {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
||||
console.error("DEBUG: Payment processing error:", error);
|
||||
}
|
||||
await logCommunication(`Payment error: ${error.message}`, 'error');
|
||||
progressBar("0", "Payment failed: " + error.message, "#ff6666");
|
||||
alert("Payment failed: " + error.message);
|
||||
@@ -1028,7 +1081,7 @@ async function downloadAndInstallSoftware(option, customerData = null) {
|
||||
if (paymentId) {
|
||||
try {
|
||||
// Verify serial number matches payment
|
||||
const response = await fetch(link + `/v2/get/payment?payment_id=${paymentId}`, {
|
||||
const response = await fetch(link + `/v2/payment?payment_id=${paymentId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
||||
|
||||
Reference in New Issue
Block a user