- Implemented PayPal webhook for handling payment notifications, including signature verification and transaction updates. - Created invoice generation and license management for software upgrades upon successful payment. - Added comprehensive logging for debugging purposes. - Introduced new CSS styles for the marketing file management system, including layout, toolbar, breadcrumb navigation, search filters, and file management UI components.
298 lines
14 KiB
PHP
298 lines
14 KiB
PHP
<?php
|
|
// NEW FILE - Webhook for software upgrade payments
|
|
// Based on structure from existing webhook.php from commerce product
|
|
// Uses existing transaction API + invoice API + email system
|
|
|
|
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)
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
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
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
$base_url = 'https://'.$_SERVER['SERVER_NAME'].'/';
|
|
define('base_url', $base_url);
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// Initialize DomPDF for invoice generation
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
use Dompdf\Dompdf;
|
|
use Dompdf\Options;
|
|
$options = new Options();
|
|
$options->set('isRemoteEnabled', true);
|
|
$dompdf = new Dompdf($options);
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// Language mapping for invoices
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
$available_languages = [
|
|
'NL' => 'NL',
|
|
'BE' => 'NL',
|
|
'US' => 'US',
|
|
'GB' => 'US',
|
|
'DE' => 'DE',
|
|
'FR' => 'FR',
|
|
'ES' => 'ES'
|
|
];
|
|
|
|
try {
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// Initialize the Mollie API library
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
require "initialize.php"; // Mollie initialization
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// DEBUG MODE - Handle fake payment IDs
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
if (debug && isset($_POST["id"]) && strpos($_POST["id"], 'DEBUG_') === 0) {
|
|
// In DEBUG mode, simulate webhook callback
|
|
$payment_id = $_POST["id"];
|
|
$orderId = $payment_id;
|
|
|
|
// Simulate payment status as paid
|
|
$payment_status = 1; // Paid
|
|
|
|
} else {
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// PRODUCTION MODE - Retrieve the payment's current state from Mollie
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
$payment = $mollie->payments->get($_POST["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()) {
|
|
$payment_status = 1; // Paid
|
|
} elseif ($payment->isOpen() || $payment->isPending()) {
|
|
$payment_status = 101; // Open/Pending
|
|
} elseif ($payment->isFailed()) {
|
|
$payment_status = 102; // Failed
|
|
} elseif ($payment->isExpired()) {
|
|
$payment_status = 103; // Expired
|
|
} elseif ($payment->isCanceled()) {
|
|
$payment_status = 999; // Canceled
|
|
}
|
|
}
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// Update transaction status directly in database
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
if ($payment_status !== null) {
|
|
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)) {
|
|
debuglog("WEBHOOK: Payment is PAID, processing license...");
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// CREATE LICENSE for software upgrade
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
$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([$transaction['id']]);
|
|
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($items as $item) {
|
|
if (!empty($item['item_options'])) {
|
|
$options = json_decode($item['item_options'], true);
|
|
|
|
// Check if this is a software upgrade (has serial_number and equipment_id)
|
|
if (isset($options['serial_number']) && isset($options['equipment_id'])) {
|
|
|
|
// Check if license already exists for this transaction
|
|
$sql = 'SELECT rowID FROM products_software_licenses WHERE transaction_id = ?';
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute([$orderId]);
|
|
$existing_license = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$existing_license) {
|
|
// Generate unique license key
|
|
$license_key = generateUniqueLicenseKey();
|
|
|
|
// 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([
|
|
$item['item_id'], // version_id
|
|
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
|
|
$orderId,
|
|
date('Y-m-d H:i:s'),
|
|
'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 directly in database
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
// 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();
|
|
} else {
|
|
$invoice_id = $existing_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);
|
|
|
|
if (!empty($invoice_data)) {
|
|
// Transform the data (group items like the API does)
|
|
$invoice_cust = transformOrderData($invoice_data);
|
|
|
|
// Determine invoice language
|
|
if (!empty($invoice_cust['customer']['language'])) {
|
|
$invoice_language = strtoupper($invoice_cust['customer']['language']);
|
|
} elseif (!empty($invoice_cust['customer']['country']) && isset($available_languages[strtoupper($invoice_cust['customer']['country'])])) {
|
|
$invoice_language = $available_languages[strtoupper($invoice_cust['customer']['country'])];
|
|
} else {
|
|
$invoice_language = 'US'; // Default fallback
|
|
}
|
|
|
|
// Generate invoice HTML (using custom template for software upgrades)
|
|
list($data,$customer_email,$order_id) = generateSoftwareInvoice($invoice_cust,$orderId,$invoice_language);
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
//CREATE PDF using DomPDF
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
$dompdf->loadHtml($data);
|
|
$dompdf->setPaper('A4', 'portrait');
|
|
$dompdf->render();
|
|
$subject = 'Software Upgrade - Invoice: '.$order_id;
|
|
$attachment = $dompdf->output();
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
//Send email via PHPMailer
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// The send_mail function will exit on error and debuglog the error
|
|
$mail_result = send_mail($customer_email, $subject, $data, $attachment, $subject);
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return 200 OK to Mollie
|
|
http_response_code(200);
|
|
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);
|
|
}
|
|
|
|
?>
|