'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'] ?? []; // Read payment_provider from top level first, then fallback to user_data $payment_provider = $post_content['payment_provider'] ?? $user_data['payment_provider'] ?? 'mollie'; //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // 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']; // Normalize software version for comparison (lowercase, trim leading zeros) - same as software_update.php line 96 $current_sw_version = 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 rowID as version_id, version, name, description, hw_version FROM products_software_versions WHERE rowID = ? AND status = 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 328-331 in software_update.php) $final_price = '0.00'; if (debug) { debuglog("DEBUG: No upgrade paths defined for version_id $version_id - upgrade is FREE"); } } else { // Check for valid upgrade path FROM current version (same logic as software_update.php lines 335-353) $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 (debug) { debuglog("DEBUG: Looking for upgrade path TO version_id=$version_id FROM current_sw_version='$current_sw_version'"); debuglog("DEBUG: Upgrade path result: " . json_encode($upgrade_path)); } if ($upgrade_path) { $final_price = $upgrade_path['price'] ?? '0.00'; $final_currency = $upgrade_path['currency'] ?? 'EUR'; if (debug) { debuglog("DEBUG: Found upgrade path - price: $final_price $final_currency"); } } else { // No upgrade path FROM current version if (debug) { debuglog("ERROR: No valid upgrade path from current version '$current_sw_version' to version_id $version_id"); } http_response_code(400); echo json_encode([ 'error' => 'No valid upgrade path from current version', 'current_version' => $current_sw_version, 'target_version_id' => $version_id ], 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, starts_at, expires_at FROM products_software_licenses WHERE license_key = ?'; $stmt = $pdo->prepare($sql); $stmt->execute([$sw_version_license]); $license = $stmt->fetch(PDO::FETCH_ASSOC); if ($license && $license['status'] == 1) { $now = date('Y-m-d H:i:s'); $starts_at = $license['starts_at']; $expires_at = $license['expires_at']; // Check if license is within valid date range if ((!$starts_at || $starts_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 - Log //+++++++++++++++++++++++++++++++++++++++++++++++++++++ if (debug) { debuglog("DEBUG MODE: Creating $payment_provider payment for testing"); debuglog("DEBUG: Serial Number: $serial_number, Version ID: $version_id, Price: $final_price"); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // STEP 7: Create payment based on provider //+++++++++++++++++++++++++++++++++++++++++++++++++++++ try { // Format price (must be string with 2 decimals) $formatted_price = number_format((float)$final_price, 2, '.', ''); //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // STEP 7A: Generate transaction ID BEFORE creating payment //+++++++++++++++++++++++++++++++++++++++++++++++++++++ $txn_id = strtoupper(uniqid('SC') . substr(md5(mt_rand()), 0, 5)); // Build URLs $protocol = 'https'; $hostname = $_SERVER['SERVER_NAME']; $path = '/'; $redirect_url = "{$protocol}://{$hostname}{$path}?page=softwaretool&payment_return=1&order_id={$txn_id}"; if (debug) { debuglog("DEBUG: Transaction ID: {$txn_id}"); debuglog("DEBUG: Redirect URL: " . $redirect_url); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // Create payment based on selected provider //+++++++++++++++++++++++++++++++++++++++++++++++++++++ if ($payment_provider === 'paypal') { //========================================== // PAYPAL PAYMENT CREATION //========================================== $cancel_url = "{$protocol}://{$hostname}{$path}?page=softwaretool&payment_return=cancelled&order_id={$txn_id}"; // Get PayPal access token $access_token = getPayPalAccessToken(); // Create PayPal order $order_data = [ 'intent' => 'CAPTURE', 'purchase_units' => [[ 'custom_id' => $txn_id, 'description' => "Software upgrade Order #{$txn_id}", 'amount' => [ 'currency_code' => $final_currency ?: 'EUR', 'value' => $formatted_price ], 'payee' => [ 'email_address' => email ] ]], 'application_context' => [ 'return_url' => $redirect_url, 'cancel_url' => $cancel_url, 'brand_name' => site_name, 'user_action' => 'PAY_NOW' ] ]; $ch = curl_init(PAYPAL_URL . '/v2/checkout/orders'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($order_data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $access_token ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code != 200 && $http_code != 201) { debuglog("PayPal API Error: HTTP $http_code - Response: $response"); throw new Exception("PayPal order creation failed: HTTP $http_code"); } $paypal_order = json_decode($response, true); $payment_id = $paypal_order['id'] ?? null; // Extract approval URL $checkout_url = ''; foreach ($paypal_order['links'] ?? [] as $link) { if ($link['rel'] === 'approve') { $checkout_url = $link['href']; break; } } if (!$checkout_url) { throw new Exception("No approval URL received from PayPal"); } $payment_method_id = 1; // PayPal $payment_metadata = 'paypal_order_id'; } else { //========================================== // MOLLIE PAYMENT CREATION //========================================== // Initialize Mollie require dirname(__FILE__, 4).'/initialize.php'; $webhook_url = "{$protocol}://{$hostname}{$path}webhook_mollie.php"; // Create payment with Mollie $payment = $mollie->payments->create([ 'amount' => [ 'currency' => $final_currency ?: 'EUR', 'value' => "{$formatted_price}" ], 'description' => "Software upgrade Order #{$txn_id}", 'redirectUrl' => "{$redirect_url}", 'webhookUrl' => "{$webhook_url}", 'metadata' => [ 'order_id' => $txn_id, 'serial_number' => $serial_number, 'version_id' => $version_id, 'equipment_id' => $equipment_id ] ]); $payment_id = $payment->id; $checkout_url = $payment->getCheckoutUrl(); if (debug) { debuglog("DEBUG: Mollie payment created successfully"); debuglog("DEBUG: Payment ID: $payment_id"); debuglog("DEBUG: Redirect URL sent: $redirect_url"); debuglog("DEBUG: Checkout URL: $checkout_url"); } $payment_method_id = 0; // Mollie $payment_metadata = 'mollie_payment_id'; } if (debug) { debuglog("DEBUG: Payment created via $payment_provider"); debuglog("DEBUG: Payment ID: $payment_id"); debuglog("DEBUG: Checkout URL: $checkout_url"); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // STEP 8: Store transaction in DB using txn_id (order ID) //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // Split name into first/last (simple split on first space) $full_name = $user_data['name'] ?? ''; $name_parts = explode(' ', $full_name, 2); $first_name = $name_parts[0] ?? ''; $last_name = $name_parts[1] ?? ''; // BUILD UP PARTNERHIERARCHY FROM USER $partner_product = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE); $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, accounthierarchy, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; $stmt = $pdo->prepare($sql); $stmt->execute([ $txn_id, $final_price, 0, // 0 = pending $user_data['email'] ?? '', $first_name, $last_name, $user_data['address'] ?? '', $user_data['city'] ?? '', '', // address_state (not collected) $user_data['postal'] ?? '', $user_data['country'] ?? '', $serial_number, $payment_method_id, // 0 = Mollie, 1 = PayPal $partner_product, date('Y-m-d H:i:s') ]); // Get the database ID $transaction_id = $pdo->lastInsertId(); //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // 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, $payment_metadata => $payment_id // Store payment provider ID ], 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([ $transaction_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' => $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; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++ // Helper function to get PayPal access token //+++++++++++++++++++++++++++++++++++++++++++++++++++++ function getPayPalAccessToken() { $ch = curl_init(PAYPAL_URL . '/v1/oauth2/token'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials'); curl_setopt($ch, CURLOPT_USERPWD, PAYPAL_CLIENT_ID . ':' . PAYPAL_CLIENT_SECRET); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code != 200) { throw new Exception("Failed to get PayPal access token: HTTP $http_code"); } $result = json_decode($response, true); return $result['access_token'] ?? ''; } ?>