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:
88
api/v2/get/payment.php
Normal file
88
api/v2/get/payment.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
defined($security_key) or exit;
|
||||
|
||||
//------------------------------------------
|
||||
// Payment Status Retrieval
|
||||
//------------------------------------------
|
||||
// This endpoint retrieves payment details for verification
|
||||
|
||||
//Connect to DB
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
//NEW ARRAY
|
||||
$criterias = [];
|
||||
|
||||
//Check for $_GET variables
|
||||
if(isset($get_content) && $get_content!=''){
|
||||
$requests = explode("&", $get_content);
|
||||
foreach ($requests as $y){
|
||||
$v = explode("=", $y);
|
||||
$criterias[$v[0]] = $v[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Validate payment_id
|
||||
if (empty($criterias['payment_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing required parameter: payment_id'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$payment_id = $criterias['payment_id'];
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 1: Fetch transaction
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$sql = 'SELECT * FROM transactions WHERE txn_id = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$payment_id]);
|
||||
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$transaction) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Payment not found'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 2: Fetch transaction item
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$sql = 'SELECT * FROM transactions_items WHERE txn_id = ? LIMIT 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$payment_id]);
|
||||
$item = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$item) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Payment item not found'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 3: Parse item_options JSON
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$item_options = [];
|
||||
if (!empty($item['item_options'])) {
|
||||
$item_options = json_decode($item['item_options'], true);
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 4: Return payment details
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$messages = json_encode([
|
||||
'payment_id' => $transaction['txn_id'],
|
||||
'payment_status' => $transaction['payment_status'],
|
||||
'payment_amount' => $transaction['payment_amount'],
|
||||
'currency' => 'EUR', // Default currency
|
||||
'serial_number' => $item_options['serial_number'] ?? null,
|
||||
'equipment_id' => $item_options['equipment_id'] ?? null,
|
||||
'hw_version' => $item_options['hw_version'] ?? null,
|
||||
'version_id' => $item['item_id'],
|
||||
'payer_email' => $transaction['payer_email'],
|
||||
'customer_name' => trim(($transaction['first_name'] ?? '') . ' ' . ($transaction['last_name'] ?? '')),
|
||||
'created' => $transaction['created']
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
echo $messages;
|
||||
|
||||
?>
|
||||
280
api/v2/post/payment.php
Normal file
280
api/v2/post/payment.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
defined($security_key) or exit;
|
||||
|
||||
//------------------------------------------
|
||||
// Payment Creation (for Software Upgrades)
|
||||
//------------------------------------------
|
||||
// This endpoint creates a Mollie payment and stores transaction data
|
||||
// SECURITY: Price is calculated SERVER-SIDE, never trusted from frontend
|
||||
|
||||
//Connect to DB
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
//CONTENT FROM API (POST)
|
||||
$post_content = json_decode($input, true);
|
||||
|
||||
// Validate required inputs
|
||||
if (empty($post_content['serial_number']) || empty($post_content['version_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing required fields: serial_number, version_id'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$serial_number = $post_content['serial_number'];
|
||||
$version_id = $post_content['version_id'];
|
||||
$user_data = $post_content['user_data'] ?? [];
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 1: Get equipment data from serial_number
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$sql = 'SELECT rowID, sw_version, sw_version_license, hw_version FROM equipment WHERE serialnumber = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$serial_number]);
|
||||
$equipment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$equipment) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Device not found with serial number: ' . $serial_number], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$equipment_id = $equipment['rowID'];
|
||||
$current_sw_version = trim(strtolower(ltrim($equipment['sw_version'], '0')));
|
||||
$sw_version_license = $equipment['sw_version_license'] ?? null;
|
||||
$hw_version = $equipment['hw_version'] ?? '';
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 2: Get version data from version_id
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$sql = 'SELECT v.rowID as version_id, v.version, v.name, v.description, v.hw_version, p.productcode
|
||||
FROM products_software_versions v
|
||||
JOIN products_software p ON v.product_software_id = p.rowID
|
||||
WHERE v.rowID = ? AND v.is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version_id]);
|
||||
$version = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$version) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Software version not found or inactive'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 3: Calculate price SERVER-SIDE (same logic as software_update.php)
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$final_price = '0.00';
|
||||
$final_currency = '';
|
||||
|
||||
// Check if version has upgrade paths defined
|
||||
$sql = 'SELECT COUNT(*) as path_count FROM products_software_upgrade_paths WHERE to_version_id = ? AND is_active = 1';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version_id]);
|
||||
$path_count_result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$has_upgrade_paths = ($path_count_result['path_count'] > 0);
|
||||
|
||||
if (!$has_upgrade_paths) {
|
||||
// No upgrade paths defined = FREE (lines 240-242 in software_update.php)
|
||||
$final_price = '0.00';
|
||||
} else {
|
||||
// Check for valid upgrade path FROM current version
|
||||
$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';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$version_id, $current_sw_version]);
|
||||
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($upgrade_path) {
|
||||
$final_price = $upgrade_path['price'] ?? '0.00';
|
||||
$final_currency = $upgrade_path['currency'] ?? 'EUR';
|
||||
} else {
|
||||
// No upgrade path FROM current version
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'No valid upgrade path from current version'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 4: Check license validity (lines 280-311 in software_update.php)
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if ($final_price > 0 && $sw_version_license) {
|
||||
$sql = 'SELECT status, start_at, expires_at
|
||||
FROM products_software_licenses
|
||||
WHERE license_key = ? AND equipment_id = ?';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$sw_version_license, $equipment_id]);
|
||||
$license = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($license && $license['status'] == 1) {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$start_at = $license['start_at'];
|
||||
$expires_at = $license['expires_at'];
|
||||
|
||||
// Check if license is within valid date range
|
||||
if ((!$start_at || $start_at <= $now) && (!$expires_at || $expires_at >= $now)) {
|
||||
$final_price = '0.00';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 5: Verify price > 0 (free upgrades shouldn't reach payment API)
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if ($final_price <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'This upgrade is free. No payment required.'], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 6: DEBUG MODE - Simulate payment without Mollie
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if (debug) {
|
||||
// Generate fake payment ID
|
||||
$fake_payment_id = 'DEBUG_' . uniqid() . '_' . time();
|
||||
$checkout_url = 'https://'.$_SERVER['SERVER_NAME'].'/softwaretool.php?payment_return=1&payment_id=' . $fake_payment_id;
|
||||
|
||||
// Store transaction in DB
|
||||
$sql = 'INSERT INTO transactions (txn_id, payment_amount, payment_status, payer_email, first_name, last_name,
|
||||
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, created)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$fake_payment_id,
|
||||
$final_price,
|
||||
0, // 0 = pending
|
||||
$user_data['email'] ?? '',
|
||||
$user_data['first_name'] ?? '',
|
||||
$user_data['last_name'] ?? '',
|
||||
$user_data['address_street'] ?? '',
|
||||
$user_data['address_city'] ?? '',
|
||||
$user_data['address_state'] ?? '',
|
||||
$user_data['address_zip'] ?? '',
|
||||
$user_data['address_country'] ?? '',
|
||||
$serial_number,
|
||||
0, // payment method
|
||||
date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Store transaction item with serial_number in item_options
|
||||
$item_options = json_encode([
|
||||
'serial_number' => $serial_number,
|
||||
'equipment_id' => $equipment_id,
|
||||
'hw_version' => $hw_version
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
|
||||
VALUES (?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$fake_payment_id,
|
||||
$version_id,
|
||||
$final_price,
|
||||
1,
|
||||
$item_options,
|
||||
date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Return fake checkout URL
|
||||
$messages = json_encode([
|
||||
'checkout_url' => $checkout_url,
|
||||
'payment_id' => $fake_payment_id,
|
||||
'debug_mode' => true
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
echo $messages;
|
||||
exit;
|
||||
}
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 7: Call Mollie API to create payment
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
try {
|
||||
// Initialize Mollie
|
||||
require dirname(__FILE__, 3).'/initialize.php';
|
||||
|
||||
// Format price for Mollie (must be string with 2 decimals)
|
||||
$formatted_price = number_format((float)$final_price, 2, '.', '');
|
||||
|
||||
// Create payment with Mollie
|
||||
$payment = $mollie->payments->create([
|
||||
'amount' => [
|
||||
'currency' => $final_currency ?: 'EUR',
|
||||
'value' => $formatted_price
|
||||
],
|
||||
'description' => 'Software upgrade to ' . $version['name'] . ' (v' . $version['version'] . ')',
|
||||
'redirectUrl' => 'https://'.$_SERVER['SERVER_NAME'].'/softwaretool.php?payment_return=1&payment_id={id}',
|
||||
'webhookUrl' => 'https://'.$_SERVER['SERVER_NAME'].'/webhook_mollie.php',
|
||||
'metadata' => [
|
||||
'order_id' => $payment->id // Store payment ID in metadata
|
||||
]
|
||||
]);
|
||||
|
||||
$mollie_payment_id = $payment->id;
|
||||
$checkout_url = $payment->getCheckoutUrl();
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 8: Store transaction in DB
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$sql = 'INSERT INTO transactions (txn_id, payment_amount, payment_status, payer_email, first_name, last_name,
|
||||
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, created)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$mollie_payment_id,
|
||||
$final_price,
|
||||
0, // 0 = pending
|
||||
$user_data['email'] ?? '',
|
||||
$user_data['first_name'] ?? '',
|
||||
$user_data['last_name'] ?? '',
|
||||
$user_data['address_street'] ?? '',
|
||||
$user_data['address_city'] ?? '',
|
||||
$user_data['address_state'] ?? '',
|
||||
$user_data['address_zip'] ?? '',
|
||||
$user_data['address_country'] ?? '',
|
||||
$serial_number,
|
||||
0, // payment method
|
||||
date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 9: Store transaction item with serial_number in item_options
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$item_options = json_encode([
|
||||
'serial_number' => $serial_number,
|
||||
'equipment_id' => $equipment_id,
|
||||
'hw_version' => $hw_version
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
|
||||
VALUES (?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
$mollie_payment_id,
|
||||
$version_id,
|
||||
$final_price,
|
||||
1,
|
||||
$item_options,
|
||||
date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// STEP 10: Return checkout URL and payment ID
|
||||
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
$messages = json_encode([
|
||||
'checkout_url' => $checkout_url,
|
||||
'payment_id' => $mollie_payment_id
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
echo $messages;
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Payment creation failed: ' . $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user