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:
“VeLiTi”
2025-12-24 14:07:28 +01:00
parent 0f968aac14
commit 543f0b3cac
21 changed files with 1400 additions and 238 deletions

View File

@@ -1,11 +1,12 @@
<?php
defined($security_key) or exit;
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
//------------------------------------------
// Payment Creation (for Software Upgrades)
//------------------------------------------
// This endpoint creates a Mollie payment and stores transaction data
// SECURITY: Price is calculated SERVER-SIDE, never trusted from frontend
//Connect to DB
$pdo = dbConnect($dbname);
@@ -13,6 +14,7 @@ $pdo = dbConnect($dbname);
//CONTENT FROM API (POST)
$post_content = json_decode($input, true);
// Validate required inputs
if (empty($post_content['serial_number']) || empty($post_content['version_id'])) {
http_response_code(400);
@@ -33,6 +35,7 @@ $stmt->execute([$serial_number]);
$equipment = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$equipment) {
http_response_code(404);
echo json_encode(['error' => 'Device not found with serial number: ' . $serial_number], JSON_UNESCAPED_UNICODE);
exit;
@@ -46,15 +49,15 @@ $hw_version = $equipment['hw_version'] ?? '';
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 2: Get version data from version_id
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
$sql = 'SELECT v.rowID as version_id, v.version, v.name, v.description, v.hw_version, p.productcode
FROM products_software_versions v
JOIN products_software p ON v.product_software_id = p.rowID
WHERE v.rowID = ? AND v.is_active = 1';
$sql = 'SELECT rowID as version_id, version, name, description, hw_version
FROM products_software_versions
WHERE rowID = ? AND status = 1';
$stmt = $pdo->prepare($sql);
$stmt->execute([$version_id]);
$version = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$version) {
http_response_code(404);
echo json_encode(['error' => 'Software version not found or inactive'], JSON_UNESCAPED_UNICODE);
exit;
@@ -93,6 +96,7 @@ if (!$has_upgrade_paths) {
$final_currency = $upgrade_path['currency'] ?? 'EUR';
} else {
// No upgrade path FROM current version
http_response_code(400);
echo json_encode(['error' => 'No valid upgrade path from current version'], JSON_UNESCAPED_UNICODE);
exit;
@@ -103,20 +107,20 @@ if (!$has_upgrade_paths) {
// STEP 4: Check license validity (lines 280-311 in software_update.php)
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($final_price > 0 && $sw_version_license) {
$sql = 'SELECT status, start_at, expires_at
$sql = 'SELECT status, starts_at, expires_at
FROM products_software_licenses
WHERE license_key = ? AND equipment_id = ?';
WHERE license_key = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sw_version_license, $equipment_id]);
$stmt->execute([$sw_version_license]);
$license = $stmt->fetch(PDO::FETCH_ASSOC);
if ($license && $license['status'] == 1) {
$now = date('Y-m-d H:i:s');
$start_at = $license['start_at'];
$starts_at = $license['starts_at'];
$expires_at = $license['expires_at'];
// Check if license is within valid date range
if ((!$start_at || $start_at <= $now) && (!$expires_at || $expires_at >= $now)) {
if ((!$starts_at || $starts_at <= $now) && (!$expires_at || $expires_at >= $now)) {
$final_price = '0.00';
}
}
@@ -126,68 +130,18 @@ if ($final_price > 0 && $sw_version_license) {
// STEP 5: Verify price > 0 (free upgrades shouldn't reach payment API)
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($final_price <= 0) {
http_response_code(400);
echo json_encode(['error' => 'This upgrade is free. No payment required.'], JSON_UNESCAPED_UNICODE);
exit;
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 6: DEBUG MODE - Simulate payment without Mollie
// STEP 6: DEBUG MODE - Log but continue to real Mollie
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
if (debug) {
// Generate fake payment ID
$fake_payment_id = 'DEBUG_' . uniqid() . '_' . time();
$checkout_url = 'https://'.$_SERVER['SERVER_NAME'].'/softwaretool.php?payment_return=1&payment_id=' . $fake_payment_id;
// Store transaction in DB
$sql = 'INSERT INTO transactions (txn_id, payment_amount, payment_status, payer_email, first_name, last_name,
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, created)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$fake_payment_id,
$final_price,
0, // 0 = pending
$user_data['email'] ?? '',
$user_data['first_name'] ?? '',
$user_data['last_name'] ?? '',
$user_data['address_street'] ?? '',
$user_data['address_city'] ?? '',
$user_data['address_state'] ?? '',
$user_data['address_zip'] ?? '',
$user_data['address_country'] ?? '',
$serial_number,
0, // payment method
date('Y-m-d H:i:s')
]);
// Store transaction item with serial_number in item_options
$item_options = json_encode([
'serial_number' => $serial_number,
'equipment_id' => $equipment_id,
'hw_version' => $hw_version
], JSON_UNESCAPED_UNICODE);
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
VALUES (?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$fake_payment_id,
$version_id,
$final_price,
1,
$item_options,
date('Y-m-d H:i:s')
]);
// Return fake checkout URL
$messages = json_encode([
'checkout_url' => $checkout_url,
'payment_id' => $fake_payment_id,
'debug_mode' => true
], JSON_UNESCAPED_UNICODE);
echo $messages;
exit;
debuglog("DEBUG MODE: Creating real Mollie payment for testing");
debuglog("DEBUG: Serial Number: $serial_number, Version ID: $version_id, Price: $final_price");
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -195,66 +149,110 @@ if (debug) {
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
try {
// Initialize Mollie
require dirname(__FILE__, 3).'/initialize.php';
require dirname(__FILE__, 4).'/initialize.php';
// Format price for Mollie (must be string with 2 decimals)
$formatted_price = number_format((float)$final_price, 2, '.', '');
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 7A: Generate transaction ID BEFORE creating Mollie payment
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generate unique transaction ID (same as placeorder.php)
$txn_id = strtoupper(uniqid('SC') . substr(md5(mt_rand()), 0, 5));
// Build webhook URL and redirect URL with actual transaction ID
$protocol = 'https';
$hostname = $_SERVER['SERVER_NAME'];
$path = '/';
$webhook_url = "{$protocol}://{$hostname}{$path}webhook_mollie.php";
$redirect_url = "{$protocol}://{$hostname}{$path}?page=softwaretool&payment_return=1&order_id={$txn_id}";
if (debug) {
debuglog("DEBUG: Transaction ID: {$txn_id}");
debuglog("DEBUG: redirectUrl being sent to Mollie: " . $redirect_url);
}
// Create payment with Mollie
$payment = $mollie->payments->create([
'amount' => [
'currency' => $final_currency ?: 'EUR',
'value' => $formatted_price
'value' => "{$formatted_price}"
],
'description' => 'Software upgrade to ' . $version['name'] . ' (v' . $version['version'] . ')',
'redirectUrl' => 'https://'.$_SERVER['SERVER_NAME'].'/softwaretool.php?payment_return=1&payment_id={id}',
'webhookUrl' => 'https://'.$_SERVER['SERVER_NAME'].'/webhook_mollie.php',
'description' => "Software upgrade Order #{$txn_id}",
'redirectUrl' => "{$redirect_url}",
'webhookUrl' => "{$webhook_url}",
'metadata' => [
'order_id' => $payment->id // Store payment ID in metadata
'order_id' => $txn_id,
'serial_number' => $serial_number,
'version_id' => $version_id,
'equipment_id' => $equipment_id
]
]);
$mollie_payment_id = $payment->id;
$checkout_url = $payment->getCheckoutUrl();
if (debug) {
debuglog("DEBUG: Mollie payment created successfully");
debuglog("DEBUG: Payment ID: $mollie_payment_id");
debuglog("DEBUG: Redirect URL sent: $redirect_url");
debuglog("DEBUG: Redirect URL from Mollie object: " . $payment->redirectUrl);
debuglog("DEBUG: Full payment object: " . json_encode($payment));
debuglog("DEBUG: Checkout URL: $checkout_url");
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 8: Store transaction in DB
// STEP 8: Store transaction in DB using txn_id (order ID)
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// Split name into first/last (simple split on first space)
$full_name = $user_data['name'] ?? '';
$name_parts = explode(' ', $full_name, 2);
$first_name = $name_parts[0] ?? '';
$last_name = $name_parts[1] ?? '';
// BUILD UP PARTNERHIERARCHY FROM USER
$partner_product = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
$sql = 'INSERT INTO transactions (txn_id, payment_amount, payment_status, payer_email, first_name, last_name,
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, created)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, accounthierarchy, created)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$mollie_payment_id,
$txn_id, // Use generated transaction ID, not Mollie payment ID
$final_price,
0, // 0 = pending
$user_data['email'] ?? '',
$user_data['first_name'] ?? '',
$user_data['last_name'] ?? '',
$user_data['address_street'] ?? '',
$user_data['address_city'] ?? '',
$user_data['address_state'] ?? '',
$user_data['address_zip'] ?? '',
$user_data['address_country'] ?? '',
$first_name,
$last_name,
$user_data['address'] ?? '',
$user_data['city'] ?? '',
'', // address_state (not collected)
$user_data['postal'] ?? '',
$user_data['country'] ?? '',
$serial_number,
0, // payment method
$partner_product,
date('Y-m-d H:i:s')
]);
// Get the database ID
$transaction_id = $pdo->lastInsertId();
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 9: Store transaction item with serial_number in item_options
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
$item_options = json_encode([
'serial_number' => $serial_number,
'equipment_id' => $equipment_id,
'hw_version' => $hw_version
'hw_version' => $hw_version,
'mollie_payment_id' => $mollie_payment_id // Store Mollie payment ID in options
], JSON_UNESCAPED_UNICODE);
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
VALUES (?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$mollie_payment_id,
$transaction_id, // Use database transaction ID (not txn_id string, not mollie_payment_id)
$version_id,
$final_price,
1,