Add Mollie API integration and webhook for software upgrade payments

- Introduced the `CaBundle.php` class for managing CA certificates.
- Updated `installed.json` and `installed.php` to include the new `composer/ca-bundle` dependency.
- Added `platform_check.php` to enforce PHP version requirements.
- Created `initialize.php` for initializing the Mollie API client with the API key.
- Implemented `webhook_mollie.php` to handle webhook callbacks for software upgrade payments, including transaction status updates and invoice generation.
- Integrated DomPDF for generating invoices and sending them via email.
This commit is contained in:
“VeLiTi”
2025-12-21 14:44:37 +01:00
parent 653e33d7e9
commit 0f968aac14
159 changed files with 16197 additions and 21 deletions

View File

@@ -5280,7 +5280,7 @@ function translateDeviceHardwareVersion($device_hw_version) {
/**
* Translates hardware version from database to match device format if needed
* This can be used for display or API responses
*
*
* @param string $db_hw_version - Hardware version from database
* @return string - Hardware version (currently returns same as input)
*/
@@ -5288,4 +5288,258 @@ function translateDbHardwareVersion($db_hw_version) {
// For now, we keep the standardized format from DB
// This function exists for future reverse translation if needed
return $db_hw_version;
}
/**
* Generates a unique license key for software upgrades
* Format: XXXX-XXXX-XXXX-XXXX (16 uppercase alphanumeric characters)
*
* @return string - Unique license key
*/
function generateUniqueLicenseKey() {
include dirname(__FILE__,2).'/settings/settings_redirector.php';
$pdo = dbConnect($dbname);
$max_attempts = 10;
$attempt = 0;
do {
// Generate 16 random bytes and convert to uppercase hex
$random_bytes = random_bytes(8); // 8 bytes = 16 hex characters
$hex = strtoupper(bin2hex($random_bytes));
// Format as XXXX-XXXX-XXXX-XXXX
$license_key = substr($hex, 0, 4) . '-' .
substr($hex, 4, 4) . '-' .
substr($hex, 8, 4) . '-' .
substr($hex, 12, 4);
// Check if this key already exists
$sql = 'SELECT COUNT(*) as count FROM products_software_licenses WHERE license_key = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$license_key]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result['count'] == 0) {
return $license_key;
}
$attempt++;
} while ($attempt < $max_attempts);
// Fallback: append timestamp if collision persists (extremely unlikely)
return $license_key . '-' . time();
}
/**
* Generates HTML invoice for software upgrade payments
* Based on existing invoice template but customized for software licenses
*
* @param array $invoice_data - Invoice data from /v2/invoice API (includes customer, transaction, items)
* @param string $order_id - Transaction ID (txn_id)
* @param string $language - Invoice language code (e.g., 'US', 'NL')
* @return array - [$html_content, $customer_email, $order_id]
*/
function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') {
include dirname(__FILE__,2).'/settings/settings_redirector.php';
// Extract customer data
$customer = $invoice_data['customer'] ?? [];
$customer_email = $customer['email'] ?? $invoice_data['payer_email'] ?? '';
$customer_name = trim(($customer['first_name'] ?? '') . ' ' . ($customer['last_name'] ?? ''));
$customer_address = $customer['address_street'] ?? '';
$customer_city = $customer['address_city'] ?? '';
$customer_state = $customer['address_state'] ?? '';
$customer_zip = $customer['address_zip'] ?? '';
$customer_country = $customer['address_country'] ?? '';
// Extract transaction data
$payment_amount = $invoice_data['payment_amount'] ?? 0;
$tax_amount = $invoice_data['tax_amount'] ?? 0;
$shipping_amount = $invoice_data['shipping_amount'] ?? 0;
$discount_amount = $invoice_data['discount_amount'] ?? 0;
$currency = 'EUR'; // Default currency
$invoice_date = $invoice_data['invoice_created'] ?? date('Y-m-d H:i:s');
// Extract item data (software upgrade details)
$items = [];
$serial_number = '';
$software_version = '';
$license_key = '';
if (isset($invoice_data['item_id'])) {
// Single item format from API
$item_options = !empty($invoice_data['item_options']) ? json_decode($invoice_data['item_options'], true) : [];
$serial_number = $item_options['serial_number'] ?? 'N/A';
$software_version = $invoice_data['productname'] ?? 'Software Upgrade';
// Get license key from database
$pdo = dbConnect($dbname);
$sql = 'SELECT license_key FROM products_software_licenses WHERE transaction_id = ? LIMIT 1';
$stmt = $pdo->prepare($sql);
$stmt->execute([$order_id]);
$license_result = $stmt->fetch(PDO::FETCH_ASSOC);
$license_key = $license_result['license_key'] ?? 'Pending';
$items[] = [
'name' => $software_version,
'quantity' => $invoice_data['item_quantity'] ?? 1,
'price' => $invoice_data['item_price'] ?? $payment_amount,
'serial_number' => $serial_number,
'license_key' => $license_key
];
}
// Load language translations
$translations = [];
$translation_file = dirname(__FILE__,2).'/settings/translations/translations_'.$language.'.php';
if (file_exists($translation_file)) {
include $translation_file;
} else {
// Fallback to US English
include dirname(__FILE__,2).'/settings/translations/translations_US.php';
}
// Invoice labels (with fallbacks)
$lbl_invoice = $translations['invoice'] ?? 'Invoice';
$lbl_invoice_number = $translations['invoice_number'] ?? 'Invoice Number';
$lbl_invoice_date = $translations['invoice_date'] ?? 'Invoice Date';
$lbl_customer = $translations['customer'] ?? 'Customer';
$lbl_product = $translations['product'] ?? 'Product';
$lbl_quantity = $translations['quantity'] ?? 'Quantity';
$lbl_price = $translations['price'] ?? 'Price';
$lbl_subtotal = $translations['subtotal'] ?? 'Subtotal';
$lbl_tax = $translations['tax'] ?? 'Tax';
$lbl_shipping = $translations['shipping'] ?? 'Shipping';
$lbl_discount = $translations['discount'] ?? 'Discount';
$lbl_total = $translations['total'] ?? 'Total';
$lbl_device_serial = $translations['device_serial'] ?? 'Device Serial Number';
$lbl_license_key = $translations['license_key'] ?? 'License Key';
$lbl_license_expiry = $translations['license_expiry'] ?? 'License Expiry';
// Build HTML invoice
$html = '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; font-size: 12px; color: #333; }
.invoice-header { margin-bottom: 30px; }
.invoice-title { font-size: 24px; font-weight: bold; margin-bottom: 10px; }
.invoice-info { margin-bottom: 20px; }
.customer-info { margin-bottom: 20px; background: #f5f5f5; padding: 15px; }
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
th { background: #4CAF50; color: white; padding: 10px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #ddd; }
.text-right { text-align: right; }
.total-row { font-weight: bold; background: #f9f9f9; }
.license-info { background: #e3f2fd; padding: 15px; margin-top: 20px; border-left: 4px solid #2196F3; }
.footer { margin-top: 40px; text-align: center; font-size: 10px; color: #666; }
</style>
</head>
<body>
<div class="invoice-header">
<div class="invoice-title">' . htmlspecialchars($lbl_invoice) . '</div>
<div class="invoice-info">
<strong>' . htmlspecialchars($lbl_invoice_number) . ':</strong> ' . htmlspecialchars($order_id) . '<br>
<strong>' . htmlspecialchars($lbl_invoice_date) . ':</strong> ' . htmlspecialchars(date('Y-m-d', strtotime($invoice_date))) . '
</div>
</div>
<div class="customer-info">
<strong>' . htmlspecialchars($lbl_customer) . ':</strong><br>
' . htmlspecialchars($customer_name) . '<br>';
if ($customer_address) {
$html .= htmlspecialchars($customer_address) . '<br>';
}
if ($customer_city || $customer_zip) {
$html .= htmlspecialchars($customer_zip . ' ' . $customer_city) . '<br>';
}
if ($customer_state) {
$html .= htmlspecialchars($customer_state) . '<br>';
}
if ($customer_country) {
$html .= htmlspecialchars($customer_country) . '<br>';
}
$html .= htmlspecialchars($customer_email) . '
</div>
<table>
<thead>
<tr>
<th>' . htmlspecialchars($lbl_product) . '</th>
<th class="text-right">' . htmlspecialchars($lbl_quantity) . '</th>
<th class="text-right">' . htmlspecialchars($lbl_price) . '</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$html .= '<tr>
<td>' . htmlspecialchars($item['name']) . '</td>
<td class="text-right">' . htmlspecialchars($item['quantity']) . '</td>
<td class="text-right">' . number_format($item['price'], 2) . ' ' . htmlspecialchars($currency) . '</td>
</tr>';
}
// Subtotal
$subtotal = $payment_amount - $tax_amount - $shipping_amount + $discount_amount;
$html .= '<tr>
<td colspan="2" class="text-right"><strong>' . htmlspecialchars($lbl_subtotal) . ':</strong></td>
<td class="text-right">' . number_format($subtotal, 2) . ' ' . htmlspecialchars($currency) . '</td>
</tr>';
// Tax
if ($tax_amount > 0) {
$html .= '<tr>
<td colspan="2" class="text-right"><strong>' . htmlspecialchars($lbl_tax) . ':</strong></td>
<td class="text-right">' . number_format($tax_amount, 2) . ' ' . htmlspecialchars($currency) . '</td>
</tr>';
}
// Shipping
if ($shipping_amount > 0) {
$html .= '<tr>
<td colspan="2" class="text-right"><strong>' . htmlspecialchars($lbl_shipping) . ':</strong></td>
<td class="text-right">' . number_format($shipping_amount, 2) . ' ' . htmlspecialchars($currency) . '</td>
</tr>';
}
// Discount
if ($discount_amount > 0) {
$html .= '<tr>
<td colspan="2" class="text-right"><strong>' . htmlspecialchars($lbl_discount) . ':</strong></td>
<td class="text-right">-' . number_format($discount_amount, 2) . ' ' . htmlspecialchars($currency) . '</td>
</tr>';
}
// Total
$html .= '<tr class="total-row">
<td colspan="2" class="text-right"><strong>' . htmlspecialchars($lbl_total) . ':</strong></td>
<td class="text-right"><strong>' . number_format($payment_amount, 2) . ' ' . htmlspecialchars($currency) . '</strong></td>
</tr>';
$html .= '</tbody>
</table>';
// License information
if ($license_key && $serial_number) {
$html .= '<div class="license-info">
<strong>Software License Information:</strong><br>
<strong>' . htmlspecialchars($lbl_device_serial) . ':</strong> ' . htmlspecialchars($serial_number) . '<br>
<strong>' . htmlspecialchars($lbl_license_key) . ':</strong> ' . htmlspecialchars($license_key) . '<br>
<strong>' . htmlspecialchars($lbl_license_expiry) . ':</strong> 2099-12-31
</div>';
}
$html .= '<div class="footer">
Thank you for your purchase!<br>
This invoice was generated automatically.
</div>
</body>
</html>';
return [$html, $customer_email, $order_id];
}