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:
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user