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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,3 +12,6 @@ settings/config.php
|
|||||||
variable_scan.php
|
variable_scan.php
|
||||||
settings/soveliti/soveliti_config.php
|
settings/soveliti/soveliti_config.php
|
||||||
settings/soveliti/soveliti_settings.php
|
settings/soveliti/soveliti_settings.php
|
||||||
|
assets/database/dev_schema.sql
|
||||||
|
assets/database/migration.sql
|
||||||
|
assets/database/prod_schema.sql
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ if(isset($get_content) && $get_content!=''){
|
|||||||
elseif ($v[0] == 'equipmentid') {
|
elseif ($v[0] == 'equipmentid') {
|
||||||
//build up search
|
//build up search
|
||||||
$clause .= ' AND e.rowID = :'.$v[0];
|
$clause .= ' AND e.rowID = :'.$v[0];
|
||||||
|
|
||||||
|
//UPDATE VERSION STATUS
|
||||||
|
$sw_version_latest_update = 1;
|
||||||
}
|
}
|
||||||
elseif ($v[0] == 'servicedate') {
|
elseif ($v[0] == 'servicedate') {
|
||||||
//build up service coverage
|
//build up service coverage
|
||||||
@@ -69,6 +72,7 @@ if(isset($get_content) && $get_content!=''){
|
|||||||
elseif ($v[0] == 'h_equipmentid') {
|
elseif ($v[0] == 'h_equipmentid') {
|
||||||
//build up search
|
//build up search
|
||||||
$clause .= ' AND h.equipmentid = :'.$v[0];
|
$clause .= ' AND h.equipmentid = :'.$v[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($v[0] == 'status') {
|
elseif ($v[0] == 'status') {
|
||||||
//Update status based on status
|
//Update status based on status
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
defined($security_key) or exit;
|
defined($security_key) or exit;
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
ini_set('display_startup_errors', '1');
|
||||||
|
error_reporting(E_ALL);
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
// Products Software Licenses
|
// Products Software Licenses
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@@ -12,7 +14,7 @@ $pdo = dbConnect($dbname);
|
|||||||
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
|
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
|
||||||
|
|
||||||
//default whereclause
|
//default whereclause
|
||||||
list($whereclause,$condition) = getWhereclauselvl2("software_licenses",$permission,$partner,'get');
|
list($whereclause,$condition) = getWhereclauselvl2("products_software_licenses",$permission,$partner,'get');
|
||||||
|
|
||||||
//NEW ARRAY
|
//NEW ARRAY
|
||||||
$criterias = [];
|
$criterias = [];
|
||||||
@@ -53,11 +55,19 @@ if(isset($criterias['totals']) && $criterias['totals'] ==''){
|
|||||||
}
|
}
|
||||||
elseif (isset($criterias['list']) && $criterias['list'] =='') {
|
elseif (isset($criterias['list']) && $criterias['list'] =='') {
|
||||||
//SQL for list
|
//SQL for list
|
||||||
$sql = 'SELECT l.*, u.username, v.name as version_name FROM products_software_licenses l LEFT JOIN users u ON l.user_id = u.id LEFT JOIN products_software_versions v ON l.version_id = v.rowID '.$whereclause.' ORDER BY l.created DESC';
|
$sql = 'SELECT l.*, v.name as version_name, v.version, e.serialnumber as assigned_serial
|
||||||
|
FROM products_software_licenses l
|
||||||
|
LEFT JOIN products_software_versions v ON l.version_id = v.rowID
|
||||||
|
LEFT JOIN equipment e ON l.license_key = e.sw_version_license
|
||||||
|
'.$whereclause.' ORDER BY l.created DESC';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//SQL for paged
|
//SQL for paged
|
||||||
$sql = 'SELECT l.*, u.username, v.name as version_name FROM products_software_licenses l LEFT JOIN users u ON l.user_id = u.id LEFT JOIN products_software_versions v ON l.version_id = v.rowID '.$whereclause.' ORDER BY l.created DESC LIMIT :page,:num_licenses';
|
$sql = 'SELECT l.*, v.name as version_name, v.version, e.serialnumber as assigned_serial
|
||||||
|
FROM products_software_licenses l
|
||||||
|
LEFT JOIN products_software_versions v ON l.version_id = v.rowID
|
||||||
|
LEFT JOIN equipment e ON l.license_key = e.sw_version_license
|
||||||
|
'.$whereclause.' ORDER BY l.created DESC LIMIT :page,:num_licenses';
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|||||||
@@ -245,16 +245,16 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
|||||||
//Check if there's a valid license for this upgrade
|
//Check if there's a valid license for this upgrade
|
||||||
if ($final_price > 0 && $sw_version_license) {
|
if ($final_price > 0 && $sw_version_license) {
|
||||||
//Check if the license is valid
|
//Check if the license is valid
|
||||||
$sql = 'SELECT status, start_at, expires_at
|
$sql = 'SELECT status, starts_at, expires_at
|
||||||
FROM products_software_licenses
|
FROM products_software_licenses
|
||||||
WHERE license_key = ? AND equipment_id = ?';
|
WHERE license_key = ?';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$sw_version_license, $equipment_rowid]);
|
$stmt->execute([$sw_version_license]);
|
||||||
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($license && $license['status'] == 1) {
|
if ($license && $license['status'] == 1) {
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$start_at = $license['start_at'];
|
$start_at = $license['starts_at'];
|
||||||
$expires_at = $license['expires_at'];
|
$expires_at = $license['expires_at'];
|
||||||
|
|
||||||
//Check if license is within valid date range
|
//Check if license is within valid date range
|
||||||
|
|||||||
@@ -281,16 +281,16 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
|||||||
$license_applied = false;
|
$license_applied = false;
|
||||||
if ($final_price > 0 && $sw_version_license) {
|
if ($final_price > 0 && $sw_version_license) {
|
||||||
//Check if the license is valid
|
//Check if the license is valid
|
||||||
$sql = 'SELECT status, start_at, expires_at
|
$sql = 'SELECT status, starts_at, expires_at
|
||||||
FROM products_software_licenses
|
FROM products_software_licenses
|
||||||
WHERE license_key = ? AND equipment_id = ?';
|
WHERE license_key = ?';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$sw_version_license, $equipment_rowid]);
|
$stmt->execute([$sw_version_license]);
|
||||||
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($license && $license['status'] == 1) {
|
if ($license && $license['status'] == 1) {
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$start_at = $license['start_at'];
|
$start_at = $license['starts_at'];
|
||||||
$expires_at = $license['expires_at'];
|
$expires_at = $license['expires_at'];
|
||||||
|
|
||||||
//Check if license is within valid date range
|
//Check if license is within valid date range
|
||||||
|
|||||||
@@ -354,6 +354,42 @@ elseif(isset($post_content['dealer_closeby'])){
|
|||||||
echo json_encode(['error' => "Latitude or longitude not provided."]);
|
echo json_encode(['error' => "Latitude or longitude not provided."]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
elseif(isset($post_content['action']) && $post_content['action']=='unsubscribe'){
|
||||||
|
//++++++++++++++++++++++
|
||||||
|
//Process DEALER UNSUBSCRIBE
|
||||||
|
//++++++++++++++++++++++
|
||||||
|
|
||||||
|
// Check if email is provided
|
||||||
|
if (isset($post_content['email']) && !empty($post_content['email'])) {
|
||||||
|
$email = $post_content['email'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update dealer status to 0 (inactive) where email matches
|
||||||
|
$sql = 'UPDATE dealers SET status = 0 WHERE email = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|
||||||
|
if ($stmt->execute([$email])) {
|
||||||
|
// Check if any rows were affected
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Dealer unsubscribed successfully']);
|
||||||
|
} else {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'No dealer found with this email']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Database update failed']);
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Database error occurred']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Email not provided']);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//++++++++++++++++++++++
|
//++++++++++++++++++++++
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
defined($security_key) or exit;
|
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)
|
// Payment Creation (for Software Upgrades)
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
// This endpoint creates a Mollie payment and stores transaction data
|
// This endpoint creates a Mollie payment and stores transaction data
|
||||||
// SECURITY: Price is calculated SERVER-SIDE, never trusted from frontend
|
|
||||||
|
|
||||||
//Connect to DB
|
//Connect to DB
|
||||||
$pdo = dbConnect($dbname);
|
$pdo = dbConnect($dbname);
|
||||||
@@ -13,6 +14,7 @@ $pdo = dbConnect($dbname);
|
|||||||
//CONTENT FROM API (POST)
|
//CONTENT FROM API (POST)
|
||||||
$post_content = json_decode($input, true);
|
$post_content = json_decode($input, true);
|
||||||
|
|
||||||
|
|
||||||
// Validate required inputs
|
// Validate required inputs
|
||||||
if (empty($post_content['serial_number']) || empty($post_content['version_id'])) {
|
if (empty($post_content['serial_number']) || empty($post_content['version_id'])) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
@@ -33,6 +35,7 @@ $stmt->execute([$serial_number]);
|
|||||||
$equipment = $stmt->fetch(PDO::FETCH_ASSOC);
|
$equipment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$equipment) {
|
if (!$equipment) {
|
||||||
|
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Device not found with serial number: ' . $serial_number], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['error' => 'Device not found with serial number: ' . $serial_number], JSON_UNESCAPED_UNICODE);
|
||||||
exit;
|
exit;
|
||||||
@@ -46,15 +49,15 @@ $hw_version = $equipment['hw_version'] ?? '';
|
|||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// STEP 2: Get version data from version_id
|
// 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
|
$sql = 'SELECT rowID as version_id, version, name, description, hw_version
|
||||||
FROM products_software_versions v
|
FROM products_software_versions
|
||||||
JOIN products_software p ON v.product_software_id = p.rowID
|
WHERE rowID = ? AND status = 1';
|
||||||
WHERE v.rowID = ? AND v.is_active = 1';
|
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$version_id]);
|
$stmt->execute([$version_id]);
|
||||||
$version = $stmt->fetch(PDO::FETCH_ASSOC);
|
$version = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$version) {
|
if (!$version) {
|
||||||
|
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Software version not found or inactive'], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['error' => 'Software version not found or inactive'], JSON_UNESCAPED_UNICODE);
|
||||||
exit;
|
exit;
|
||||||
@@ -93,6 +96,7 @@ if (!$has_upgrade_paths) {
|
|||||||
$final_currency = $upgrade_path['currency'] ?? 'EUR';
|
$final_currency = $upgrade_path['currency'] ?? 'EUR';
|
||||||
} else {
|
} else {
|
||||||
// No upgrade path FROM current version
|
// No upgrade path FROM current version
|
||||||
|
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'No valid upgrade path from current version'], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['error' => 'No valid upgrade path from current version'], JSON_UNESCAPED_UNICODE);
|
||||||
exit;
|
exit;
|
||||||
@@ -103,20 +107,20 @@ if (!$has_upgrade_paths) {
|
|||||||
// STEP 4: Check license validity (lines 280-311 in software_update.php)
|
// STEP 4: Check license validity (lines 280-311 in software_update.php)
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
if ($final_price > 0 && $sw_version_license) {
|
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
|
FROM products_software_licenses
|
||||||
WHERE license_key = ? AND equipment_id = ?';
|
WHERE license_key = ?';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$sw_version_license, $equipment_id]);
|
$stmt->execute([$sw_version_license]);
|
||||||
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($license && $license['status'] == 1) {
|
if ($license && $license['status'] == 1) {
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$start_at = $license['start_at'];
|
$starts_at = $license['starts_at'];
|
||||||
$expires_at = $license['expires_at'];
|
$expires_at = $license['expires_at'];
|
||||||
|
|
||||||
// Check if license is within valid date range
|
// 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';
|
$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)
|
// STEP 5: Verify price > 0 (free upgrades shouldn't reach payment API)
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
if ($final_price <= 0) {
|
if ($final_price <= 0) {
|
||||||
|
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'This upgrade is free. No payment required.'], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['error' => 'This upgrade is free. No payment required.'], JSON_UNESCAPED_UNICODE);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// STEP 6: DEBUG MODE - Simulate payment without Mollie
|
// STEP 6: DEBUG MODE - Log but continue to real Mollie
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
if (debug) {
|
if (debug) {
|
||||||
// Generate fake payment ID
|
debuglog("DEBUG MODE: Creating real Mollie payment for testing");
|
||||||
$fake_payment_id = 'DEBUG_' . uniqid() . '_' . time();
|
debuglog("DEBUG: Serial Number: $serial_number, Version ID: $version_id, Price: $final_price");
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
@@ -195,66 +149,110 @@ if (debug) {
|
|||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
try {
|
try {
|
||||||
// Initialize Mollie
|
// Initialize Mollie
|
||||||
require dirname(__FILE__, 3).'/initialize.php';
|
require dirname(__FILE__, 4).'/initialize.php';
|
||||||
|
|
||||||
// Format price for Mollie (must be string with 2 decimals)
|
// Format price for Mollie (must be string with 2 decimals)
|
||||||
$formatted_price = number_format((float)$final_price, 2, '.', '');
|
$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
|
// Create payment with Mollie
|
||||||
$payment = $mollie->payments->create([
|
$payment = $mollie->payments->create([
|
||||||
'amount' => [
|
'amount' => [
|
||||||
'currency' => $final_currency ?: 'EUR',
|
'currency' => $final_currency ?: 'EUR',
|
||||||
'value' => $formatted_price
|
'value' => "{$formatted_price}"
|
||||||
],
|
],
|
||||||
'description' => 'Software upgrade to ' . $version['name'] . ' (v' . $version['version'] . ')',
|
'description' => "Software upgrade Order #{$txn_id}",
|
||||||
'redirectUrl' => 'https://'.$_SERVER['SERVER_NAME'].'/softwaretool.php?payment_return=1&payment_id={id}',
|
'redirectUrl' => "{$redirect_url}",
|
||||||
'webhookUrl' => 'https://'.$_SERVER['SERVER_NAME'].'/webhook_mollie.php',
|
'webhookUrl' => "{$webhook_url}",
|
||||||
'metadata' => [
|
'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;
|
$mollie_payment_id = $payment->id;
|
||||||
$checkout_url = $payment->getCheckoutUrl();
|
$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,
|
$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)
|
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, accounthierarchy, created)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$mollie_payment_id,
|
$txn_id, // Use generated transaction ID, not Mollie payment ID
|
||||||
$final_price,
|
$final_price,
|
||||||
0, // 0 = pending
|
0, // 0 = pending
|
||||||
$user_data['email'] ?? '',
|
$user_data['email'] ?? '',
|
||||||
$user_data['first_name'] ?? '',
|
$first_name,
|
||||||
$user_data['last_name'] ?? '',
|
$last_name,
|
||||||
$user_data['address_street'] ?? '',
|
$user_data['address'] ?? '',
|
||||||
$user_data['address_city'] ?? '',
|
$user_data['city'] ?? '',
|
||||||
$user_data['address_state'] ?? '',
|
'', // address_state (not collected)
|
||||||
$user_data['address_zip'] ?? '',
|
$user_data['postal'] ?? '',
|
||||||
$user_data['address_country'] ?? '',
|
$user_data['country'] ?? '',
|
||||||
$serial_number,
|
$serial_number,
|
||||||
0, // payment method
|
0, // payment method
|
||||||
|
$partner_product,
|
||||||
date('Y-m-d H:i:s')
|
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
|
// STEP 9: Store transaction item with serial_number in item_options
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
$item_options = json_encode([
|
$item_options = json_encode([
|
||||||
'serial_number' => $serial_number,
|
'serial_number' => $serial_number,
|
||||||
'equipment_id' => $equipment_id,
|
'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);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
|
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)';
|
VALUES (?, ?, ?, ?, ?, ?)';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$mollie_payment_id,
|
$transaction_id, // Use database transaction ID (not txn_id string, not mollie_payment_id)
|
||||||
$version_id,
|
$version_id,
|
||||||
$final_price,
|
$final_price,
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ $post_content = json_decode($input,true);
|
|||||||
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
|
if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';}
|
||||||
|
|
||||||
//default whereclause
|
//default whereclause
|
||||||
list($whereclause,$condition) = getWhereclauselvl2("software_licenses",$permission,$partner,'');
|
list($whereclause,$condition) = getWhereclauselvl2("products_software_licenses",$permission,$partner,'');
|
||||||
|
|
||||||
//SET PARAMETERS FOR QUERY
|
//SET PARAMETERS FOR QUERY
|
||||||
$id = $post_content['rowID'] ?? ''; //check for rowID
|
$id = $post_content['rowID'] ?? ''; //check for rowID
|
||||||
$command = ($id == '')? 'insert' : 'update'; //IF rowID = empty then INSERT
|
$command = ($id == '')? 'insert' : 'update'; //IF rowID = empty then INSERT
|
||||||
if (isset($post_content['delete'])){$command = 'delete';} //change command to delete
|
if (isset($post_content['delete'])){$command = 'delete';} //change command to delete
|
||||||
|
|
||||||
|
// Check for bulk creation
|
||||||
|
$is_bulk = isset($post_content['bulk']) && $post_content['bulk'] === true;
|
||||||
|
|
||||||
$date = date('Y-m-d H:i:s');
|
$date = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
//CREATE EMPTY STRINGS
|
//CREATE EMPTY STRINGS
|
||||||
@@ -27,12 +31,90 @@ $clause = '';
|
|||||||
$clause_insert ='';
|
$clause_insert ='';
|
||||||
$input_insert = '';
|
$input_insert = '';
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// BULK LICENSE CREATION
|
||||||
|
//------------------------------------------
|
||||||
|
if ($command == 'insert' && $is_bulk && isAllowed('products_software_licenses',$profile,$permission,'C') === 1){
|
||||||
|
|
||||||
|
$version_id = $post_content['version_id'] ?? '';
|
||||||
|
$serials = $post_content['serials'] ?? [];
|
||||||
|
$transaction_id = $post_content['transaction_id'] ?? '';
|
||||||
|
$license_type = $post_content['license_type'] ?? 0;
|
||||||
|
$status = $post_content['status'] ?? 0;
|
||||||
|
|
||||||
|
if (empty($version_id) || empty($serials) || !is_array($serials)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Invalid parameters for bulk creation']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accounthierarchy = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
// Prepare statement for bulk insert
|
||||||
|
$sql = 'INSERT INTO products_software_licenses (version_id, license_key, license_type, status, transaction_id, accounthierarchy, created, createdby)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|
||||||
|
$created_count = 0;
|
||||||
|
foreach ($serials as $serial) {
|
||||||
|
if (empty($serial)) continue;
|
||||||
|
|
||||||
|
// Generate UUID for license key
|
||||||
|
$license_key = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||||
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||||
|
mt_rand(0, 0xffff),
|
||||||
|
mt_rand(0, 0x0fff) | 0x4000,
|
||||||
|
mt_rand(0, 0x3fff) | 0x8000,
|
||||||
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt->execute([
|
||||||
|
$version_id,
|
||||||
|
$license_key,
|
||||||
|
$license_type,
|
||||||
|
$status,
|
||||||
|
$transaction_id,
|
||||||
|
$accounthierarchy,
|
||||||
|
$date,
|
||||||
|
$username
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assign license to equipment if serial number exists
|
||||||
|
$eq_sql = 'UPDATE equipment SET sw_version_license = ? WHERE serialnumber = ? AND accounthierarchy LIKE ?';
|
||||||
|
$eq_stmt = $pdo->prepare($eq_sql);
|
||||||
|
$eq_stmt->execute([$license_key, $serial, '%'.$partner->soldto.'%']);
|
||||||
|
|
||||||
|
$created_count++;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
debuglog("Error creating license for serial $serial: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'created' => $created_count]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// SINGLE LICENSE CREATION OR UPDATE
|
||||||
|
//------------------------------------------
|
||||||
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
|
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
|
||||||
if ($command == 'update'){
|
if ($command == 'update'){
|
||||||
$post_content['updated'] = $date;
|
$post_content['updated'] = $date;
|
||||||
$post_content['updatedby'] = $username;
|
$post_content['updatedby'] = $username;
|
||||||
}
|
}
|
||||||
elseif ($command == 'insert'){
|
elseif ($command == 'insert'){
|
||||||
|
// Generate UUID for license key if not provided
|
||||||
|
if (empty($post_content['license_key'])) {
|
||||||
|
$post_content['license_key'] = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||||
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||||
|
mt_rand(0, 0xffff),
|
||||||
|
mt_rand(0, 0x0fff) | 0x4000,
|
||||||
|
mt_rand(0, 0x3fff) | 0x8000,
|
||||||
|
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$post_content['created'] = $date;
|
$post_content['created'] = $date;
|
||||||
$post_content['createdby'] = $username;
|
$post_content['createdby'] = $username;
|
||||||
$post_content['accounthierarchy'] = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
|
$post_content['accounthierarchy'] = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
|
||||||
@@ -44,8 +126,8 @@ else {
|
|||||||
//CREATE NEW ARRAY AND MAP TO CLAUSE
|
//CREATE NEW ARRAY AND MAP TO CLAUSE
|
||||||
if(isset($post_content) && $post_content!=''){
|
if(isset($post_content) && $post_content!=''){
|
||||||
foreach ($post_content as $key => $var){
|
foreach ($post_content as $key => $var){
|
||||||
if ($key == 'submit' || $key == 'rowID'){
|
if ($key == 'submit' || $key == 'rowID' || $key == 'serial' || $key == 'bulk' || $key == 'serials'){
|
||||||
//do nothing
|
//do nothing - skip these fields
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$criterias[$key] = $var;
|
$criterias[$key] = $var;
|
||||||
@@ -69,13 +151,28 @@ if ($command == 'update' && isAllowed('products_software_licenses',$profile,$per
|
|||||||
$execute_input[] = $id;
|
$execute_input[] = $id;
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($execute_input);
|
$stmt->execute($execute_input);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
}
|
}
|
||||||
elseif ($command == 'insert' && isAllowed('products_software_licenses',$profile,$permission,'C') === 1){
|
elseif ($command == 'insert' && isAllowed('products_software_licenses',$profile,$permission,'C') === 1){
|
||||||
|
|
||||||
//INSERT NEW ITEM
|
//INSERT NEW ITEM
|
||||||
$sql = 'INSERT INTO products_software_licenses ('.$clause_insert.') VALUES ('.$input_insert.')';
|
$sql = 'INSERT INTO products_software_licenses ('.$clause_insert.') VALUES ('.$input_insert.')';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($execute_input);
|
$stmt->execute($execute_input);
|
||||||
|
|
||||||
|
$new_license_id = $pdo->lastInsertId();
|
||||||
|
$license_key = $post_content['license_key'];
|
||||||
|
|
||||||
|
// Assign license to equipment if serial number provided
|
||||||
|
if (!empty($post_content['serial'])) {
|
||||||
|
$serial = $post_content['serial'];
|
||||||
|
$eq_sql = 'UPDATE equipment SET sw_version_license = ? WHERE serialnumber = ? AND accounthierarchy LIKE ?';
|
||||||
|
$eq_stmt = $pdo->prepare($eq_sql);
|
||||||
|
$eq_stmt->execute([$license_key, $serial, '%'.$partner->soldto.'%']);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'license_id' => $new_license_id, 'license_key' => $license_key]);
|
||||||
}
|
}
|
||||||
elseif ($command == 'delete' && isAllowed('products_software_licenses',$profile,$permission,'D') === 1){
|
elseif ($command == 'delete' && isAllowed('products_software_licenses',$profile,$permission,'D') === 1){
|
||||||
|
|
||||||
@@ -85,6 +182,7 @@ elseif ($command == 'delete' && isAllowed('products_software_licenses',$profile,
|
|||||||
//Add deletion to changelog
|
//Add deletion to changelog
|
||||||
changelog($dbname,'products_software_licenses',$id,'Delete','Delete',$username);
|
changelog($dbname,'products_software_licenses',$id,'Delete','Delete',$username);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
//do nothing
|
//do nothing
|
||||||
|
|||||||
@@ -72,10 +72,9 @@ $hw_version = (isset($criterias['hw_version']))? $criterias['hw_version']:'';
|
|||||||
if ($command == 'update' && isAllowed('products_software_versions',$profile,$permission,'U') === 1){
|
if ($command == 'update' && isAllowed('products_software_versions',$profile,$permission,'U') === 1){
|
||||||
|
|
||||||
//REMOVE LATEST FLAG FROM OTHER WHEN SEND
|
//REMOVE LATEST FLAG FROM OTHER WHEN SEND
|
||||||
|
//Max 2 latest flags per hw_version: 1 with price (has upgrade path with price) and 1 without
|
||||||
if (isset($criterias['latest']) && $criterias['latest'] == 1){
|
if (isset($criterias['latest']) && $criterias['latest'] == 1){
|
||||||
$sql = 'UPDATE products_software_versions SET latest = 0 WHERE hw_version = ? AND rowID != ?';
|
updateSoftwareLatestFlags($pdo, $id, $hw_version);
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute([$hw_version, $id]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = 'UPDATE products_software_versions SET '.$clause.' WHERE rowID = ? ';
|
$sql = 'UPDATE products_software_versions SET '.$clause.' WHERE rowID = ? ';
|
||||||
@@ -85,17 +84,17 @@ if ($command == 'update' && isAllowed('products_software_versions',$profile,$per
|
|||||||
}
|
}
|
||||||
elseif ($command == 'insert' && isAllowed('products_software_versions',$profile,$permission,'C') === 1){
|
elseif ($command == 'insert' && isAllowed('products_software_versions',$profile,$permission,'C') === 1){
|
||||||
|
|
||||||
//REMOVE LATEST FLAG FROM OTHER IF SET
|
|
||||||
if (isset($criterias['latest']) && $criterias['latest'] == 1){
|
|
||||||
$sql = 'UPDATE products_software_versions SET latest = 0 WHERE hw_version = ?';
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute([$hw_version]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//INSERT NEW ITEM
|
//INSERT NEW ITEM
|
||||||
$sql = 'INSERT INTO products_software_versions ('.$clause_insert.') VALUES ('.$input_insert.')';
|
$sql = 'INSERT INTO products_software_versions ('.$clause_insert.') VALUES ('.$input_insert.')';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($execute_input);
|
$stmt->execute($execute_input);
|
||||||
|
$new_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
//REMOVE LATEST FLAG FROM OTHER IF SET
|
||||||
|
//Max 2 latest flags per hw_version: 1 with price (has upgrade path with price) and 1 without
|
||||||
|
if (isset($criterias['latest']) && $criterias['latest'] == 1){
|
||||||
|
updateSoftwareLatestFlags($pdo, $new_id, $hw_version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
elseif ($command == 'delete' && isAllowed('products_software_versions',$profile,$permission,'D') === 1){
|
elseif ($command == 'delete' && isAllowed('products_software_versions',$profile,$permission,'D') === 1){
|
||||||
|
|
||||||
|
|||||||
@@ -1418,7 +1418,8 @@ function getWhereclauselvl2($table_name,$permission,$partner,$method){
|
|||||||
"software" => "p.accounthierarchy",
|
"software" => "p.accounthierarchy",
|
||||||
"transactions" => "tx.accounthierarchy",
|
"transactions" => "tx.accounthierarchy",
|
||||||
"dealers" => "d.accounthierarchy",
|
"dealers" => "d.accounthierarchy",
|
||||||
"categories" => "c.accounthierarchy"
|
"categories" => "c.accounthierarchy",
|
||||||
|
"products_software_licenses" => "l.accounthierarchy"
|
||||||
];
|
];
|
||||||
|
|
||||||
$table = ($table_name != '') ? $table[$table_name] : 'accounthierarchy';
|
$table = ($table_name != '') ? $table[$table_name] : 'accounthierarchy';
|
||||||
@@ -5154,23 +5155,7 @@ function updateSoftwareVersionStatus($pdo, $serialnumber = null) {
|
|||||||
$stmt->execute($bind_params);
|
$stmt->execute($bind_params);
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
// STEP 3: Set sw_version_latest = 0 for equipment NOT matching latest version
|
// STEP 3: Set sw_version_latest = 1 for equipment matching latest version
|
||||||
//------------------------------------------
|
|
||||||
$sql = 'UPDATE equipment e
|
|
||||||
JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
|
|
||||||
JOIN products_software_versions psv ON psa.software_version_id = psv.rowID
|
|
||||||
SET e.sw_version_latest = 0
|
|
||||||
WHERE psv.latest = 1
|
|
||||||
AND psv.status = 1
|
|
||||||
AND lower(e.sw_version) <> lower(psv.version)
|
|
||||||
AND (psv.hw_version = e.hw_version OR psv.hw_version IS NULL OR psv.hw_version = "")
|
|
||||||
AND e.sw_version_latest = 1' . $sn_clause;
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute($bind_params);
|
|
||||||
|
|
||||||
//------------------------------------------
|
|
||||||
// STEP 4: Set sw_version_latest = 1 for equipment matching latest version
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
$sql = 'UPDATE equipment e
|
$sql = 'UPDATE equipment e
|
||||||
JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
|
JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
|
||||||
@@ -5179,7 +5164,7 @@ function updateSoftwareVersionStatus($pdo, $serialnumber = null) {
|
|||||||
WHERE psv.latest = 1
|
WHERE psv.latest = 1
|
||||||
AND psv.status = 1
|
AND psv.status = 1
|
||||||
AND lower(e.sw_version) = lower(psv.version)
|
AND lower(e.sw_version) = lower(psv.version)
|
||||||
AND (psv.hw_version = e.hw_version OR psv.hw_version IS NULL OR psv.hw_version = "")
|
AND (lower(psv.hw_version) = lower(e.hw_version) OR lower(psv.hw_version) IS NULL OR lower(psv.hw_version) = "")
|
||||||
AND e.sw_version_latest = 0' . $sn_clause;
|
AND e.sw_version_latest = 0' . $sn_clause;
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
@@ -5543,3 +5528,48 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') {
|
|||||||
|
|
||||||
return [$html, $customer_email, $order_id];
|
return [$html, $customer_email, $order_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update latest flags for software versions
|
||||||
|
* Max 2 latest flags per hw_version: 1 with price (has upgrade path with price) and 1 without
|
||||||
|
*
|
||||||
|
* @param PDO $pdo - Database connection
|
||||||
|
* @param int $version_id - The version ID being set as latest
|
||||||
|
* @param string $hw_version - Hardware version
|
||||||
|
*/
|
||||||
|
function updateSoftwareLatestFlags($pdo, $version_id, $hw_version) {
|
||||||
|
//Check if current version has a priced upgrade path
|
||||||
|
$sql = 'SELECT COUNT(*) as has_price
|
||||||
|
FROM products_software_upgrade_paths
|
||||||
|
WHERE to_version_id = ? AND is_active = 1 AND price > 0';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$version_id]);
|
||||||
|
$current_has_price = $stmt->fetch(PDO::FETCH_ASSOC)['has_price'] > 0;
|
||||||
|
|
||||||
|
//Remove latest flag only from versions in the same category (priced or free)
|
||||||
|
//Get all versions with same hw_version and check their pricing
|
||||||
|
$sql = 'SELECT psv.rowID,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS(
|
||||||
|
SELECT 1 FROM products_software_upgrade_paths pup
|
||||||
|
WHERE pup.to_version_id = psv.rowID
|
||||||
|
AND pup.is_active = 1
|
||||||
|
AND pup.price > 0
|
||||||
|
) THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END as has_price
|
||||||
|
FROM products_software_versions psv
|
||||||
|
WHERE psv.hw_version = ? AND psv.rowID != ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$hw_version, $version_id]);
|
||||||
|
$versions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
//Update only versions in the same price category
|
||||||
|
foreach ($versions as $version) {
|
||||||
|
if ($version['has_price'] == ($current_has_price ? 1 : 0)) {
|
||||||
|
$sql = 'UPDATE products_software_versions SET latest = 0 WHERE rowID = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$version['rowID']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -459,132 +459,143 @@ function displaySoftwareOptions(options) {
|
|||||||
const isFree = price === 0;
|
const isFree = price === 0;
|
||||||
const isCurrent = option.is_current === true || option.is_current === 1;
|
const isCurrent = option.is_current === true || option.is_current === 1;
|
||||||
|
|
||||||
// Create card
|
// Create card with gradient background
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.style.cssText = `
|
card.style.cssText = `
|
||||||
background: ${isCurrent ? '#f5f5f5' : 'white'};
|
background: ${isCurrent ? 'linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%)' : 'linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%)'};
|
||||||
border: 2px solid ${isCurrent ? '#bbb' : (isFree ? '#e0e0e0' : '#e0e0e0')};
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 15px;
|
padding: 25px 20px;
|
||||||
transition: 0.3s;
|
transition: all 0.3s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
transform: translateY(0px);
|
transform: translateY(0px);
|
||||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px;
|
box-shadow: ${isCurrent ? '0 4px 12px rgba(0,0,0,0.08)' : '0 8px 20px rgba(0,0,0,0.12)'};
|
||||||
opacity: ${isCurrent ? '0.6' : '1'};
|
opacity: ${isCurrent ? '0.7' : '1'};
|
||||||
pointer-events: ${isCurrent ? 'none' : 'auto'};
|
pointer-events: ${isCurrent ? 'none' : 'auto'};
|
||||||
|
min-height: 320px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (!isCurrent) {
|
if (!isCurrent) {
|
||||||
card.onmouseenter = () => {
|
card.onmouseenter = () => {
|
||||||
card.style.transform = 'translateY(-5px)';
|
card.style.transform = 'translateY(-8px) scale(1.02)';
|
||||||
card.style.boxShadow = '0 8px 16px rgba(0,0,0,0.15)';
|
card.style.boxShadow = '0 12px 28px rgba(0,0,0,0.2)';
|
||||||
|
card.style.borderColor = isFree ? '#038f5a' : '#FF4500';
|
||||||
};
|
};
|
||||||
card.onmouseleave = () => {
|
card.onmouseleave = () => {
|
||||||
card.style.transform = 'translateY(0)';
|
card.style.transform = 'translateY(0) scale(1)';
|
||||||
card.style.boxShadow = '0 4px 6px rgba(0,0,0,0.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");
|
const badge = document.createElement("div");
|
||||||
badge.style.cssText = `
|
badge.style.cssText = `
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: -10px;
|
||||||
right: 15px;
|
right: 20px;
|
||||||
background: ${isCurrent ? '#6c757d' : '#04AA6D'};
|
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
|
||||||
color: white;
|
color: white;
|
||||||
padding: 5px 12px;
|
padding: 8px 16px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
font-weight: bold;
|
font-weight: 700;
|
||||||
display:none;
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
badge.textContent = "CURRENT VERSION";
|
badge.textContent = "INSTALLED";
|
||||||
} else if (isFree) {
|
} 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");
|
const name = document.createElement("h4");
|
||||||
name.style.cssText = `
|
name.style.cssText = `
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 12px 0;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 20px;
|
font-size: 22px;
|
||||||
font-weight: 600;
|
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);
|
card.appendChild(name);
|
||||||
|
|
||||||
// Version
|
// Version with enhanced styling
|
||||||
const version = document.createElement("div");
|
const version = document.createElement("div");
|
||||||
version.style.cssText = `
|
version.style.cssText = `
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 15px;
|
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);
|
card.appendChild(version);
|
||||||
|
|
||||||
// Description
|
// Description with preserved newlines
|
||||||
const desc = document.createElement("p");
|
const descContainer = document.createElement("div");
|
||||||
desc.style.cssText = `
|
descContainer.style.cssText = `
|
||||||
color: #555;
|
color: #555;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
line-height: 1.7;
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
white-space: pre-line;
|
||||||
`;
|
`;
|
||||||
desc.textContent = option.description || "No description available";
|
descContainer.textContent = option.description || "No description available";
|
||||||
card.appendChild(desc);
|
card.appendChild(descContainer);
|
||||||
|
|
||||||
// Price section
|
// Price section
|
||||||
const priceSection = document.createElement("div");
|
const priceSection = document.createElement("div");
|
||||||
priceSection.style.cssText = `
|
priceSection.style.cssText = `
|
||||||
border-top: 1px solid #e0e0e0;
|
border-top: 2px solid ${isFree ? '#04AA6D20' : '#FF6B3520'};
|
||||||
padding-top: 15px;
|
padding-top: 20px;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const priceText = document.createElement("div");
|
const priceText = document.createElement("div");
|
||||||
priceText.style.cssText = `
|
priceText.style.cssText = `
|
||||||
font-size: 24px;
|
font-size: ${isCurrent ? '18px' : '28px'};
|
||||||
font-weight: bold;
|
font-weight: ${isCurrent ? '600' : '800'};
|
||||||
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#333')};
|
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')};
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
priceText.textContent = "INSTALLED";
|
priceText.innerHTML = '<i class="fa-solid fa-check-circle"></i> INSTALLED';
|
||||||
} else {
|
} else {
|
||||||
priceText.textContent = isFree ? "Included" : `${option.currency || "€"} ${price.toFixed(2)}`;
|
priceText.innerHTML = isFree
|
||||||
|
? 'Free'
|
||||||
|
: `${option.currency || "€"} ${price.toFixed(2)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
priceSection.appendChild(priceText);
|
priceSection.appendChild(priceText);
|
||||||
|
|
||||||
// Action button
|
// Action button with gradient for paid
|
||||||
const actionBtn = document.createElement("button");
|
const actionBtn = document.createElement("button");
|
||||||
actionBtn.className = "btn";
|
actionBtn.className = "btn";
|
||||||
actionBtn.style.cssText = `
|
actionBtn.style.cssText = `
|
||||||
width: 100%;
|
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;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: ${isCurrent ? 'not-allowed' : 'pointer'};
|
cursor: ${isCurrent ? 'not-allowed' : 'pointer'};
|
||||||
transition: background 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
opacity: ${isCurrent ? '0.5' : '1'};
|
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) {
|
if (isCurrent) {
|
||||||
@@ -593,13 +604,29 @@ function displaySoftwareOptions(options) {
|
|||||||
} else if (isFree) {
|
} else if (isFree) {
|
||||||
actionBtn.innerHTML = '<i class="fa-solid fa-download"></i>';
|
actionBtn.innerHTML = '<i class="fa-solid fa-download"></i>';
|
||||||
actionBtn.onclick = () => selectUpgrade(option);
|
actionBtn.onclick = () => selectUpgrade(option);
|
||||||
actionBtn.onmouseenter = () => actionBtn.style.background = '#038f5a';
|
actionBtn.onmouseenter = () => {
|
||||||
actionBtn.onmouseleave = () => actionBtn.style.background = '#04AA6D';
|
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 {
|
} else {
|
||||||
actionBtn.innerHTML = '<i class="fa-solid fa-shopping-cart"></i>';
|
actionBtn.innerHTML = '<i class="fa-solid fa-shopping-cart"></i>';
|
||||||
actionBtn.onclick = () => selectUpgrade(option);
|
actionBtn.onclick = () => selectUpgrade(option);
|
||||||
actionBtn.onmouseenter = () => actionBtn.style.background = '#038f5a';
|
actionBtn.onmouseenter = () => {
|
||||||
actionBtn.onmouseleave = () => actionBtn.style.background = '#04AA6D';
|
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);
|
priceSection.appendChild(actionBtn);
|
||||||
@@ -980,10 +1007,19 @@ async function processPayment(paymentData, option, modal) {
|
|||||||
user_data: paymentData // name, email, address only
|
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');
|
await logCommunication(`Payment initiated for version ${option.version_id}`, 'sent');
|
||||||
|
|
||||||
// Call payment API to create Mollie payment
|
// Call payment API to create Mollie payment
|
||||||
const response = await fetch(link + "/v2/post/payment", {
|
const response = await fetch(link + "/v2/payment", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -994,13 +1030,27 @@ async function processPayment(paymentData, option, modal) {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json();
|
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");
|
throw new Error(errorData.error || "Failed to create payment");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
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) {
|
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
|
// Close modal before redirect
|
||||||
document.body.removeChild(modal);
|
document.body.removeChild(modal);
|
||||||
@@ -1012,6 +1062,9 @@ async function processPayment(paymentData, option, modal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (typeof DEBUG !== 'undefined' && DEBUG) {
|
||||||
|
console.error("DEBUG: Payment processing error:", error);
|
||||||
|
}
|
||||||
await logCommunication(`Payment error: ${error.message}`, 'error');
|
await logCommunication(`Payment error: ${error.message}`, 'error');
|
||||||
progressBar("0", "Payment failed: " + error.message, "#ff6666");
|
progressBar("0", "Payment failed: " + error.message, "#ff6666");
|
||||||
alert("Payment failed: " + error.message);
|
alert("Payment failed: " + error.message);
|
||||||
@@ -1028,7 +1081,7 @@ async function downloadAndInstallSoftware(option, customerData = null) {
|
|||||||
if (paymentId) {
|
if (paymentId) {
|
||||||
try {
|
try {
|
||||||
// Verify serial number matches payment
|
// 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",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
"Authorization": "Bearer " + document.getElementById("servicetoken").textContent
|
||||||
|
|||||||
@@ -2920,3 +2920,50 @@ main .products .product .price, main .products .products-wrapper .product .price
|
|||||||
height: 25px;
|
height: 25px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Button alignment styles */
|
||||||
|
.form-actions,
|
||||||
|
.modal-actions,
|
||||||
|
.dialog-actions,
|
||||||
|
.button-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
main .form .button-container,
|
||||||
|
main .form .form-actions,
|
||||||
|
main .content-block .button-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog .content .footer,
|
||||||
|
.modal .modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
@@ -2922,3 +2922,50 @@ main .products .product .price, main .products .products-wrapper .product .price
|
|||||||
height: 25px;
|
height: 25px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Button alignment styles */
|
||||||
|
.form-actions,
|
||||||
|
.modal-actions,
|
||||||
|
.dialog-actions,
|
||||||
|
.button-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
main .form .button-container,
|
||||||
|
main .form .form-actions,
|
||||||
|
main .content-block .button-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog .content .footer,
|
||||||
|
.modal .modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
@@ -127,6 +127,7 @@ $view = '
|
|||||||
<div class="content-title responsive-flex-wrap responsive-pad-bot-3">
|
<div class="content-title responsive-flex-wrap responsive-pad-bot-3">
|
||||||
<h2 class="responsive-width-100">'.$view_asset_h2.' - '.$responses->equipmentID.'</h2>
|
<h2 class="responsive-width-100">'.$view_asset_h2.' - '.$responses->equipmentID.'</h2>
|
||||||
<a href="index.php?page='.$_SESSION['origin'].'&p='.$_SESSION['p'].$_SESSION['status'].$_SESSION['sort'].$_SESSION['search'].$_SESSION['firmware'].$_SESSION['servicedate'].$_SESSION['warrantydate'].$_SESSION['partnerid'].'" class="btn alt mar-right-2">←</a>
|
<a href="index.php?page='.$_SESSION['origin'].'&p='.$_SESSION['p'].$_SESSION['status'].$_SESSION['sort'].$_SESSION['search'].$_SESSION['firmware'].$_SESSION['servicedate'].$_SESSION['warrantydate'].$_SESSION['partnerid'].'" class="btn alt mar-right-2">←</a>
|
||||||
|
<a href="index.php?page=history&equipmentID='.$responses->equipmentID.'" class="btn"><i class="fa-solid fa-clock-rotate-left"></i></a>
|
||||||
';
|
';
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
@@ -395,7 +396,7 @@ $view .= '<div class="content-block">
|
|||||||
<div class="block-header">
|
<div class="block-header">
|
||||||
<i class="fa-solid fa-bars fa-sm"></i>'.$view_asset_actions.'
|
<i class="fa-solid fa-bars fa-sm"></i>'.$view_asset_actions.'
|
||||||
</div>
|
</div>
|
||||||
<a href="index.php?page=history&equipmentID='.$responses->equipmentID.'" class="btn">'.$button_history.'</a>
|
|
||||||
'.$view_communication.'
|
'.$view_communication.'
|
||||||
'.$view_users.'
|
'.$view_users.'
|
||||||
';
|
';
|
||||||
|
|||||||
606
licenses.php
Normal file
606
licenses.php
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
<?php
|
||||||
|
defined(page_security_key) or exit;
|
||||||
|
|
||||||
|
if (debug && debug_id == $_SESSION['id']){
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
ini_set('display_startup_errors', '1');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once './assets/functions.php';
|
||||||
|
include_once './settings/settings_redirector.php';
|
||||||
|
|
||||||
|
//SET ORIGIN FOR NAVIGATION
|
||||||
|
$prev_page = $_SESSION['prev_origin'] ?? '';
|
||||||
|
$page = $_SESSION['origin'] = 'licenses';
|
||||||
|
|
||||||
|
//Check if allowed
|
||||||
|
if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){
|
||||||
|
header('location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
//PAGE Security
|
||||||
|
$page_manage = 'licenses';
|
||||||
|
$update_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'U');
|
||||||
|
$delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D');
|
||||||
|
$create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C');
|
||||||
|
|
||||||
|
// Handle license status update
|
||||||
|
if ($update_allowed === 1 && isset($_POST['submit'])) {
|
||||||
|
$data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
|
||||||
|
$responses = ioServer('/v2/products_software_licenses', $data);
|
||||||
|
if ($responses !== 'NOK'){
|
||||||
|
header('Location: index.php?page=licenses&success_msg=2');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//GET PARAMETERS
|
||||||
|
$pagination_page = isset($_GET['p']) ? $_GET['p'] : 1;
|
||||||
|
$status = isset($_GET['status']) ? '&status='.$_GET['status'] : '';
|
||||||
|
$search = isset($_GET['search']) ? '&search='.$_GET['search'] : '';
|
||||||
|
|
||||||
|
// Determine the URL
|
||||||
|
$url = 'index.php?page=licenses'.$status.$search;
|
||||||
|
//GET Details from URL
|
||||||
|
$GET_VALUES = urlGETdetails($_GET) ?? '';
|
||||||
|
//CALL TO API
|
||||||
|
$api_url = '/v2/products_software_licenses/'.$GET_VALUES;
|
||||||
|
$responses = ioServer($api_url,'');
|
||||||
|
|
||||||
|
//Decode Payload
|
||||||
|
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
|
||||||
|
|
||||||
|
//Return QueryTotal from API
|
||||||
|
$api_url = '/v2/products_software_licenses/'.$GET_VALUES.'&totals=';
|
||||||
|
$query_total = ioServer($api_url,'');
|
||||||
|
//Decode Payload
|
||||||
|
if (!empty($query_total)){$query_total = json_decode($query_total,);}else{$query_total = null;}
|
||||||
|
|
||||||
|
// Handle success messages
|
||||||
|
if (isset($_GET['success_msg'])) {
|
||||||
|
if ($_GET['success_msg'] == 1) {
|
||||||
|
$success_msg = 'Licenses created successfully!';
|
||||||
|
}
|
||||||
|
if ($_GET['success_msg'] == 2) {
|
||||||
|
$success_msg = 'License updated successfully!';
|
||||||
|
}
|
||||||
|
if ($_GET['success_msg'] == 3) {
|
||||||
|
$success_msg = 'License deleted successfully!';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get software versions for dropdown
|
||||||
|
$api_url = '/v2/products_software_versions/list=&status=1';
|
||||||
|
$software_versions = ioServer($api_url,'');
|
||||||
|
if (!empty($software_versions)){$software_versions = json_decode($software_versions);}else{$software_versions = null;}
|
||||||
|
|
||||||
|
template_header('Licenses', 'licenses','view');
|
||||||
|
$view = '
|
||||||
|
<div class="content-title">
|
||||||
|
<div class="title">
|
||||||
|
<i class="fa-solid fa-key"></i>
|
||||||
|
<div class="txt">
|
||||||
|
<h2>Software Licenses ('.$query_total.')</h2>
|
||||||
|
<p>Manage and create software licenses for devices</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="title-actions">';
|
||||||
|
|
||||||
|
if ($create_allowed === 1){
|
||||||
|
$view .= '<button onclick="openBulkLicenseModal()" class="btn">+</button>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$view .= '<button id="filter-toggle" class="btn alt" onclick="toggleFilters()">
|
||||||
|
<i class="fa-solid fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
if (isset($success_msg)){
|
||||||
|
$view .= ' <div class="msg success">
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
<p>'.$success_msg.'</p>
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$view .= '
|
||||||
|
<div id="filter-panel" class="filter-panel" style="display: none;">
|
||||||
|
<div class="filter-content">
|
||||||
|
<form action="" method="get">
|
||||||
|
<input type="hidden" name="page" value="licenses">
|
||||||
|
<div class="filter-row">
|
||||||
|
<div class="filter-group">
|
||||||
|
<select name="status">
|
||||||
|
<option value="" disabled selected>Status</option>
|
||||||
|
<option value="0"'.($status==0?' selected':'').'>Inactive</option>
|
||||||
|
<option value="1"'.($status==1?' selected':'').'>Assigned</option>
|
||||||
|
<option value="2"'.($status==2?' selected':'').'>Expired</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group search-group">
|
||||||
|
<input type="text" name="search" placeholder="Search license key..." value="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-actions">
|
||||||
|
<button type="submit" class="btn"><i class="fas fa-level-down-alt fa-rotate-90"></i></button>
|
||||||
|
<a class="btn alt" href="index.php?page=licenses">Clear</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
$view .= '
|
||||||
|
<div class="content-block">
|
||||||
|
<div class="table">
|
||||||
|
<table class="sortable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>License Key</th>
|
||||||
|
<th>Software Version</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Transaction ID</th>
|
||||||
|
<th>Starts At</th>
|
||||||
|
<th>Expires</th>
|
||||||
|
<th>Assigned To (Serial)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
';
|
||||||
|
|
||||||
|
if (empty($responses)){
|
||||||
|
|
||||||
|
$view .= '
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" style="text-align:center;">No licenses found</td>
|
||||||
|
</tr>';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach ($responses as $response){
|
||||||
|
// Check if license is expired based on timestamp
|
||||||
|
$actual_status = $response->status;
|
||||||
|
if (!empty($response->expires_at)) {
|
||||||
|
$expiry_time = strtotime($response->expires_at);
|
||||||
|
$current_time = time();
|
||||||
|
if ($current_time > $expiry_time) {
|
||||||
|
// License is expired - override status
|
||||||
|
$actual_status = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status display based on actual status
|
||||||
|
$status_text = '';
|
||||||
|
if ($actual_status == 0) {
|
||||||
|
$status_text = 'Inactive';
|
||||||
|
} elseif ($actual_status == 1) {
|
||||||
|
$status_text = 'Assigned';
|
||||||
|
} elseif ($actual_status == 2) {
|
||||||
|
$status_text = 'Expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format dates
|
||||||
|
$starts_display = '-';
|
||||||
|
if (!empty($response->starts_at)) {
|
||||||
|
$starts_display = date('Y-m-d', strtotime($response->starts_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
$expires_display = '-';
|
||||||
|
if (!empty($response->expires_at)) {
|
||||||
|
$expires_display = date('Y-m-d', strtotime($response->expires_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
$view .= '
|
||||||
|
<tr style="cursor: pointer;" onclick="openLicenseModal('.htmlspecialchars(json_encode($response), ENT_QUOTES).')">
|
||||||
|
<td>'.$response->license_key.'</td>
|
||||||
|
<td>'.$response->version_name.'</td>
|
||||||
|
<td><span class="status id'.$actual_status.'">'.$status_text.'</span></td>
|
||||||
|
<td>'.($response->transaction_id ?? '-').'</td>
|
||||||
|
<td>'.$starts_display.'</td>
|
||||||
|
<td>'.$expires_display.'</td>
|
||||||
|
<td>'.($response->assigned_serial ?? '-').'</td>
|
||||||
|
</tr>
|
||||||
|
';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$view .= '
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
$view.='<div class="pagination">';
|
||||||
|
if ($pagination_page > 1) {
|
||||||
|
$page = $pagination_page-1;
|
||||||
|
$view .= '<a href="'.$url.'&p=1">First</a>';
|
||||||
|
$view .= '<a href="'.$url.'&p='.$page.'">Prev</a>';
|
||||||
|
}
|
||||||
|
$totals = ceil($query_total / 50) == 0 ? 1 : ceil($query_total / 50);
|
||||||
|
$view .= '<span> Page '.$pagination_page.' of '.$totals.'</span>';
|
||||||
|
if ($pagination_page * 50 < $query_total){
|
||||||
|
$page = $pagination_page+1;
|
||||||
|
$view .= '<a href="'.$url.'&p='.$page.'">Next</a>';
|
||||||
|
$view .= '<a href="'.$url.'&p='.$totals.'">Last</a>';
|
||||||
|
|
||||||
|
}
|
||||||
|
$view .= '</div>';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Bulk License Modal
|
||||||
|
$view .= '
|
||||||
|
<div id="bulkLicenseModal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><i class="fa-solid fa-key"></i> Create Bulk Licenses</h3>
|
||||||
|
<button onclick="closeBulkLicenseModal()" class="close-btn">
|
||||||
|
<i class="fa-solid fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="bulkLicenseForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bulk_version_id">Software Version *</label>
|
||||||
|
<select id="bulk_version_id" name="version_id" required>
|
||||||
|
<option value="">Select software version...</option>';
|
||||||
|
|
||||||
|
if (!empty($software_versions)) {
|
||||||
|
foreach ($software_versions as $version) {
|
||||||
|
$view .= '<option value="'.$version->rowID.'">'.$version->name.' (v'.$version->version.')</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$view .= '
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bulk_serials">Serial Numbers *</label>
|
||||||
|
<textarea id="bulk_serials" name="serials" rows="8" placeholder="Paste serial numbers, one per line: SN001 SN002 SN003" required></textarea>
|
||||||
|
<small style="color: #666; display: block; margin-top: 5px;">Enter one serial number per line</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bulk_transaction_id">Transaction ID *</label>
|
||||||
|
<input type="text" id="bulk_transaction_id" name="transaction_id" placeholder="e.g., PO-12345" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" onclick="closeBulkLicenseModal()" class="btn alt">Cancel</button>
|
||||||
|
<button type="submit" class="btn">Create Licenses</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
// License Details Modal
|
||||||
|
$view .= '
|
||||||
|
<div id="licenseDetailModal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content" style="max-width: 700px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><i class="fa-solid fa-key"></i> License Details</h3>
|
||||||
|
<button onclick="closeLicenseDetailModal()" class="close-btn">
|
||||||
|
<i class="fa-solid fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="" method="post" id="licenseDetailForm">
|
||||||
|
<div class="license-details-grid" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 25px;">
|
||||||
|
<div class="detail-group">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">License Key</label>
|
||||||
|
<div id="detail_license_key" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-family: monospace; font-size: 14px; word-break: break-all;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">Software Version</label>
|
||||||
|
<div id="detail_version_name" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-size: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">Status</label>
|
||||||
|
<div id="detail_status" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-size: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">Transaction ID</label>
|
||||||
|
<div id="detail_transaction_id" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-size: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">Starts At</label>
|
||||||
|
<div id="detail_starts_at" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-size: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">Expires</label>
|
||||||
|
<div id="detail_expires_at" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-size: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group" style="grid-column: 1 / -1;">
|
||||||
|
<label style="display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: 500;">Assigned To (Serial)</label>
|
||||||
|
<div id="detail_assigned_serial" style="padding: 10px 12px; background: #f8f9fa; border-radius: 4px; font-size: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" id="detail_rowID" name="rowID" value="">
|
||||||
|
<input type="hidden" name="status" value="0">
|
||||||
|
|
||||||
|
<div class="form-actions" style="border-top: 1px solid #e0e0e0; padding-top: 20px;">';
|
||||||
|
|
||||||
|
if ($update_allowed === 1) {
|
||||||
|
$view .= '
|
||||||
|
<button type="submit" name="submit" id="setInactiveBtn" class="btn" style="background: #dc3545;" onclick="return confirm(\'Are you sure you want to set this license as inactive?\')">
|
||||||
|
<i class="fa-solid fa-ban"></i> Set Inactive
|
||||||
|
</button>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$view .= '
|
||||||
|
<button type="button" onclick="closeLicenseDetailModal()" class="btn alt">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
//OUTPUT
|
||||||
|
echo $view;
|
||||||
|
|
||||||
|
// Add JavaScript for modals and API calls
|
||||||
|
echo '
|
||||||
|
<script>
|
||||||
|
// Store current license data
|
||||||
|
let currentLicenseData = null;
|
||||||
|
|
||||||
|
// Modal functions
|
||||||
|
function openBulkLicenseModal() {
|
||||||
|
document.getElementById("bulkLicenseModal").style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBulkLicenseModal() {
|
||||||
|
document.getElementById("bulkLicenseModal").style.display = "none";
|
||||||
|
document.getElementById("bulkLicenseForm").reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// License detail modal functions
|
||||||
|
function openLicenseModal(licenseData) {
|
||||||
|
currentLicenseData = licenseData;
|
||||||
|
|
||||||
|
// Calculate actual status (check expiry)
|
||||||
|
let actualStatus = licenseData.status;
|
||||||
|
if (licenseData.expires_at) {
|
||||||
|
const expiryTime = new Date(licenseData.expires_at).getTime();
|
||||||
|
const currentTime = new Date().getTime();
|
||||||
|
if (currentTime > expiryTime) {
|
||||||
|
actualStatus = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status text and formatting
|
||||||
|
let statusText = "";
|
||||||
|
let statusClass = "";
|
||||||
|
if (actualStatus == 0) {
|
||||||
|
statusText = "Inactive";
|
||||||
|
statusClass = "id0";
|
||||||
|
} else if (actualStatus == 1) {
|
||||||
|
statusText = "Assigned";
|
||||||
|
statusClass = "id1";
|
||||||
|
} else if (actualStatus == 2) {
|
||||||
|
statusText = "Expired";
|
||||||
|
statusClass = "id2";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format dates
|
||||||
|
const startsDisplay = licenseData.starts_at ? new Date(licenseData.starts_at).toLocaleDateString() : "-";
|
||||||
|
const expiresDisplay = licenseData.expires_at ? new Date(licenseData.expires_at).toLocaleDateString() : "-";
|
||||||
|
|
||||||
|
// Populate modal fields
|
||||||
|
document.getElementById("detail_license_key").textContent = licenseData.license_key || "-";
|
||||||
|
document.getElementById("detail_version_name").textContent = licenseData.version_name || "-";
|
||||||
|
document.getElementById("detail_status").innerHTML = \'<span class="status \' + statusClass + \'">\' + statusText + \'</span>\';
|
||||||
|
document.getElementById("detail_transaction_id").textContent = licenseData.transaction_id || "-";
|
||||||
|
document.getElementById("detail_starts_at").textContent = startsDisplay;
|
||||||
|
document.getElementById("detail_expires_at").textContent = expiresDisplay;
|
||||||
|
document.getElementById("detail_assigned_serial").textContent = licenseData.assigned_serial || "-";
|
||||||
|
|
||||||
|
// Set hidden form field
|
||||||
|
document.getElementById("detail_rowID").value = licenseData.rowID || "";
|
||||||
|
|
||||||
|
// Show/hide inactive button based on current status
|
||||||
|
const inactiveBtn = document.getElementById("setInactiveBtn");
|
||||||
|
if (inactiveBtn) {
|
||||||
|
if (actualStatus == 0) {
|
||||||
|
inactiveBtn.style.display = "none";
|
||||||
|
} else {
|
||||||
|
inactiveBtn.style.display = "inline-block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("licenseDetailModal").style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeLicenseDetailModal() {
|
||||||
|
document.getElementById("licenseDetailModal").style.display = "none";
|
||||||
|
currentLicenseData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal on background click
|
||||||
|
window.onclick = function(event) {
|
||||||
|
const bulkModal = document.getElementById("bulkLicenseModal");
|
||||||
|
const detailModal = document.getElementById("licenseDetailModal");
|
||||||
|
|
||||||
|
if (event.target == bulkModal) {
|
||||||
|
closeBulkLicenseModal();
|
||||||
|
}
|
||||||
|
if (event.target == detailModal) {
|
||||||
|
closeLicenseDetailModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Bulk license form submission
|
||||||
|
document.getElementById("bulkLicenseForm").addEventListener("submit", async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const serials = document.getElementById("bulk_serials").value
|
||||||
|
.split("\\n")
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(s => s.length > 0);
|
||||||
|
|
||||||
|
if (serials.length === 0) {
|
||||||
|
alert("Please enter at least one serial number.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
bulk: true,
|
||||||
|
version_id: document.getElementById("bulk_version_id").value,
|
||||||
|
serials: serials,
|
||||||
|
transaction_id: document.getElementById("bulk_transaction_id").value,
|
||||||
|
license_type: 0,
|
||||||
|
status: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("api.php/v2/products_software_licenses", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": "Bearer " + sessionStorage.getItem("token")
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.href = "index.php?page=licenses&success_msg=1";
|
||||||
|
} else {
|
||||||
|
alert("Error creating licenses. Please try again.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
alert("Error creating licenses. Please try again.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter toggle function
|
||||||
|
function toggleFilters() {
|
||||||
|
const panel = document.getElementById("filter-panel");
|
||||||
|
if (panel.style.display === "none") {
|
||||||
|
panel.style.display = "block";
|
||||||
|
} else {
|
||||||
|
panel.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Modal styles */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fff;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 20px 25px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"],
|
||||||
|
.form-group select,
|
||||||
|
.form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea {
|
||||||
|
resize: vertical;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus,
|
||||||
|
.form-group select:focus,
|
||||||
|
.form-group textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #04AA6D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
';
|
||||||
|
|
||||||
|
template_footer();
|
||||||
|
?>
|
||||||
@@ -47,6 +47,12 @@ $main_menu = [
|
|||||||
"icon" => "fas fa-tachometer-alt",
|
"icon" => "fas fa-tachometer-alt",
|
||||||
"name" => "menu_sales_orders"
|
"name" => "menu_sales_orders"
|
||||||
],
|
],
|
||||||
|
"licenses" => [
|
||||||
|
"url" => "licenses",
|
||||||
|
"selected" => "licenses",
|
||||||
|
"icon" => "fas fa-tachometer-alt",
|
||||||
|
"name" => "menu_sales_licenses"
|
||||||
|
],
|
||||||
"identity" => [
|
"identity" => [
|
||||||
"url" => "identity",
|
"url" => "identity",
|
||||||
"selected" => "identity",
|
"selected" => "identity",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ define('superuser_profile','admin,dashboard,profile,application,assets,firmwaret
|
|||||||
/*Admin*/
|
/*Admin*/
|
||||||
define('admin_profile','account,accounts,admin,dashboard,profile,application,assets,buildtool,buildtool,cartest,cartest_manage,cartests,changelog,communication,communication_send,communications,firmwaretool,histories,history,history_manage,marketing,partner,partners,sales,servicereport,servicereports,contract,contract_manage,contracts,equipment,equipment_data,equipment_healthindex,equipment_manage,equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,products_software,products_versions,report_build,report_contracts_billing,report_healthindex,reporting,rma,rma_history,rma_history_manage,rma_manage,rmas,user,user_manage,users');
|
define('admin_profile','account,accounts,admin,dashboard,profile,application,assets,buildtool,buildtool,cartest,cartest_manage,cartests,changelog,communication,communication_send,communications,firmwaretool,histories,history,history_manage,marketing,partner,partners,sales,servicereport,servicereports,contract,contract_manage,contracts,equipment,equipment_data,equipment_healthindex,equipment_manage,equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,products_software,products_versions,report_build,report_contracts_billing,report_healthindex,reporting,rma,rma_history,rma_history_manage,rma_manage,rmas,user,user_manage,users');
|
||||||
/*AdminPlus*/
|
/*AdminPlus*/
|
||||||
define('adminplus_profile','account,account_manage,accounts,admin,config,dashboard,profile,settings,api,application,appointment,assets,billing,buildtool,buildtool,cartest,cartest_manage,cartests,catalog,categories,category,changelog,checkout,com_log,communication,communication_send,communications,cronjob,debug,dev,discount,discounts,firmwaretool,generate_download_token,histories,history,history_manage,identity,identity_dealers,invoice,language,logfile,mailer,maintenance,marketing,media,media_manage,media_scanner,media_upload,order,orders,partner,partners,placeorder,pricelists,pricelists_items,pricelists_manage,profiles,register,render_service_report,reset,sales,security,servicereport,servicereports,shipping,shipping_manage,shopping_cart,software_available,software_download,software_update,tax,taxes,test,transactions,transactions_items,translation_manage,translations,translations_details,unscribe,upgrades,uploader,vin,contract,contract_manage,contracts,dealer,dealer_manage,dealers,dealers_media,equipment,equipment_data,equipment_healthindex,equipment_manage,equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,products_attributes,products_attributes_items,products_attributes_manage,products_categories,products_configurations,products_media,products_software,products_software_assignment,products_software_assignments,products_software_assignments,products_software_licenses,products_software_upgrade_paths,products_software_upgrade_paths_manage,products_software_version,products_software_version_access_rules_manage,products_software_version_manage,products_software_versions,products_versions,report_build,report_contracts_billing,report_healthindex,report_usage,reporting,rma,rma_history,rma_history_manage,rma_manage,rmas,user,user_credentials,user_manage,users');
|
define('adminplus_profile','account,account_manage,accounts,admin,config,dashboard,profile,settings,api,application,appointment,assets,billing,buildtool,buildtool,cartest,cartest_manage,cartests,catalog,categories,category,changelog,checkout,com_log,communication,communication_send,communications,cronjob,debug,dev,discount,discounts,firmwaretool,generate_download_token,histories,history,history_manage,identity,identity_dealers,language,licenses,logfile,mailer,maintenance,marketing,media,media_manage,media_scanner,media_upload,order,orders,partner,partners,payment,placeorder,pricelists,pricelists_items,pricelists_manage,profiles,register,render_service_report,reset,sales,security,servicereport,servicereports,shipping,shipping_manage,shopping_cart,software_available,software_download,software_update,softwaretool,tax,taxes,test,transactions,transactions_items,translation_manage,translations,translations_details,unscribe,upgrades,uploader,vin,contract,contract_manage,contracts,dealer,dealer_manage,dealers,dealers_media,equipment,equipment_data,equipment_healthindex,equipment_manage,equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,products_attributes,products_attributes_items,products_attributes_manage,products_categories,products_configurations,products_media,products_software,products_software_assignment,products_software_assignments,products_software_assignments,products_software_licenses,products_software_upgrade_paths,products_software_upgrade_paths_manage,products_software_version,products_software_version_access_rules_manage,products_software_version_manage,products_software_versions,products_versions,report_build,report_contracts_billing,report_healthindex,report_usage,reporting,rma,rma_history,rma_history_manage,rma_manage,rmas,user,user_credentials,user_manage,users');
|
||||||
/*Build*/
|
/*Build*/
|
||||||
define('build','dashboard,profile,application,buildtool,buildtool,firmwaretool,products_software');
|
define('build','dashboard,profile,application,buildtool,buildtool,firmwaretool,products_software');
|
||||||
/*Commerce*/
|
/*Commerce*/
|
||||||
@@ -18,7 +18,7 @@ define('firmware','application,firmwaretool,products_software');
|
|||||||
/*Garage*/
|
/*Garage*/
|
||||||
define('garage','dashboard,profile,application,cartest,cartest_manage,cartests,products_versions');
|
define('garage','dashboard,profile,application,cartest,cartest_manage,cartests,products_versions');
|
||||||
/*Interface*/
|
/*Interface*/
|
||||||
define('interface','application,firmwaretool,contract,contracts,equipment_manage,equipments,products_software,products_versions,users');
|
define('interface','application,firmwaretool,invoice,payment,transactions,transactions_items,contract,contracts,equipment_manage,equipments,products_software,products_versions,users');
|
||||||
/*Service*/
|
/*Service*/
|
||||||
define('service','admin,dashboard,profile,application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,servicereport,servicereports,equipment,equipment_manage,equipments,products_software,user,user_manage,users');
|
define('service','admin,dashboard,profile,application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,servicereport,servicereports,equipment,equipment_manage,equipments,products_software,user,user_manage,users');
|
||||||
/*Other*/
|
/*Other*/
|
||||||
|
|||||||
@@ -55,8 +55,10 @@ $all_views = [
|
|||||||
"history_manage",
|
"history_manage",
|
||||||
"identity",
|
"identity",
|
||||||
"identity_dealers",
|
"identity_dealers",
|
||||||
|
"initialize",
|
||||||
"invoice",
|
"invoice",
|
||||||
"language",
|
"language",
|
||||||
|
"licenses",
|
||||||
"logfile",
|
"logfile",
|
||||||
"mailer",
|
"mailer",
|
||||||
"maintenance",
|
"maintenance",
|
||||||
@@ -69,6 +71,7 @@ $all_views = [
|
|||||||
"orders",
|
"orders",
|
||||||
"partner",
|
"partner",
|
||||||
"partners",
|
"partners",
|
||||||
|
"payment",
|
||||||
"placeorder",
|
"placeorder",
|
||||||
"pricelists",
|
"pricelists",
|
||||||
"pricelists_items",
|
"pricelists_items",
|
||||||
@@ -120,6 +123,7 @@ $all_views = [
|
|||||||
"software_available",
|
"software_available",
|
||||||
"software_download",
|
"software_download",
|
||||||
"software_update",
|
"software_update",
|
||||||
|
"softwaretool",
|
||||||
"tax",
|
"tax",
|
||||||
"taxes",
|
"taxes",
|
||||||
"test",
|
"test",
|
||||||
@@ -136,6 +140,7 @@ $all_views = [
|
|||||||
"user_manage",
|
"user_manage",
|
||||||
"users",
|
"users",
|
||||||
"vin",
|
"vin",
|
||||||
|
"webhook_mollie",
|
||||||
];
|
];
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -18,24 +18,73 @@ $bearertoken = createCommunicationToken($_SESSION['userkey']);
|
|||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// PAYMENT RETURN DETECTION
|
// PAYMENT RETURN DETECTION
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
$payment_return = isset($_GET['payment_id']) ? $_GET['payment_id'] : null;
|
$payment_return = isset($_GET['order_id']) ? $_GET['order_id'] : null;
|
||||||
$payment_return_status = isset($_GET['payment_return']) ? $_GET['payment_return'] : null;
|
$payment_return_status = isset($_GET['payment_return']) ? $_GET['payment_return'] : null;
|
||||||
|
|
||||||
template_header('Softwaretool', 'softwaretool','view');
|
template_header('Softwaretool', 'softwaretool','view');
|
||||||
|
|
||||||
// Show payment return message if returning from payment
|
// Show payment return message if returning from payment
|
||||||
|
$view = '';
|
||||||
|
$payment_modal = '';
|
||||||
if ($payment_return && $payment_return_status) {
|
if ($payment_return && $payment_return_status) {
|
||||||
$view = '
|
// Check actual payment status in database
|
||||||
<div class="content-title">
|
$pdo = dbConnect($dbname);
|
||||||
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 15px; border-radius: 6px; margin-bottom: 20px;">
|
$sql = 'SELECT payment_status FROM transactions WHERE txn_id = ?';
|
||||||
<i class="fa-solid fa-check-circle"></i>
|
$stmt = $pdo->prepare($sql);
|
||||||
<strong>Payment Successful!</strong>
|
$stmt->execute([$payment_return]);
|
||||||
<p style="margin: 10px 0 0 0;">Your payment has been processed. Please reconnect your device to apply the software upgrade.</p>
|
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
<p style="margin: 5px 0 0 0; font-size: 12px; color: #666;">Payment ID: '.htmlspecialchars($payment_return).'</p>
|
|
||||||
</div>
|
if ($transaction) {
|
||||||
</div>';
|
if ($transaction['payment_status'] == 1) {
|
||||||
} else {
|
// Payment confirmed as paid
|
||||||
$view = '';
|
$payment_modal = '
|
||||||
|
<div id="paymentModal" class="modal" style="display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
|
||||||
|
<div class="modal-content" style="background: white; border-radius: 12px; max-width: 500px; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); position: relative;">
|
||||||
|
<span class="close" onclick="closePaymentModal()" style="position: absolute; top: 15px; right: 20px; font-size: 28px; font-weight: bold; color: #999; cursor: pointer;">×</span>
|
||||||
|
<div style="text-align: center; padding: 40px 30px;">
|
||||||
|
<i class="fa-solid fa-check-circle" style="font-size: 64px; color: #28a745; margin-bottom: 20px;"></i>
|
||||||
|
<h2 style="color: #155724; margin-bottom: 15px;">Payment Successful!</h2>
|
||||||
|
<p style="margin-bottom: 10px; color: #333;">Your payment has been processed. Please reconnect your device to apply the software upgrade.</p>
|
||||||
|
<p style="font-size: 12px; color: #666; margin-bottom: 25px;">Order ID: '.htmlspecialchars($payment_return).'</p>
|
||||||
|
<button onclick="closePaymentModal()" class="btn" style="padding: 12px 30px;">Continue</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>';
|
||||||
|
} else if ($transaction['payment_status'] == 0 || $transaction['payment_status'] == 101) {
|
||||||
|
// Payment pending
|
||||||
|
$payment_modal = '
|
||||||
|
<div id="paymentModal" class="modal" style="display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
|
||||||
|
<div class="modal-content" style="background: white; border-radius: 12px; max-width: 500px; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3);">
|
||||||
|
<div style="text-align: center; padding: 40px 30px;">
|
||||||
|
<i class="fa-solid fa-clock" style="font-size: 64px; color: #ffc107; margin-bottom: 20px;"></i>
|
||||||
|
<h2 style="color: #856404; margin-bottom: 15px;">Payment Processing...</h2>
|
||||||
|
<p style="margin-bottom: 10px; color: #333;">Your payment is being processed. This page will update automatically when confirmed.</p>
|
||||||
|
<p style="font-size: 12px; color: #666; margin-bottom: 25px;">Order ID: '.htmlspecialchars($payment_return).'</p>
|
||||||
|
<i class="fa-solid fa-spinner fa-spin" style="font-size: 32px; color: #ffc107;"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Auto-refresh every 3 seconds to check payment status
|
||||||
|
setTimeout(function() { location.reload(); }, 3000);
|
||||||
|
</script>';
|
||||||
|
} else {
|
||||||
|
// Payment failed/cancelled
|
||||||
|
$payment_modal = '
|
||||||
|
<div id="paymentModal" class="modal" style="display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
|
||||||
|
<div class="modal-content" style="background: white; border-radius: 12px; max-width: 500px; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); position: relative;">
|
||||||
|
<span class="close" onclick="closePaymentModal()" style="position: absolute; top: 15px; right: 20px; font-size: 28px; font-weight: bold; color: #999; cursor: pointer;">×</span>
|
||||||
|
<div style="text-align: center; padding: 40px 30px;">
|
||||||
|
<i class="fa-solid fa-exclamation-circle" style="font-size: 64px; color: #dc3545; margin-bottom: 20px;"></i>
|
||||||
|
<h2 style="color: #721c24; margin-bottom: 15px;">Payment Failed</h2>
|
||||||
|
<p style="margin-bottom: 10px; color: #333;">Your payment could not be processed. Please try again.</p>
|
||||||
|
<p style="font-size: 12px; color: #666; margin-bottom: 25px;">Order ID: '.htmlspecialchars($payment_return).'</p>
|
||||||
|
<button onclick="closePaymentModal()" class="btn" style="padding: 12px 30px;">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$view .= '
|
$view .= '
|
||||||
@@ -90,7 +139,7 @@ $view .= '<div class="content-block">
|
|||||||
|
|
||||||
<div id="softwareOptions" style="margin-top: 20px; display:none;">
|
<div id="softwareOptions" style="margin-top: 20px; display:none;">
|
||||||
<h3 style="margin-bottom: 20px; color: #333;">'.$softwaretool_select_upgrade.'</h3>
|
<h3 style="margin-bottom: 20px; color: #333;">'.$softwaretool_select_upgrade.'</h3>
|
||||||
<div id="softwareOptionsGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;">
|
<div id="softwareOptionsGrid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; justify-content: center; max-width: 1200px; margin: 0 auto;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -144,6 +193,9 @@ $view .= '</div>';
|
|||||||
//OUTPUT
|
//OUTPUT
|
||||||
echo $view;
|
echo $view;
|
||||||
|
|
||||||
|
// Output payment modal if exists
|
||||||
|
echo $payment_modal;
|
||||||
|
|
||||||
|
|
||||||
echo '
|
echo '
|
||||||
<script src="assets/upload.js?'.script_version.'"></script>
|
<script src="assets/upload.js?'.script_version.'"></script>
|
||||||
@@ -169,12 +221,29 @@ echo '
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Payment modal functions
|
||||||
|
window.closePaymentModal = function() {
|
||||||
|
const modal = document.getElementById("paymentModal");
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = "none";
|
||||||
|
// Clean URL by removing payment_return and order_id parameters
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.delete("payment_return");
|
||||||
|
url.searchParams.delete("order_id");
|
||||||
|
window.history.replaceState({}, document.title, url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Close modal on background click
|
// Close modal on background click
|
||||||
document.addEventListener("click", function(e) {
|
document.addEventListener("click", function(e) {
|
||||||
const modal = document.getElementById("helpModal");
|
const helpModal = document.getElementById("helpModal");
|
||||||
if (modal && e.target === modal) {
|
if (helpModal && e.target === helpModal) {
|
||||||
closeInstructions();
|
closeInstructions();
|
||||||
}
|
}
|
||||||
|
const paymentModal = document.getElementById("paymentModal");
|
||||||
|
if (paymentModal && e.target === paymentModal) {
|
||||||
|
closePaymentModal();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>';
|
</script>';
|
||||||
|
|
||||||
|
|||||||
@@ -2937,6 +2937,7 @@ main .products .product .price, main .products .products-wrapper .product .price
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-panel {
|
.filter-panel {
|
||||||
@@ -2979,6 +2980,7 @@ main .products .product .price, main .products .products-wrapper .product .price
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3085,3 +3087,45 @@ main .products .product .price, main .products .products-wrapper .product .price
|
|||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Button alignment styles */
|
||||||
|
.form-actions,
|
||||||
|
.modal-actions,
|
||||||
|
.dialog-actions,
|
||||||
|
.button-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure title-actions stay right-aligned */
|
||||||
|
.title-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form button containers should be right-aligned */
|
||||||
|
main .form .button-container,
|
||||||
|
main .form .form-actions,
|
||||||
|
main .content-block .button-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right-align buttons in dialog footers */
|
||||||
|
.dialog .content .footer,
|
||||||
|
.modal .modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
@@ -5,14 +5,22 @@
|
|||||||
|
|
||||||
require_once 'settings/config_redirector.php';
|
require_once 'settings/config_redirector.php';
|
||||||
require_once 'assets/functions.php';
|
require_once 'assets/functions.php';
|
||||||
|
include dirname(__FILE__).'/settings/settings_redirector.php';
|
||||||
|
|
||||||
|
// DEBUG: Log webhook call
|
||||||
|
debuglog("WEBHOOK CALLED - POST data: " . print_r($_POST, true));
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
//LOGIN TO API (same as commerce webhook.php)
|
//LOGIN TO API (same as commerce webhook.php)
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
$data = json_encode(array("clientID" => software_update_user, "clientsecret" => software_update_pw), JSON_UNESCAPED_UNICODE);
|
debuglog("WEBHOOK: Attempting API authorization...");
|
||||||
|
debuglog("WEBHOOK: Interface user: " . interface_user);
|
||||||
|
$data = json_encode(array("clientID" => interface_user, "clientsecret" => interface_pw), JSON_UNESCAPED_UNICODE);
|
||||||
$responses = ioAPIv2('/v2/authorization', $data,'');
|
$responses = ioAPIv2('/v2/authorization', $data,'');
|
||||||
|
debuglog("WEBHOOK: Authorization response: " . $responses);
|
||||||
if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';}
|
if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';}
|
||||||
$clientsecret = $responses['token'];
|
$clientsecret = $responses['token'];
|
||||||
|
debuglog("WEBHOOK: Token obtained: " . ($clientsecret ? 'YES' : 'NO'));
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// BASEURL is required for invoice template
|
// BASEURL is required for invoice template
|
||||||
@@ -64,7 +72,16 @@ try {
|
|||||||
// PRODUCTION MODE - Retrieve the payment's current state from Mollie
|
// PRODUCTION MODE - Retrieve the payment's current state from Mollie
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
$payment = $mollie->payments->get($_POST["id"]);
|
$payment = $mollie->payments->get($_POST["id"]);
|
||||||
$orderId = $payment->metadata->order_id;
|
// Get order ID from metadata (same as commerce product)
|
||||||
|
$orderId = $payment->metadata->order_id ?? null;
|
||||||
|
|
||||||
|
if (!$orderId) {
|
||||||
|
debuglog("WEBHOOK ERROR: No order_id in payment metadata");
|
||||||
|
http_response_code(400);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
debuglog("WEBHOOK: Payment ID: {$payment->id}, Order ID: {$orderId}");
|
||||||
$payment_status = null;
|
$payment_status = null;
|
||||||
|
|
||||||
if ($payment->isPaid() && !$payment->hasRefunds() && !$payment->hasChargebacks()) {
|
if ($payment->isPaid() && !$payment->hasRefunds() && !$payment->hasChargebacks()) {
|
||||||
@@ -81,18 +98,33 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// Update transaction status via API
|
// Update transaction status directly in database
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
if ($payment_status !== null) {
|
if ($payment_status !== null) {
|
||||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => $payment_status), JSON_UNESCAPED_UNICODE);
|
debuglog("WEBHOOK: Order ID: $orderId, Payment Status: $payment_status");
|
||||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
|
||||||
$transaction = json_decode($transaction,true);
|
$pdo = dbConnect($dbname);
|
||||||
|
|
||||||
|
// Update transaction status
|
||||||
|
$sql = 'UPDATE transactions SET payment_status = ? WHERE txn_id = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$payment_status, $orderId]);
|
||||||
|
|
||||||
|
debuglog("WEBHOOK: Transaction status updated in database");
|
||||||
|
|
||||||
|
// Fetch transaction data for license creation
|
||||||
|
$sql = 'SELECT * FROM transactions WHERE txn_id = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$orderId]);
|
||||||
|
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
debuglog("WEBHOOK: Transaction data: " . print_r($transaction, true));
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// Only create license and invoice if payment is PAID
|
// Only create license and invoice if payment is PAID
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
if ($payment_status == 1 && $transaction !== null && !empty($transaction)) {
|
if ($payment_status == 1 && $transaction !== null && !empty($transaction)) {
|
||||||
if(count($transaction) > 0) {
|
debuglog("WEBHOOK: Payment is PAID, processing license...");
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// CREATE LICENSE for software upgrade
|
// CREATE LICENSE for software upgrade
|
||||||
@@ -100,9 +132,10 @@ try {
|
|||||||
$pdo = dbConnect($dbname);
|
$pdo = dbConnect($dbname);
|
||||||
|
|
||||||
// Fetch transaction items to find software upgrade
|
// Fetch transaction items to find software upgrade
|
||||||
|
// Note: transactions_items.txn_id is the database ID (transaction.id), not the txn_id string
|
||||||
$sql = 'SELECT * FROM transactions_items WHERE txn_id = ?';
|
$sql = 'SELECT * FROM transactions_items WHERE txn_id = ?';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$orderId]);
|
$stmt->execute([$transaction['id']]);
|
||||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
@@ -124,14 +157,14 @@ try {
|
|||||||
|
|
||||||
// Create license
|
// Create license
|
||||||
$sql = 'INSERT INTO products_software_licenses
|
$sql = 'INSERT INTO products_software_licenses
|
||||||
(license_key, equipment_id, license_type, status, start_at, expires_at, transaction_id, created, createdby)
|
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
|
$item['item_id'], // version_id
|
||||||
|
1, // license_type (1 = upgrade)
|
||||||
$license_key,
|
$license_key,
|
||||||
$options['equipment_id'],
|
1, // status = active
|
||||||
'upgrade',
|
|
||||||
1, // active
|
|
||||||
date('Y-m-d H:i:s'),
|
date('Y-m-d H:i:s'),
|
||||||
'2099-12-31 23:59:59', // effectively permanent
|
'2099-12-31 23:59:59', // effectively permanent
|
||||||
$orderId,
|
$orderId,
|
||||||
@@ -139,26 +172,79 @@ try {
|
|||||||
'webhook' // created by webhook
|
'webhook' // created by webhook
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
debuglog("WEBHOOK: License created: $license_key");
|
||||||
|
|
||||||
// Update equipment.sw_version_license
|
// Update equipment.sw_version_license
|
||||||
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
|
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$license_key, $options['equipment_id']]);
|
$stmt->execute([$license_key, $options['equipment_id']]);
|
||||||
|
|
||||||
|
debuglog("WEBHOOK: Equipment updated with license: {$options['equipment_id']}");
|
||||||
|
} else {
|
||||||
|
debuglog("WEBHOOK: License already exists for order: $orderId");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debuglog("WEBHOOK: Not a software upgrade item (no serial_number/equipment_id)");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debuglog("WEBHOOK: No item_options found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// Generate INVOICE via API
|
// Generate INVOICE directly in database
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
$payload = json_encode(array("txn_id" => $transaction['transaction_id']), JSON_UNESCAPED_UNICODE);
|
|
||||||
$invoice = ioAPIv2('/v2/invoice/',$payload,$clientsecret);
|
|
||||||
$invoice = json_decode($invoice,true);
|
|
||||||
|
|
||||||
if ($invoice !== null && !empty($invoice)) {
|
// Check if invoice already exists for this transaction
|
||||||
// Fetch full invoice data with customer details
|
$sql = 'SELECT id FROM invoice WHERE txn_id = ?';
|
||||||
$invoice_cust = ioAPIv2('/v2/invoice/list=invoice&id='.$invoice['invoice_id'],'',$clientsecret);
|
$stmt = $pdo->prepare($sql);
|
||||||
$invoice_cust = json_decode($invoice_cust,true);
|
$stmt->execute([$transaction['txn_id']]);
|
||||||
|
$existing_invoice = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$existing_invoice) {
|
||||||
|
// Create invoice
|
||||||
|
$sql = 'INSERT INTO invoice (txn_id, payment_status, payment_amount, shipping_amount, discount_amount, tax_amount, created)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
$transaction['txn_id'],
|
||||||
|
$transaction['payment_status'],
|
||||||
|
$transaction['payment_amount'],
|
||||||
|
$transaction['shipping_amount'] ?? 0.00,
|
||||||
|
$transaction['discount_amount'] ?? 0.00,
|
||||||
|
$transaction['tax_amount'] ?? 0.00,
|
||||||
|
date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
$invoice_id = $pdo->lastInsertId();
|
||||||
|
debuglog("WEBHOOK: Invoice created with ID: $invoice_id");
|
||||||
|
} else {
|
||||||
|
$invoice_id = $existing_invoice['id'];
|
||||||
|
debuglog("WEBHOOK: Invoice already exists with ID: $invoice_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch full invoice data with customer details for email
|
||||||
|
// Note: invoice.txn_id = transactions.txn_id (VARCHAR order ID)
|
||||||
|
// transactions_items.txn_id = transactions.id (INT database ID)
|
||||||
|
$sql = 'SELECT tx.*, txi.item_id, txi.item_price, txi.item_quantity, txi.item_options,
|
||||||
|
p.productcode, p.productname, inv.id as invoice, inv.created as invoice_created,
|
||||||
|
i.language as user_language
|
||||||
|
FROM invoice inv
|
||||||
|
LEFT JOIN transactions tx ON tx.txn_id = inv.txn_id
|
||||||
|
LEFT JOIN transactions_items txi ON txi.txn_id = tx.id
|
||||||
|
LEFT JOIN products p ON p.rowID = txi.item_id
|
||||||
|
LEFT JOIN identity i ON i.userkey = tx.account_id
|
||||||
|
WHERE inv.id = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$invoice_id]);
|
||||||
|
$invoice_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
debuglog("WEBHOOK: Invoice data fetched: " . print_r($invoice_data, true));
|
||||||
|
|
||||||
|
if (!empty($invoice_data)) {
|
||||||
|
debuglog("WEBHOOK: Transforming invoice data...");
|
||||||
|
// Transform the data (group items like the API does)
|
||||||
|
$invoice_cust = transformOrderData($invoice_data);
|
||||||
|
debuglog("WEBHOOK: Transformed invoice data: " . print_r($invoice_cust, true));
|
||||||
|
|
||||||
// Determine invoice language
|
// Determine invoice language
|
||||||
if (!empty($invoice_cust['customer']['language'])) {
|
if (!empty($invoice_cust['customer']['language'])) {
|
||||||
@@ -170,28 +256,44 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate invoice HTML (using custom template for software upgrades)
|
// Generate invoice HTML (using custom template for software upgrades)
|
||||||
|
debuglog("WEBHOOK: Calling generateSoftwareInvoice with language: $invoice_language");
|
||||||
list($data,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language);
|
list($data,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language);
|
||||||
|
debuglog("WEBHOOK: Invoice generated - Customer email: $customer_email, Order ID: $order_id");
|
||||||
|
debuglog("WEBHOOK: Invoice HTML length: " . strlen($data));
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
//CREATE PDF using DomPDF
|
//CREATE PDF using DomPDF
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
debuglog("WEBHOOK: Creating PDF...");
|
||||||
$dompdf->loadHtml($data);
|
$dompdf->loadHtml($data);
|
||||||
$dompdf->setPaper('A4', 'portrait');
|
$dompdf->setPaper('A4', 'portrait');
|
||||||
$dompdf->render();
|
$dompdf->render();
|
||||||
$subject = 'Software Upgrade - Invoice: '.$order_id;
|
$subject = 'Software Upgrade - Invoice: '.$order_id;
|
||||||
$attachment = $dompdf->output();
|
$attachment = $dompdf->output();
|
||||||
|
debuglog("WEBHOOK: PDF created, size: " . strlen($attachment) . " bytes");
|
||||||
|
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
//Send email via PHPMailer
|
//Send email via PHPMailer
|
||||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
send_mail($customer_email, $subject, $data, $attachment, $subject);
|
debuglog("WEBHOOK: Attempting to send email to: $customer_email");
|
||||||
|
debuglog("WEBHOOK: Email subject: $subject");
|
||||||
|
debuglog("WEBHOOK: Email config - Host: " . (defined('email_host_name') ? email_host_name : 'NOT DEFINED'));
|
||||||
|
debuglog("WEBHOOK: Email config - Port: " . (defined('email_outgoing_port') ? email_outgoing_port : 'NOT DEFINED'));
|
||||||
|
debuglog("WEBHOOK: Email config - Security: " . (defined('email_outgoing_security') ? email_outgoing_security : 'NOT DEFINED'));
|
||||||
|
debuglog("WEBHOOK: Email config - Username: " . (defined('email') ? email : 'NOT DEFINED'));
|
||||||
|
|
||||||
|
// The send_mail function will exit on error and debuglog the error
|
||||||
|
$mail_result = send_mail($customer_email, $subject, $data, $attachment, $subject);
|
||||||
|
debuglog("WEBHOOK: Email sent successfully to: $customer_email");
|
||||||
|
|
||||||
// Send to bookkeeping if configured
|
// Send to bookkeeping if configured
|
||||||
if(invoice_bookkeeping){
|
if(invoice_bookkeeping){
|
||||||
|
debuglog("WEBHOOK: Sending to bookkeeping: " . email_bookkeeping);
|
||||||
send_mail(email_bookkeeping, $subject, $data, $attachment, $subject);
|
send_mail(email_bookkeeping, $subject, $data, $attachment, $subject);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debuglog("WEBHOOK: No invoice data found for invoice_id: $invoice_id");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,9 +302,13 @@ try {
|
|||||||
echo "OK";
|
echo "OK";
|
||||||
|
|
||||||
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
||||||
|
debuglog("WEBHOOK ERROR (Mollie API): " . $e->getMessage());
|
||||||
|
debuglog("WEBHOOK ERROR TRACE: " . $e->getTraceAsString());
|
||||||
error_log("Webhook API call failed: " . htmlspecialchars($e->getMessage()));
|
error_log("Webhook API call failed: " . htmlspecialchars($e->getMessage()));
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
debuglog("WEBHOOK ERROR (General): " . $e->getMessage());
|
||||||
|
debuglog("WEBHOOK ERROR TRACE: " . $e->getTraceAsString());
|
||||||
error_log("Webhook error: " . htmlspecialchars($e->getMessage()));
|
error_log("Webhook error: " . htmlspecialchars($e->getMessage()));
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user