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:
@@ -5,14 +5,22 @@
|
||||
|
||||
require_once 'settings/config_redirector.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)
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$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,'');
|
||||
debuglog("WEBHOOK: Authorization response: " . $responses);
|
||||
if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';}
|
||||
$clientsecret = $responses['token'];
|
||||
debuglog("WEBHOOK: Token obtained: " . ($clientsecret ? 'YES' : 'NO'));
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// BASEURL is required for invoice template
|
||||
@@ -64,7 +72,16 @@ try {
|
||||
// PRODUCTION MODE - Retrieve the payment's current state from Mollie
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$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;
|
||||
|
||||
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) {
|
||||
$payload = json_encode(array("txn_id" => $orderId, "payment_status" => $payment_status), JSON_UNESCAPED_UNICODE);
|
||||
$transaction = ioAPIv2('/v2/transactions/',$payload,$clientsecret);
|
||||
$transaction = json_decode($transaction,true);
|
||||
debuglog("WEBHOOK: Order ID: $orderId, Payment Status: $payment_status");
|
||||
|
||||
$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
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if ($payment_status == 1 && $transaction !== null && !empty($transaction)) {
|
||||
if(count($transaction) > 0) {
|
||||
debuglog("WEBHOOK: Payment is PAID, processing license...");
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// CREATE LICENSE for software upgrade
|
||||
@@ -100,9 +132,10 @@ try {
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
// 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 = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$orderId]);
|
||||
$stmt->execute([$transaction['id']]);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($items as $item) {
|
||||
@@ -124,14 +157,14 @@ try {
|
||||
|
||||
// Create license
|
||||
$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 (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$item['item_id'], // version_id
|
||||
1, // license_type (1 = upgrade)
|
||||
$license_key,
|
||||
$options['equipment_id'],
|
||||
'upgrade',
|
||||
1, // active
|
||||
1, // status = active
|
||||
date('Y-m-d H:i:s'),
|
||||
'2099-12-31 23:59:59', // effectively permanent
|
||||
$orderId,
|
||||
@@ -139,26 +172,79 @@ try {
|
||||
'webhook' // created by webhook
|
||||
]);
|
||||
|
||||
debuglog("WEBHOOK: License created: $license_key");
|
||||
|
||||
// Update equipment.sw_version_license
|
||||
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$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)) {
|
||||
// Fetch full invoice data with customer details
|
||||
$invoice_cust = ioAPIv2('/v2/invoice/list=invoice&id='.$invoice['invoice_id'],'',$clientsecret);
|
||||
$invoice_cust = json_decode($invoice_cust,true);
|
||||
// Check if invoice already exists for this transaction
|
||||
$sql = 'SELECT id FROM invoice WHERE txn_id = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$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
|
||||
if (!empty($invoice_cust['customer']['language'])) {
|
||||
@@ -170,28 +256,44 @@ try {
|
||||
}
|
||||
|
||||
// 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);
|
||||
debuglog("WEBHOOK: Invoice generated - Customer email: $customer_email, Order ID: $order_id");
|
||||
debuglog("WEBHOOK: Invoice HTML length: " . strlen($data));
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//CREATE PDF using DomPDF
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
debuglog("WEBHOOK: Creating PDF...");
|
||||
$dompdf->loadHtml($data);
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
$subject = 'Software Upgrade - Invoice: '.$order_id;
|
||||
$attachment = $dompdf->output();
|
||||
debuglog("WEBHOOK: PDF created, size: " . strlen($attachment) . " bytes");
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
//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
|
||||
if(invoice_bookkeeping){
|
||||
debuglog("WEBHOOK: Sending to bookkeeping: " . email_bookkeeping);
|
||||
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";
|
||||
|
||||
} 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()));
|
||||
http_response_code(500);
|
||||
} catch (Exception $e) {
|
||||
debuglog("WEBHOOK ERROR (General): " . $e->getMessage());
|
||||
debuglog("WEBHOOK ERROR TRACE: " . $e->getTraceAsString());
|
||||
error_log("Webhook error: " . htmlspecialchars($e->getMessage()));
|
||||
http_response_code(500);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user