feat: Enhance software tool with country selection and tax calculation
- Added a helper function to generate country select options in software tool. - Updated user info modal and payment modal to use country dropdowns instead of text inputs. - Implemented tax calculation based on selected country in payment modal. - Improved software options loading behavior in debug mode. - Enhanced description formatting in payment modal. - Added log modal for equipment updates with a link to view logs. - Introduced a new countries settings file with tax rates for various countries. - Minor adjustments to various PHP files for better handling of equipment and payment processes.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ api/v2/.DS_Store
|
||||
api/.DS_Store
|
||||
assets/.DS_Store
|
||||
assets/images/.DS_Store
|
||||
assets/database/ManualUpdates.sql
|
||||
|
||||
12
api.php
12
api.php
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
define('secure_34563$52', true);
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// Get DATA from API
|
||||
//------------------------------------------
|
||||
@@ -16,6 +17,17 @@ require_once './assets/functions.php';
|
||||
include './settings/settings_redirector.php';
|
||||
include './settings/config_redirector.php';
|
||||
|
||||
if (debug){
|
||||
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
||||
debuglog("PHP ERROR [$errno]: $errstr in $errfile on line $errline");
|
||||
return false; // Let PHP handle as usual (optional)
|
||||
});
|
||||
|
||||
set_exception_handler(function($exception) {
|
||||
debuglog("PHP EXCEPTION: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
// Header security - enabled via config
|
||||
//------------------------------------------
|
||||
|
||||
BIN
api/.DS_Store
vendored
BIN
api/.DS_Store
vendored
Binary file not shown.
@@ -56,7 +56,9 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$user = $username;
|
||||
$account = $partnerhierarchy; //string
|
||||
$current_date = date("Y-m-d");
|
||||
$service_date = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
|
||||
$warranty_date = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
|
||||
$order_send_date = date("Y-m-d");
|
||||
$input_type = $post_content['type'];
|
||||
$testdetails = json_encode($post_content['testdetails']);
|
||||
$serial = $post_content['sn'];
|
||||
@@ -187,9 +189,9 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
|
||||
// Create equipment when not exist +++++++++++++++++++++++++
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if ($equipmentCreate == 1 && $total_equipment == 0){
|
||||
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date) VALUES (?,?,?,?,?,?,?,?)';
|
||||
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date,order_send_date) VALUES (?,?,?,?,?,?,?,?,?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$current_date,$current_date]);
|
||||
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$service_date,$warranty_date,$order_send_date]);
|
||||
$rowID = $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
@@ -311,7 +313,7 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
|
||||
//Update Equipment record
|
||||
$sql = "UPDATE equipment SET service_date = ? $whereclause";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$current_date]);
|
||||
$stmt->execute([$service_date]);
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
BIN
api/v1/.DS_Store
vendored
BIN
api/v1/.DS_Store
vendored
Binary file not shown.
@@ -85,20 +85,6 @@ switch ($action) {
|
||||
$message_box = [];
|
||||
$timestamp = date("Y-m-d H:i:s");
|
||||
|
||||
// Create history description
|
||||
$history_description = [
|
||||
"start_date"=>$timestamp,
|
||||
"end_date"=>date("Y-m-d", strtotime("+730 days")),
|
||||
"organization"=>strip_tags(trim($post_content['organization'])),
|
||||
"phone"=>strip_tags(trim($post_content['phone'])),
|
||||
"city"=>strip_tags(trim($post_content['city'])),
|
||||
"country"=>strip_tags(trim($post_content['country'])),
|
||||
"email_consent"=>strip_tags(trim($post_content['email_consent'])),
|
||||
"terms_consent"=>strip_tags(trim($post_content['terms_consent']))
|
||||
];
|
||||
|
||||
$description = json_encode($history_description, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// --------------------------------------------
|
||||
// Check if multiple serialnumbers are provided
|
||||
// --------------------------------------------
|
||||
@@ -108,9 +94,12 @@ switch ($action) {
|
||||
|
||||
foreach ($serial_numbers as $sn) {
|
||||
// Get equipment ID based on serial number
|
||||
$rowID = getrowID($dbname, 'rowID', 'equipment', 'serialnumber="' . $sn . '"');
|
||||
$sql = 'SELECT rowID, warranty_date, order_send_date from equipment where serialnumber = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$sn]);
|
||||
$rowID = $stmt->fetch();
|
||||
|
||||
if (!$rowID) {
|
||||
if (!$rowID['rowID']) {
|
||||
// Serial number not recognized
|
||||
$message_box[] = $sn . ' - ' . $register_message_1;
|
||||
continue;
|
||||
@@ -128,9 +117,46 @@ switch ($action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//define warranty_end_date
|
||||
$order_send_date = $rowID['order_send_date'] ?? $rowID['warranty_date'];
|
||||
|
||||
// Check if order_send_date is available
|
||||
if (empty($order_send_date)) {
|
||||
// No valid date found - skip this serial number
|
||||
$message_box[] = $sn . ' - ' . $register_message_1; // or create a specific message for missing date
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate warranty end date based on eligibility window
|
||||
$current_date = new DateTime();
|
||||
$order_date = new DateTime($order_send_date);
|
||||
$months_diff = $current_date->diff($order_date)->m + ($current_date->diff($order_date)->y * 12);
|
||||
|
||||
if ($months_diff <= WARRANTY_ELIGIBILITY_WINDOW) {
|
||||
// Within eligibility window - apply extended warranty
|
||||
$warranty_end_date = (clone $order_date)->modify('+' . WARRANTY_EXTENDED_MONTH . ' months')->format('Y-m-d');
|
||||
} else {
|
||||
// Outside eligibility window - apply standard warranty
|
||||
$warranty_end_date = (clone $order_date)->modify('+' . WARRANTY_MONTHS . ' months')->format('Y-m-d');
|
||||
}
|
||||
|
||||
// Not under warranty - process registration
|
||||
$firmware_account_send = 1;
|
||||
|
||||
//Create history description
|
||||
$history_description = [
|
||||
"start_date"=>$timestamp,
|
||||
"end_date"=> $warranty_end_date,
|
||||
"organization"=>strip_tags(trim($post_content['organization'])),
|
||||
"phone"=>strip_tags(trim($post_content['phone'])),
|
||||
"city"=>strip_tags(trim($post_content['city'])),
|
||||
"country"=>strip_tags(trim($post_content['country'])),
|
||||
"email_consent"=>strip_tags(trim($post_content['email_consent'])),
|
||||
"terms_consent"=>strip_tags(trim($post_content['terms_consent']))
|
||||
];
|
||||
|
||||
$description = json_encode($history_description, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Create history entry
|
||||
$sql = 'INSERT INTO equipment_history (equipmentid, type, description, created, createdby, updatedby) VALUES (?,?,?,?,?,?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
@@ -190,11 +216,11 @@ switch ($action) {
|
||||
// Update equipment record
|
||||
$sql = 'UPDATE equipment SET status = ?, warranty_date = ?, accounthierarchy = ?, updatedby = ? WHERE rowID = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(['4', $warranty_extended, $partnerhierarchy_json, $username, $rowID['rowID']]);
|
||||
$stmt->execute(['4', $warranty_end_date, $partnerhierarchy_json, $username, $rowID['rowID']]);
|
||||
|
||||
// Add warranty to changelog
|
||||
$warranty_user = $post_content['email'] ?? 'system';
|
||||
changelog($dbname, 'equipment', $rowID['rowID'], 'Warranty', $warranty_extended, $warranty_user);
|
||||
changelog($dbname, 'equipment', $rowID['rowID'], 'Warranty', $warranty_end_date, $warranty_user);
|
||||
|
||||
// Serial number recognized
|
||||
$message_box[] = $sn . ' - ' . $register_message_3;
|
||||
|
||||
BIN
api/v2/.DS_Store
vendored
BIN
api/v2/.DS_Store
vendored
Binary file not shown.
@@ -294,8 +294,9 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
FROM products_software_upgrade_paths pup
|
||||
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE pup.to_version_id = ?
|
||||
AND LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
AND pup.is_active = 1';
|
||||
AND (LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
OR pup.from_version_id = 9999999)
|
||||
AND pup.is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version['version_id'], $current_sw_version]);
|
||||
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -332,26 +332,28 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
|
||||
} else {
|
||||
//Part of an upgrade path system
|
||||
//Only show if there's an explicit path FROM current version TO this version
|
||||
// OR a wildcard path (from_version_id = 9999999)
|
||||
$sql = 'SELECT pup.price, pup.currency
|
||||
FROM products_software_upgrade_paths pup
|
||||
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
|
||||
WHERE pup.to_version_id = ?
|
||||
AND LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
AND pup.is_active = 1';
|
||||
AND (LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
|
||||
OR pup.from_version_id = 9999999)
|
||||
AND pup.is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version['version_id'], $current_sw_version]);
|
||||
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($upgrade_path) {
|
||||
//Valid upgrade path found FROM current version
|
||||
//Valid upgrade path found FROM current version or wildcard
|
||||
$show_version = true;
|
||||
$final_price = $upgrade_path['price'] ?? '0.00';
|
||||
$final_currency = $upgrade_path['currency'] ?? '';
|
||||
$decision_reason = 'Showing - found upgrade path FROM current (' . $current_sw_version . ') with price: ' . $final_price . ' ' . $final_currency;
|
||||
$decision_reason = 'Showing - found upgrade path FROM current (' . $current_sw_version . ') or wildcard with price: ' . $final_price . ' ' . $final_currency;
|
||||
} else {
|
||||
$decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ')';
|
||||
$decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ') or wildcard';
|
||||
}
|
||||
//If no path from current version exists, don't show (show_version stays false)
|
||||
//If no path from current version or wildcard exists, don't show (show_version stays false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,9 +148,9 @@ if ($command == 'update'){
|
||||
//RESET WARRANTY AND SERVICE DATES WHEN STATUS IS CHANGED TO SEND(3)
|
||||
if (isset($post_content['status']) && $post_content['status'] == 3 && $equipment_data['status'] != 3)
|
||||
{
|
||||
$post_content['service_date'] = $date;
|
||||
$post_content['warranty_date'] = $date;
|
||||
|
||||
$post_content['service_date'] = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
|
||||
$post_content['warranty_date'] = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
|
||||
$post_content['order_send_date'] = $date;
|
||||
}
|
||||
//UPDATE CHANGELOG BASED ON STATUS CHANGE
|
||||
if (isset($post_content['status']) && $post_content['status'] != $equipment_data['status'])
|
||||
@@ -188,8 +188,15 @@ elseif ($command == 'insert'){
|
||||
$post_content['created'] = $date;
|
||||
$post_content['createdby'] = $username;
|
||||
$post_content['accounthierarchy'] = $accounthierarchy;
|
||||
$post_content['service_date'] = $date;
|
||||
$post_content['warranty_date'] = $date;
|
||||
$post_content['service_date'] = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
|
||||
$post_content['warranty_date'] = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
|
||||
|
||||
if (isset($post_content['status']) && $post_content['status'] == 3)
|
||||
{
|
||||
$post_content['order_send_date'] = $date;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
//do nothing
|
||||
|
||||
@@ -44,12 +44,16 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
|
||||
$updateObject_visual = 0; //update visual inspection object
|
||||
$sendServiceReport = 0; //send service report via email
|
||||
$transfercartest = 0; //Update cartest table with incoming data
|
||||
$create_software_license = 0; //Create software license
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//SET DEFAULT PARAMETERS
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$user = $username;
|
||||
$account = $partnerhierarchy; //string
|
||||
$current_date = date("Y-m-d");
|
||||
$service_date = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
|
||||
$warranty_date = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
|
||||
$order_send_date = date("Y-m-d");
|
||||
$input_type = $post_content['type'];
|
||||
$testdetails = json_encode($post_content['payload']);
|
||||
$serial = $post_content['sn'];
|
||||
@@ -146,6 +150,11 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
|
||||
$transfercartest = 1;
|
||||
break;
|
||||
|
||||
case 12: //customer_consent
|
||||
$historytype = 'Customer_consent';
|
||||
$create_software_license = 1;
|
||||
break;
|
||||
|
||||
case 'firmware': //update from Portal
|
||||
$historytype = $HistoryType_2;
|
||||
$equipmentUpdate = 1;
|
||||
@@ -171,7 +180,7 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//CHECK if EQUIPMENT EXISTS
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$sql = "SELECT count(rowID) as total, rowID FROM equipment $whereclause";
|
||||
$sql = "SELECT count(rowID) as total, rowID, hw_version FROM equipment $whereclause";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute();
|
||||
$total = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -182,9 +191,9 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
|
||||
// Create equipment when not exist +++++++++++++++++++++++++
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if ($equipmentCreate == 1 && $total_equipment == 0){
|
||||
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date) VALUES (?,?,?,?,?,?,?,?)';
|
||||
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date,order_send_date) VALUES (?,?,?,?,?,?,?,?,?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$current_date,$current_date]);
|
||||
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$service_date,$warranty_date,$order_send_date]);
|
||||
$rowID = $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
@@ -305,7 +314,7 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
|
||||
//Update Equipment record
|
||||
$sql = "UPDATE equipment SET service_date = ? $whereclause";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$current_date]);
|
||||
$stmt->execute([$service_date]);
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
@@ -357,6 +366,49 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
|
||||
if ($transfercartest == 1){
|
||||
convertCartest();
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// create software license ++++++++++++++++++++++++++
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
if ($create_software_license == 1){
|
||||
// Generate unique license key
|
||||
$license_key = generateUniqueLicenseKey();
|
||||
|
||||
$sw_version_consent = strtolower($post_content['testdetails']['logdetails']['FW'] ?? '');// version_id
|
||||
$eq_version_hw = strtolower($rowID['hw_version'] ?? '');
|
||||
|
||||
//GET VERSION_ID FROM VERSION TABLE
|
||||
$sql = 'SELECT rowID FROM products_software_versions WHERE version = ? and hw_version = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$sw_version_consent, $eq_version_hw]);
|
||||
$version_row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
//GET VERSION_ID or use WILDCARD
|
||||
$sw_version_consent = $version_row['rowID'] ?? '9999999';
|
||||
|
||||
// Create license
|
||||
$sql = 'INSERT INTO products_software_licenses
|
||||
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$sw_version_consent,
|
||||
1, // license_type (1 = upgrade)
|
||||
$license_key,
|
||||
1, // status = active
|
||||
date('Y-m-d H:i:s'),
|
||||
'2099-12-31 23:59:59', // effectively permanent
|
||||
'Customer_consent',
|
||||
date('Y-m-d H:i:s'),
|
||||
$user
|
||||
]);
|
||||
|
||||
// Update equipment.sw_version_license
|
||||
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$license_key, $rowID]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -25,6 +25,11 @@ $user_data = $post_content['user_data'] ?? [];
|
||||
// Read payment_provider from top level first, then fallback to user_data
|
||||
$payment_provider = $post_content['payment_provider'] ?? $user_data['payment_provider'] ?? 'mollie';
|
||||
|
||||
// Extract tax information from user_data (sent from frontend)
|
||||
$item_price = $user_data['item_price'] ?? null; // Price without VAT
|
||||
$tax_amount = $user_data['tax_amount'] ?? 0; // VAT amount
|
||||
$payment_amount = $user_data['payment_amount'] ?? null; // Total including VAT
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 1: Get equipment data from serial_number
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
@@ -165,8 +170,17 @@ if (debug) {
|
||||
// STEP 7: Create payment based on provider
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
try {
|
||||
// Use payment_amount (with tax) if provided, otherwise use final_price
|
||||
$amount_to_charge = $payment_amount ? (float)$payment_amount : (float)$final_price;
|
||||
|
||||
// Format price (must be string with 2 decimals)
|
||||
$formatted_price = number_format((float)$final_price, 2, '.', '');
|
||||
$formatted_price = number_format($amount_to_charge, 2, '.', '');
|
||||
|
||||
if (debug) {
|
||||
debuglog("DEBUG: Item Price (excl. VAT): " . ($item_price ?? $final_price));
|
||||
debuglog("DEBUG: Tax Amount: " . $tax_amount);
|
||||
debuglog("DEBUG: Total Amount (incl. VAT): " . $amount_to_charge);
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 7A: Generate transaction ID BEFORE creating payment
|
||||
@@ -252,7 +266,7 @@ try {
|
||||
throw new Exception("No approval URL received from PayPal");
|
||||
}
|
||||
|
||||
$payment_method_id = 1; // PayPal
|
||||
$payment_method_id = 3; // PayPal
|
||||
$payment_metadata = 'paypal_order_id';
|
||||
|
||||
} else {
|
||||
@@ -291,7 +305,7 @@ try {
|
||||
debuglog("DEBUG: Checkout URL: $checkout_url");
|
||||
}
|
||||
|
||||
$payment_method_id = 0; // Mollie
|
||||
$payment_method_id = 1; // Mollie
|
||||
$payment_metadata = 'mollie_payment_id';
|
||||
}
|
||||
|
||||
@@ -313,13 +327,14 @@ try {
|
||||
// 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, tax_amount, payment_status, payer_email, first_name, last_name,
|
||||
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, accounthierarchy, created)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$txn_id,
|
||||
$final_price,
|
||||
$amount_to_charge, // Total amount including tax
|
||||
$tax_amount, // Tax amount
|
||||
0, // 0 = pending
|
||||
$user_data['email'] ?? '',
|
||||
$first_name,
|
||||
@@ -348,13 +363,16 @@ try {
|
||||
$payment_metadata => $payment_id // Store payment provider ID
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Use item_price (without VAT) if provided, otherwise use final_price
|
||||
$item_price_to_store = $item_price ? (float)$item_price : (float)$final_price;
|
||||
|
||||
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
|
||||
VALUES (?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$transaction_id,
|
||||
$version_id,
|
||||
$final_price,
|
||||
$item_price_to_store, // Price without VAT
|
||||
1,
|
||||
$item_options,
|
||||
date('Y-m-d H:i:s')
|
||||
|
||||
BIN
assets/.DS_Store
vendored
BIN
assets/.DS_Store
vendored
Binary file not shown.
@@ -1264,8 +1264,10 @@ function ioServer($api_call, $data){
|
||||
$http_status = curl_getinfo($curl) ?? '200';
|
||||
curl_close($curl);
|
||||
|
||||
if(debug){
|
||||
debuglog($date." - ioServer: URL=$url, HTTP Code=$http_status, Response=" . substr($resp, 0, 500) . (strlen($resp) > 500 ? '...' : ''));
|
||||
|
||||
if (debug) {
|
||||
$resp_log = $date . " - ioServer: URL=$url, HTTP Code= ". ($http_status['http_code'] ?? 'unknown') . ", Response=" . substr($resp, 0, 500) . (strlen($resp) > 500 ? '...' : '');
|
||||
debuglog(json_encode($resp_log));
|
||||
}
|
||||
|
||||
//Check If errorcode is returned
|
||||
@@ -1728,33 +1730,38 @@ function getPartnerID($str){
|
||||
// overview Indicators
|
||||
//------------------------------------------
|
||||
function overviewIndicators($warranty, $service, $sw_version, $sw_version_latest){
|
||||
include dirname(__FILE__,2).'/settings/settings_redirector.php';
|
||||
include dirname(__FILE__,2).'/settings/systemfirmware.php';
|
||||
$indicator ='';
|
||||
//In warranty
|
||||
if (!empty($warranty ) && $warranty > $warrantydate){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">W</span>';
|
||||
} else {
|
||||
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">W</span>';
|
||||
}
|
||||
//Out of Service
|
||||
if (!empty($service) && $service < $servicedate){
|
||||
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">S</span>';
|
||||
} else {
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">S</span>';
|
||||
}
|
||||
|
||||
include dirname(__FILE__,2).'/settings/settings_redirector.php';
|
||||
include dirname(__FILE__,2).'/settings/systemfirmware.php';
|
||||
|
||||
$indicator ='';
|
||||
$current_date = date('Y-m-d');
|
||||
|
||||
//In warranty
|
||||
if (!empty($warranty ) && $warranty >= $current_date){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">W</span>';
|
||||
} else {
|
||||
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">W</span>';
|
||||
}
|
||||
//Out of Service
|
||||
if (!empty($service) && $service >= $current_date){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">S</span>';
|
||||
} else {
|
||||
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">S</span>';
|
||||
}
|
||||
|
||||
//Firmware
|
||||
if (isset($sw_version_latest)){
|
||||
if($sw_version_latest == 1){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
|
||||
if($sw_version_latest == 1){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
|
||||
}
|
||||
else {
|
||||
if ($sw_version == ''){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
|
||||
} else {
|
||||
if ($sw_version == ''){
|
||||
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
|
||||
} else {
|
||||
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">F</span>';
|
||||
}
|
||||
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">F</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $indicator;
|
||||
@@ -1783,11 +1790,12 @@ function warrantyStatus($input){
|
||||
}
|
||||
|
||||
$warranty_date_due ='<span class="status">Unknown</span>';
|
||||
$current_date = date('Y-m-d');
|
||||
|
||||
if (!empty($input) && $input < $warrantydate){
|
||||
$warranty_date_due = '<span class="status warranty_outdated">'.$warranty_outdated_text.'</span>';
|
||||
if (!empty($input) && $input >= $current_date){
|
||||
$warranty_date_due = '<span class="">'.$warranty_recent.' ('.$input.')</span>';
|
||||
} else {
|
||||
$warranty_date_due = '<span class="">'.$warranty_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')</span>';
|
||||
$warranty_date_due = '<span class="status warranty_outdated">'.$warranty_outdated_text.'</span>';
|
||||
}
|
||||
|
||||
return $warranty_date_due;
|
||||
@@ -1815,12 +1823,14 @@ function serviceStatus($input){
|
||||
include dirname(__FILE__,2).'/settings/translations/translations_US.php';
|
||||
}
|
||||
|
||||
$current_date = date('Y-m-d');
|
||||
$service_date_due ='<span class="status">Unknown</span>';
|
||||
|
||||
if (!empty($input) && $input < $servicedate){
|
||||
$service_date_due = '<span class="status service_renewal">'.$service_renewal_text.'</span>';
|
||||
if (!empty($input) && $input >= $current_date){
|
||||
$service_date_due ='<span class="">'.$service_recent.' ('.$input.')</span>';
|
||||
} else {
|
||||
$service_date_due ='<span class="">'.$service_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')</span>';
|
||||
$service_date_due = '<span class="status service_renewal">'.$service_renewal_text.'</span>';
|
||||
|
||||
}
|
||||
|
||||
return $service_date_due;
|
||||
@@ -2976,20 +2986,29 @@ function showlog($object,$objectID){
|
||||
$stmt->execute([$object,$objectID]);
|
||||
$changes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$view = '<label for="productcode">Changelog</label>';
|
||||
foreach($changes as $change){
|
||||
$view = '<div class="reg-fields">';
|
||||
if ($changes) {
|
||||
foreach ($changes as $change) {
|
||||
$object_value = $change['object_value'];
|
||||
// Human-readable status
|
||||
if ($object == 'equipment' && $change['object_field'] == 'status') {
|
||||
$object_text = 'status' . $change['object_value'] . '_text';
|
||||
if (isset($$object_text)) {
|
||||
$object_value = $$object_text;
|
||||
}
|
||||
}
|
||||
$entry = htmlspecialchars( $object_value . ' - ' . $change['created'] . ' - ' . $change['createdby']);
|
||||
$view .= ' <div class="reg-field">
|
||||
<label>'.$change['object_field'].'</label>
|
||||
<p>'.$entry.'</p>
|
||||
</div>';
|
||||
|
||||
$object_value = $change['object_value'];
|
||||
|
||||
//UPDATE TO HUMANREADABLE STATUS
|
||||
if ($object == 'equipment' && $change['object_field'] == 'status'){
|
||||
$object_text = 'status'.$change['object_value'].'_text';
|
||||
$object_value = $$object_text;
|
||||
}
|
||||
} else {
|
||||
$view .= '<div style="color:#888;font-size:13px;padding:8px;">No changelog entries found.</div>';
|
||||
}
|
||||
$view .= '<input id="name" type="text" value="'.$change['object_field'].' - '.$object_value.' - '.$change['created'].' - '.$change['createdby'].'" readonly>';
|
||||
}
|
||||
|
||||
return $view;
|
||||
$view .= '</div>';
|
||||
return $view;
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
@@ -5578,3 +5597,47 @@ function updateSoftwareLatestFlags($pdo, $version_id, $hw_version) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// Generate Countries File from Taxes API +++++++++++++++++
|
||||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
function generateCountriesFile($token){
|
||||
|
||||
//API call to get all taxes
|
||||
$api_url = '/v2/taxes';
|
||||
$response = ioAPIv2($api_url, '', $token);
|
||||
|
||||
if(!empty($response)){
|
||||
//decode the API response
|
||||
$taxes = json_decode($response, true);
|
||||
|
||||
if(!empty($taxes) && is_array($taxes)){
|
||||
//Build the countries array - id as key, with country name and tax rate
|
||||
$countries = [];
|
||||
foreach($taxes as $tax){
|
||||
$countries[$tax['id']] = [
|
||||
'country' => $tax['country'] ?? '',
|
||||
'taxes' => $tax['rate'] ?? 0
|
||||
];
|
||||
}
|
||||
|
||||
//Generate PHP file content
|
||||
$fileContent = "<?php\n";
|
||||
$fileContent .= "// Auto-generated countries file from taxes API\n";
|
||||
$fileContent .= "// Generated on: " . date('Y-m-d H:i:s') . "\n\n";
|
||||
$fileContent .= "\$countries = [\n";
|
||||
foreach($countries as $id => $data){
|
||||
$fileContent .= " " . $id . " => ['country' => '" . addslashes($data['country']) . "', 'taxes' => " . $data['taxes'] . "],\n";
|
||||
}
|
||||
$fileContent .= "];\n";
|
||||
|
||||
//Write to settings/countries.php
|
||||
$filePath = dirname(__FILE__, 2) . '/settings/countries.php';
|
||||
$result = file_put_contents($filePath, $fileContent);
|
||||
|
||||
return ($result !== false);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
BIN
assets/images/.DS_Store
vendored
BIN
assets/images/.DS_Store
vendored
Binary file not shown.
@@ -10,6 +10,26 @@ let deviceVersion = "";
|
||||
let deviceHwVersion = "";
|
||||
let selectedSoftwareUrl = "";
|
||||
|
||||
// Helper function to generate country select options
|
||||
function generateCountryOptions(selectedCountry = '') {
|
||||
if (typeof COUNTRIES === 'undefined' || !COUNTRIES) {
|
||||
return `<option value="">${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}</option>`;
|
||||
}
|
||||
|
||||
// Sort countries alphabetically
|
||||
const sortedCountries = Object.values(COUNTRIES).sort((a, b) => {
|
||||
return a.country.localeCompare(b.country);
|
||||
});
|
||||
|
||||
let options = '<option value="">Select country</option>';
|
||||
sortedCountries.forEach(data => {
|
||||
const selected = (selectedCountry === data.country) ? 'selected' : '';
|
||||
options += `<option value="${data.country}" ${selected}>${data.country}</option>`;
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP)
|
||||
let reader;
|
||||
let readableStreamClosed;
|
||||
@@ -528,8 +548,18 @@ async function fetchSoftwareOptions() {
|
||||
document.getElementById("softwareOptionsContainer").style.display = "block";
|
||||
progressBar("100", "Software options loaded", "#04AA6D");
|
||||
|
||||
// Show user info modal immediately
|
||||
showUserInfoModal();
|
||||
// Show user info modal immediately (skip in debug mode)
|
||||
if (typeof DEBUG === 'undefined' || !DEBUG) {
|
||||
showUserInfoModal();
|
||||
} else {
|
||||
// In debug mode, reveal software options immediately
|
||||
const softwareOptions = document.getElementById("softwareOptions");
|
||||
if (softwareOptions) {
|
||||
softwareOptions.style.filter = "none";
|
||||
softwareOptions.style.opacity = "1";
|
||||
softwareOptions.style.pointerEvents = "auto";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await logCommunication(`Software options error: ${error.message}`, 'error');
|
||||
@@ -665,7 +695,7 @@ function displaySoftwareOptions(options) {
|
||||
} else {
|
||||
priceText.innerHTML = isFree
|
||||
? 'Free'
|
||||
: `${option.currency || "€"} ${price.toFixed(2)}`;
|
||||
: `${option.currency || "€"} ${price.toFixed(2)} <small style="font-size: 12px; font-weight: 400; color: #888;">(excl. VAT)</small>`;
|
||||
}
|
||||
|
||||
priceSection.appendChild(priceText);
|
||||
@@ -777,7 +807,9 @@ function showUserInfoModal() {
|
||||
<input type="text" name="city" id="userInfoCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 10px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<input type="text" name="postal" id="userInfoPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
<input type="text" name="country" id="userInfoCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
<select name="country" id="userInfoCountry" required style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
${generateCountryOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -967,7 +999,9 @@ function showFreeInstallModal(option) {
|
||||
<input type="text" name="city" id="freeInstallCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<input type="text" name="postal" id="freeInstallPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<input type="text" name="country" id="freeInstallCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<select name="country" id="freeInstallCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
${generateCountryOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1045,6 +1079,17 @@ function showPaymentModal(option) {
|
||||
const price = parseFloat(option.price || 0);
|
||||
const currency = option.currency || "€";
|
||||
|
||||
// Format description as bullet points
|
||||
const formatDescription = (desc) => {
|
||||
if (!desc) return '';
|
||||
// Split by bullet points or newlines and filter out empty lines
|
||||
const lines = desc.split(/[•·\n]/).map(line => line.trim()).filter(line => line.length > 0);
|
||||
if (lines.length <= 1) return desc; // Return as-is if no multiple lines
|
||||
return '<ul style="margin: 0; padding-left: 20px; color: #666; font-size: 13px; line-height: 1.6;">' +
|
||||
lines.map(line => `<li>${line}</li>`).join('') +
|
||||
'</ul>';
|
||||
};
|
||||
|
||||
// Create modal overlay
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "paymentModal";
|
||||
@@ -1082,9 +1127,20 @@ function showPaymentModal(option) {
|
||||
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4>
|
||||
<p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p>
|
||||
<p style="margin: 0 0 15px 0; color: #666;">${option.description || ""}</p>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #04AA6D;">
|
||||
${currency} ${price.toFixed(2)}
|
||||
<div style="margin: 0 0 15px 0;">${formatDescription(option.description)}</div>
|
||||
<div id="priceDisplay" style="font-size: 14px; color: #666;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
||||
<span>Price (excl. VAT):</span>
|
||||
<span style="font-weight: 600;">${currency} ${price.toFixed(2)}</span>
|
||||
</div>
|
||||
<div id="taxDisplay" style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
||||
<span>VAT:</span>
|
||||
<span style="font-weight: 600;">-</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding-top: 10px; border-top: 2px solid #ddd; margin-top: 10px;">
|
||||
<span style="font-weight: bold;">Total:</span>
|
||||
<span id="totalDisplay" style="font-size: 24px; font-weight: bold; color: #04AA6D;">${currency} ${price.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1105,7 +1161,9 @@ function showPaymentModal(option) {
|
||||
<input type="text" name="city" id="paymentCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<input type="text" name="postal" id="paymentPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<input type="text" name="country" id="paymentCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<select name="country" id="paymentCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
${generateCountryOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1134,6 +1192,45 @@ function showPaymentModal(option) {
|
||||
modal.appendChild(modalContent);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Function to calculate and update tax
|
||||
function updateTaxDisplay() {
|
||||
const selectedCountry = document.getElementById("paymentCountry").value;
|
||||
let taxRate = 0;
|
||||
|
||||
if (selectedCountry && typeof COUNTRIES !== 'undefined' && COUNTRIES) {
|
||||
const countryData = Object.values(COUNTRIES).find(c => c.country === selectedCountry);
|
||||
if (countryData) {
|
||||
taxRate = parseFloat(countryData.taxes) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const taxAmount = price * (taxRate / 100);
|
||||
const totalAmount = price + taxAmount;
|
||||
|
||||
// Update display
|
||||
const taxDisplay = document.getElementById("taxDisplay");
|
||||
const totalDisplay = document.getElementById("totalDisplay");
|
||||
|
||||
if (taxRate > 0) {
|
||||
taxDisplay.innerHTML = `
|
||||
<span>VAT (${taxRate}%):</span>
|
||||
<span style="font-weight: 600;">${currency} ${taxAmount.toFixed(2)}</span>
|
||||
`;
|
||||
} else {
|
||||
taxDisplay.innerHTML = `
|
||||
<span>VAT:</span>
|
||||
<span style="font-weight: 600;">-</span>
|
||||
`;
|
||||
}
|
||||
|
||||
totalDisplay.textContent = `${currency} ${totalAmount.toFixed(2)}`;
|
||||
|
||||
// Store tax info for form submission
|
||||
modal.taxRate = taxRate;
|
||||
modal.taxAmount = taxAmount;
|
||||
modal.totalAmount = totalAmount;
|
||||
}
|
||||
|
||||
// Prefill form with customer data from sessionStorage if available
|
||||
const savedCustomerData = sessionStorage.getItem('customerData');
|
||||
if (savedCustomerData) {
|
||||
@@ -1144,12 +1241,18 @@ function showPaymentModal(option) {
|
||||
if (customerData.address) document.getElementById("paymentAddress").value = customerData.address;
|
||||
if (customerData.city) document.getElementById("paymentCity").value = customerData.city;
|
||||
if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal;
|
||||
if (customerData.country) document.getElementById("paymentCountry").value = customerData.country;
|
||||
if (customerData.country) {
|
||||
document.getElementById("paymentCountry").value = customerData.country;
|
||||
updateTaxDisplay(); // Calculate tax based on saved country
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error parsing saved customer data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listener to country select to update tax
|
||||
document.getElementById("paymentCountry").addEventListener('change', updateTaxDisplay);
|
||||
|
||||
// Close modal on cancel
|
||||
document.getElementById("cancelPayment").onclick = () => {
|
||||
document.body.removeChild(modal);
|
||||
@@ -1163,9 +1266,9 @@ function showPaymentModal(option) {
|
||||
|
||||
// Auto-determine payment provider based on payment method
|
||||
let paymentProvider = 'mollie'; // default
|
||||
if (paymentMethod === 'paypal') {
|
||||
if (paymentMethod === '3') { // PayPal payment method ID
|
||||
paymentProvider = 'paypal';
|
||||
} else if (paymentMethod === 'credit_card' || paymentMethod === 'bank_transfer') {
|
||||
} else if (paymentMethod === '1' || paymentMethod === 'bank_transfer') { // Mollie (Credit Card) or Bank Transfer
|
||||
paymentProvider = 'mollie';
|
||||
}
|
||||
|
||||
@@ -1179,7 +1282,9 @@ function showPaymentModal(option) {
|
||||
payment_method: paymentMethod,
|
||||
payment_provider: paymentProvider,
|
||||
version_id: option.version_id,
|
||||
price: price,
|
||||
item_price: price, // Price without VAT
|
||||
tax_amount: modal.taxAmount || 0, // Tax amount
|
||||
payment_amount: modal.totalAmount || price, // Total price including tax
|
||||
currency: currency
|
||||
};
|
||||
|
||||
|
||||
0
custom/morvalwatches/style/VeLiTi-Logo2.png
Executable file → Normal file
0
custom/morvalwatches/style/VeLiTi-Logo2.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -441,7 +441,7 @@ $view .= '<div class="content-block">
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width:25%;">'.$general_updated.'</td>
|
||||
<td>'.getRelativeTime($responses->updated).'</td>
|
||||
<td><a href="#" onclick="openLogModal(); return false;" class="link-with-icon">'.getRelativeTime($responses->updated).'<i class="fa-solid fa-up-right-from-square"></i></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -704,6 +704,32 @@ if ($latest_customer || $latest_warranty) {
|
||||
';
|
||||
}
|
||||
|
||||
// Modal for log
|
||||
echo '
|
||||
<div id="logModal" class="reg-modal" style="display:none;">
|
||||
<div class="reg-modal-content" style="min-width:600px;">
|
||||
<div class="reg-modal-header">
|
||||
<h3>Log</h3>
|
||||
<button onclick="closeLogModal()" class="reg-modal-close">×</button>
|
||||
</div>
|
||||
<div id="logModalBody" style="max-height:400px;overflow:auto;">
|
||||
<div id="registrationTab" class="reg-tab-content" style="display: block;">
|
||||
'.showlog("equipment", "'.$responses->equipmentID.'").'
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function openLogModal() {
|
||||
document.getElementById("logModal").style.display = "flex";
|
||||
|
||||
}
|
||||
function closeLogModal() {
|
||||
document.getElementById("logModal").style.display = "none";
|
||||
}
|
||||
</script>
|
||||
';
|
||||
|
||||
template_footer()
|
||||
|
||||
?>
|
||||
@@ -87,11 +87,12 @@ if (isset($_GET['equipmentID'])) {
|
||||
$data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
|
||||
//API call
|
||||
$responses = ioServer('/v2/equipments', $data);
|
||||
|
||||
if ($responses === 'NOK'){
|
||||
|
||||
} else {
|
||||
header('Location: index.php?page=equipment&equipmentID='.$equipment_ID.'&success_msg=2');
|
||||
exit;
|
||||
header('Location: index.php?page=equipment&equipmentID='.$equipment_ID.'&success_msg=2');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ $responses = ioServer($api_url,'');
|
||||
//Decode Payload
|
||||
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
|
||||
|
||||
// Redirect if only one equipment is found
|
||||
if (is_array($responses) && count($responses) === 1 && isset($responses[0]->equipmentID)) {
|
||||
header('Location: index.php?page=equipment&equipmentID=' . $responses[0]->equipmentID);
|
||||
exit;
|
||||
}
|
||||
|
||||
//Return QueryTotal from API
|
||||
$total_url = ((!empty($GET_VALUES) && $GET_VALUES !='') ? '&totals=' : 'totals=' );
|
||||
$api_url = '/v2/equipments/'.$GET_VALUES.$total_url;
|
||||
|
||||
12
index.php
12
index.php
@@ -15,9 +15,21 @@ if (debug && debug_id == $_SESSION['id']){
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
if (debug){
|
||||
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
||||
debuglog("PHP ERROR [$errno]: $errstr in $errfile on line $errline");
|
||||
return false;
|
||||
});
|
||||
|
||||
set_exception_handler(function($exception) {
|
||||
debuglog("PHP EXCEPTION: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
|
||||
});
|
||||
}
|
||||
|
||||
//INCLUDE FUNCTIONS AND SETTINGS
|
||||
include dirname(__FILE__).'/assets/functions.php';
|
||||
include dirname(__FILE__).'/settings/settings_redirector.php';
|
||||
include_once dirname(__FILE__).'/settings/countries.php';
|
||||
|
||||
//=====================================
|
||||
//TRANSLATION FILE LOCATION
|
||||
|
||||
@@ -40,6 +40,9 @@ if ($update_allowed === 1){
|
||||
if (isset($_POST['generateDealerInformation'])){
|
||||
generateDealerInformation($_SESSION['userkey']);
|
||||
}
|
||||
if (isset($_POST['generateCountriesFile'])){
|
||||
generateCountriesFile($_SESSION['userkey']);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle success messages
|
||||
@@ -91,6 +94,10 @@ $view .= '<div class="content-block tab-content active">
|
||||
<label for="service">GenerateDealerInfo</label>
|
||||
<input type="submit" name="generateDealerInformation" style="width: 15%;" value="DealerInfo" class="btn">
|
||||
</div>
|
||||
<div class="form responsive-width-100">
|
||||
<label for="service">Generate Countries File</label>
|
||||
<input type="submit" name="generateCountriesFile" style="width: 15%;" value="Countries" class="btn">
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
@@ -186,7 +186,8 @@ $view .= '<div class="content-block">
|
||||
<div class="form responsive-width-100">
|
||||
<label for="from_version_id"><i class="required">*</i>From Version</label>
|
||||
<select id="from_version_id" name="from_version_id" required>
|
||||
<option value="">Select From Version</option>';
|
||||
<option value="">Select From Version</option>
|
||||
<option value="9999999"'. ($path['from_version_id'] == 9999999 ? ' selected' : '') .'>Any Version (*)</option>';
|
||||
if (!empty($versions)) {
|
||||
foreach ($versions as $ver) {
|
||||
// Skip the TO version from FROM dropdown to prevent FROM = TO
|
||||
|
||||
14
register.php
14
register.php
@@ -9,6 +9,7 @@ $lang = in_array($lang, $supportedLanguages) ? $lang : 'US';
|
||||
|
||||
//INCLUDE THE TRANSLATION
|
||||
include_once './settings/translations/translations_'.$lang.'.php';
|
||||
include_once './settings/countries.php';
|
||||
|
||||
//=========================================
|
||||
//GET DOMAIN FOR CORRECT STYLING AND SETTINGS
|
||||
@@ -537,7 +538,18 @@ echo'
|
||||
<input type="text" name="city" form="regForm" placeholder="'.$register_3_city.'" value="'.$register['city'].'" required="" class="form-field">
|
||||
|
||||
<label for="country" class="form-label">'.$register_3_country.' *</label>
|
||||
<input type="text" name="country" form="regForm" placeholder="'.$register_3_country.'" value="'.$register['country'].'" required="" class="form-field">
|
||||
<select name="country" form="regForm" required="" class="form-field">';
|
||||
// Sort countries alphabetically
|
||||
usort($countries, function($a, $b) {
|
||||
return strcmp($a['country'], $b['country']);
|
||||
});
|
||||
|
||||
foreach ($countries as $id => $data) {
|
||||
$selected = ($register['country'] == $data['country']) ? 'selected' : '';
|
||||
echo '<option value="' . $data['country'] . '" ' . $selected . '>' . $data['country'] . '</option>';
|
||||
}
|
||||
echo '
|
||||
</select>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<input type="checkbox" name="email_consent" form="regForm" checked required/>'.$register_3_email_consent.'<br>
|
||||
|
||||
148
settings/countries.php
Normal file
148
settings/countries.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
// Auto-generated countries file from taxes API
|
||||
// Generated on: 2026-01-16 14:21:38
|
||||
|
||||
$countries = [
|
||||
1 => ['country' => 'Austria', 'taxes' => 20.00],
|
||||
2 => ['country' => 'Belgium', 'taxes' => 21.00],
|
||||
3 => ['country' => 'Bulgaria', 'taxes' => 20.00],
|
||||
4 => ['country' => 'Croatia', 'taxes' => 25.00],
|
||||
5 => ['country' => 'Cyprus', 'taxes' => 19.00],
|
||||
6 => ['country' => 'Czech Republic', 'taxes' => 21.00],
|
||||
7 => ['country' => 'Denmark', 'taxes' => 25.00],
|
||||
8 => ['country' => 'Estonia', 'taxes' => 24.00],
|
||||
9 => ['country' => 'Finland', 'taxes' => 25.50],
|
||||
10 => ['country' => 'France', 'taxes' => 20.00],
|
||||
11 => ['country' => 'Germany', 'taxes' => 19.00],
|
||||
12 => ['country' => 'Greece', 'taxes' => 24.00],
|
||||
13 => ['country' => 'Hungary', 'taxes' => 27.00],
|
||||
14 => ['country' => 'Ireland', 'taxes' => 23.00],
|
||||
15 => ['country' => 'Italy', 'taxes' => 22.00],
|
||||
16 => ['country' => 'Latvia', 'taxes' => 21.00],
|
||||
17 => ['country' => 'Lithuania', 'taxes' => 21.00],
|
||||
18 => ['country' => 'Luxembourg', 'taxes' => 16.00],
|
||||
19 => ['country' => 'Malta', 'taxes' => 18.00],
|
||||
20 => ['country' => 'Netherlands', 'taxes' => 21.00],
|
||||
21 => ['country' => 'Poland', 'taxes' => 23.00],
|
||||
22 => ['country' => 'Portugal', 'taxes' => 23.00],
|
||||
23 => ['country' => 'Romania', 'taxes' => 19.00],
|
||||
24 => ['country' => 'Slovakia', 'taxes' => 23.00],
|
||||
25 => ['country' => 'Slovenia', 'taxes' => 22.00],
|
||||
26 => ['country' => 'Spain', 'taxes' => 21.00],
|
||||
27 => ['country' => 'Sweden', 'taxes' => 25.00],
|
||||
28 => ['country' => 'United Kingdom', 'taxes' => 20.00],
|
||||
29 => ['country' => 'Switzerland', 'taxes' => 8.10],
|
||||
30 => ['country' => 'Norway', 'taxes' => 25.00],
|
||||
31 => ['country' => 'Iceland', 'taxes' => 24.00],
|
||||
32 => ['country' => 'Albania', 'taxes' => 20.00],
|
||||
33 => ['country' => 'Serbia', 'taxes' => 20.00],
|
||||
34 => ['country' => 'North Macedonia', 'taxes' => 18.00],
|
||||
35 => ['country' => 'Bosnia and Herzegovina', 'taxes' => 17.00],
|
||||
36 => ['country' => 'Montenegro', 'taxes' => 21.00],
|
||||
37 => ['country' => 'Moldova', 'taxes' => 20.00],
|
||||
38 => ['country' => 'Ukraine', 'taxes' => 20.00],
|
||||
39 => ['country' => 'Belarus', 'taxes' => 20.00],
|
||||
40 => ['country' => 'Turkey', 'taxes' => 20.00],
|
||||
41 => ['country' => 'Andorra', 'taxes' => 4.50],
|
||||
42 => ['country' => 'Australia', 'taxes' => 10.00],
|
||||
43 => ['country' => 'New Zealand', 'taxes' => 15.00],
|
||||
44 => ['country' => 'Japan', 'taxes' => 10.00],
|
||||
45 => ['country' => 'China', 'taxes' => 13.00],
|
||||
46 => ['country' => 'India', 'taxes' => 18.00],
|
||||
47 => ['country' => 'South Korea', 'taxes' => 10.00],
|
||||
48 => ['country' => 'Singapore', 'taxes' => 9.00],
|
||||
49 => ['country' => 'Indonesia', 'taxes' => 11.00],
|
||||
50 => ['country' => 'Thailand', 'taxes' => 7.00],
|
||||
51 => ['country' => 'Vietnam', 'taxes' => 8.00],
|
||||
52 => ['country' => 'Philippines', 'taxes' => 12.00],
|
||||
53 => ['country' => 'Malaysia', 'taxes' => 0.00],
|
||||
54 => ['country' => 'Taiwan', 'taxes' => 5.00],
|
||||
55 => ['country' => 'Pakistan', 'taxes' => 18.00],
|
||||
56 => ['country' => 'Bangladesh', 'taxes' => 15.00],
|
||||
57 => ['country' => 'Sri Lanka', 'taxes' => 18.00],
|
||||
58 => ['country' => 'Nepal', 'taxes' => 13.00],
|
||||
59 => ['country' => 'Cambodia', 'taxes' => 10.00],
|
||||
60 => ['country' => 'Myanmar', 'taxes' => 5.00],
|
||||
61 => ['country' => 'Laos', 'taxes' => 10.00],
|
||||
62 => ['country' => 'Mongolia', 'taxes' => 10.00],
|
||||
63 => ['country' => 'Kazakhstan', 'taxes' => 12.00],
|
||||
64 => ['country' => 'Uzbekistan', 'taxes' => 12.00],
|
||||
65 => ['country' => 'Armenia', 'taxes' => 20.00],
|
||||
66 => ['country' => 'Georgia', 'taxes' => 18.00],
|
||||
67 => ['country' => 'Azerbaijan', 'taxes' => 18.00],
|
||||
68 => ['country' => 'Fiji', 'taxes' => 9.00],
|
||||
69 => ['country' => 'Papua New Guinea', 'taxes' => 10.00],
|
||||
70 => ['country' => 'Samoa', 'taxes' => 15.00],
|
||||
71 => ['country' => 'Tonga', 'taxes' => 15.00],
|
||||
72 => ['country' => 'Vanuatu', 'taxes' => 15.00],
|
||||
73 => ['country' => 'Bhutan', 'taxes' => 7.00],
|
||||
74 => ['country' => 'Saudi Arabia', 'taxes' => 15.00],
|
||||
75 => ['country' => 'United Arab Emirates', 'taxes' => 5.00],
|
||||
76 => ['country' => 'Bahrain', 'taxes' => 10.00],
|
||||
77 => ['country' => 'Kuwait', 'taxes' => 0.00],
|
||||
78 => ['country' => 'Oman', 'taxes' => 5.00],
|
||||
79 => ['country' => 'Qatar', 'taxes' => 0.00],
|
||||
80 => ['country' => 'Israel', 'taxes' => 17.00],
|
||||
81 => ['country' => 'Jordan', 'taxes' => 16.00],
|
||||
82 => ['country' => 'Lebanon', 'taxes' => 11.00],
|
||||
83 => ['country' => 'Egypt', 'taxes' => 14.00],
|
||||
85 => ['country' => 'South Africa', 'taxes' => 15.00],
|
||||
86 => ['country' => 'Nigeria', 'taxes' => 7.50],
|
||||
87 => ['country' => 'Kenya', 'taxes' => 16.00],
|
||||
88 => ['country' => 'Ghana', 'taxes' => 15.00],
|
||||
89 => ['country' => 'Morocco', 'taxes' => 20.00],
|
||||
90 => ['country' => 'Tunisia', 'taxes' => 19.00],
|
||||
91 => ['country' => 'Algeria', 'taxes' => 19.00],
|
||||
92 => ['country' => 'Egypt', 'taxes' => 14.00],
|
||||
93 => ['country' => 'Ethiopia', 'taxes' => 15.00],
|
||||
94 => ['country' => 'Tanzania', 'taxes' => 18.00],
|
||||
95 => ['country' => 'Uganda', 'taxes' => 18.00],
|
||||
96 => ['country' => 'Zimbabwe', 'taxes' => 15.00],
|
||||
97 => ['country' => 'Zambia', 'taxes' => 16.00],
|
||||
98 => ['country' => 'Botswana', 'taxes' => 14.00],
|
||||
99 => ['country' => 'Mauritius', 'taxes' => 15.00],
|
||||
100 => ['country' => 'Namibia', 'taxes' => 15.00],
|
||||
101 => ['country' => 'Rwanda', 'taxes' => 18.00],
|
||||
102 => ['country' => 'Senegal', 'taxes' => 18.00],
|
||||
103 => ['country' => 'Ivory Coast', 'taxes' => 18.00],
|
||||
104 => ['country' => 'Cameroon', 'taxes' => 19.25],
|
||||
105 => ['country' => 'Angola', 'taxes' => 14.00],
|
||||
106 => ['country' => 'Mozambique', 'taxes' => 16.00],
|
||||
107 => ['country' => 'Madagascar', 'taxes' => 20.00],
|
||||
108 => ['country' => 'Mali', 'taxes' => 18.00],
|
||||
109 => ['country' => 'Burkina Faso', 'taxes' => 18.00],
|
||||
110 => ['country' => 'Niger', 'taxes' => 19.00],
|
||||
111 => ['country' => 'Benin', 'taxes' => 18.00],
|
||||
112 => ['country' => 'Togo', 'taxes' => 18.00],
|
||||
113 => ['country' => 'Guinea', 'taxes' => 18.00],
|
||||
114 => ['country' => 'Malawi', 'taxes' => 16.50],
|
||||
115 => ['country' => 'Gabon', 'taxes' => 18.00],
|
||||
116 => ['country' => 'Mauritania', 'taxes' => 16.00],
|
||||
117 => ['country' => 'Lesotho', 'taxes' => 15.00],
|
||||
118 => ['country' => 'Eswatini', 'taxes' => 15.00],
|
||||
119 => ['country' => 'Liberia', 'taxes' => 18.00],
|
||||
120 => ['country' => 'Canada', 'taxes' => 5.00],
|
||||
121 => ['country' => 'United States', 'taxes' => 10.00],
|
||||
122 => ['country' => 'Mexico', 'taxes' => 16.00],
|
||||
123 => ['country' => 'Argentina', 'taxes' => 21.00],
|
||||
124 => ['country' => 'Brazil', 'taxes' => 17.00],
|
||||
125 => ['country' => 'Chile', 'taxes' => 19.00],
|
||||
126 => ['country' => 'Colombia', 'taxes' => 19.00],
|
||||
127 => ['country' => 'Peru', 'taxes' => 18.00],
|
||||
128 => ['country' => 'Ecuador', 'taxes' => 15.00],
|
||||
129 => ['country' => 'Uruguay', 'taxes' => 22.00],
|
||||
130 => ['country' => 'Paraguay', 'taxes' => 10.00],
|
||||
131 => ['country' => 'Bolivia', 'taxes' => 13.00],
|
||||
132 => ['country' => 'Venezuela', 'taxes' => 16.00],
|
||||
133 => ['country' => 'Costa Rica', 'taxes' => 13.00],
|
||||
134 => ['country' => 'Panama', 'taxes' => 7.00],
|
||||
135 => ['country' => 'Guatemala', 'taxes' => 12.00],
|
||||
136 => ['country' => 'Honduras', 'taxes' => 15.00],
|
||||
137 => ['country' => 'El Salvador', 'taxes' => 13.00],
|
||||
138 => ['country' => 'Nicaragua', 'taxes' => 15.00],
|
||||
139 => ['country' => 'Dominican Republic', 'taxes' => 18.00],
|
||||
140 => ['country' => 'Jamaica', 'taxes' => 15.00],
|
||||
141 => ['country' => 'Trinidad and Tobago', 'taxes' => 12.50],
|
||||
142 => ['country' => 'Barbados', 'taxes' => 17.50],
|
||||
143 => ['country' => 'Bahamas', 'taxes' => 10.00],
|
||||
];
|
||||
@@ -22,6 +22,24 @@ $payment_return = isset($_GET['order_id']) ? $_GET['order_id'] : null;
|
||||
$payment_return_status = isset($_GET['payment_return']) ? $_GET['payment_return'] : null;
|
||||
$paypal_token = isset($_GET['token']) ? $_GET['token'] : null; // PayPal returns with ?token=
|
||||
|
||||
// Handle payment cancellation - mark order as cancelled
|
||||
if ($payment_return && $payment_return_status === 'cancelled') {
|
||||
try {
|
||||
$pdo = dbConnect($dbname);
|
||||
$sql = 'UPDATE transactions SET payment_status = 999 WHERE txn_id = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$payment_return]);
|
||||
|
||||
if (debug) {
|
||||
debuglog("Payment cancelled - Order ID: {$payment_return} marked as cancelled (999)");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if (debug) {
|
||||
debuglog("Error marking order as cancelled: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle PayPal return - capture the order directly
|
||||
if ($paypal_token && $payment_return) {
|
||||
try {
|
||||
@@ -134,8 +152,23 @@ if ($payment_return && $payment_return_status) {
|
||||
// Auto-refresh every 3 seconds to check payment status
|
||||
setTimeout(function() { location.reload(); }, 3000);
|
||||
</script>';
|
||||
} else if ($transaction['payment_status'] == 999) {
|
||||
// Payment 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-times-circle" style="font-size: 64px; color: #ffc107; margin-bottom: 20px;"></i>
|
||||
<h2 style="color: #856404; margin-bottom: 15px;">Payment Cancelled</h2>
|
||||
<p style="margin-bottom: 10px; color: #333;">You cancelled the payment. The order has been cancelled.</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>';
|
||||
} else {
|
||||
// Payment failed/cancelled
|
||||
// Payment failed/expired
|
||||
$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;">
|
||||
@@ -298,6 +331,9 @@ echo '
|
||||
var TRANS_USER_INFO_DESCRIPTION = "'.($user_information_description ?? 'Please provide your information to continue with software updates').'";
|
||||
var TRANS_CONTINUE = "'.($general_continue ?? 'Continue').'";
|
||||
|
||||
// Countries data
|
||||
var COUNTRIES = '.json_encode($countries ?? []).';
|
||||
|
||||
var port, textEncoder, writableStreamClosed, writer, historyIndex = -1;
|
||||
const lineHistory = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user