Changeset 3294585
- Timestamp:
- 05/16/2025 06:39:52 AM (10 months ago)
- Location:
- webtonative/trunk
- Files:
-
- 8 edited
-
README.md (modified) (1 diff)
-
admin/iap/index.php (modified) (4 diffs)
-
admin/iap/rest.php (modified) (6 diffs)
-
index.php (modified) (1 diff)
-
website/iap.php (modified) (3 diffs)
-
website/scripts/woocommerce.js (modified) (3 diffs)
-
webtonative-biometric/assets/biometric.js (modified) (2 diffs)
-
webtonative-biometric/webtonative-biometric.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
webtonative/trunk/README.md
r3290467 r3294585 4 4 Requires at least: 2.0.2 5 5 Tested up to: 6.7.1 6 Stable tag: 2. 7.66 Stable tag: 2.8.0 7 7 License: GPLv2 or later 8 8 -
webtonative/trunk/admin/iap/index.php
r3227974 r3294585 14 14 { 15 15 $defaults = array( 16 'iap_enabled' => false, 16 17 'cart_button_text' => 'Buy Now', 17 18 'processing_button_text' => 'Processing...', … … 75 76 $settings = WebtonativeIAPUtil::getIAPSettings(); 76 77 78 $iapEnabled = !empty($settings['iap_enabled']) ? true : false; 77 79 $cartButtonText = $settings['cart_button_text']; 78 80 $processingButtonText = $settings['processing_button_text']; … … 89 91 // Save settings 90 92 $newSettings = array( 93 'iap_enabled' => isset($_POST['iap_enabled']) ? true : false, 91 94 'cart_button_text' => sanitize_text_field($_POST['cart_button_text']), 92 95 'processing_button_text' => sanitize_text_field($_POST['processing_button_text']), … … 106 109 107 110 <table class="form-table"> 111 <tr> 112 <th scope="row"> 113 <label for="iap_enabled">Enable In-App Purchases</label> 114 </th> 115 <td> 116 <input type="checkbox" name="iap_enabled" id="iap_enabled" value="1" <?php checked($iapEnabled, true); ?> /> 117 <!-- <p class="description">Check to enable in-app purchases.</p> --> 118 </td> 119 </tr> 108 120 <tr> 109 121 <th scope="row"> -
webtonative/trunk/admin/iap/rest.php
r3227974 r3294585 139 139 ); 140 140 141 $product_data = wc_get_product($product_id); 141 142 $order = wc_create_order(); 142 $order->add_product(wc_get_product($product_id)); 143 $order->set_payment_method('webtonative'); 144 // $order->set_status('wc-completed'); 145 $order->set_customer_id($current_user->ID); 146 $order->calculate_totals(); 147 $order->save(); 148 149 $note = 'Webtonative Payment Data <br>'; 150 foreach ($native_order as $key => $value) { 151 $note .= $key . ' : ' . $value . '<br>'; 152 } 153 $order->add_order_note($note); 154 $order_id = $order->get_id(); 155 update_post_meta($order_id, '_wtn_payment_data', json_encode($native_order)); 156 157 $response = array( 158 'status' => 'success', 159 ); 160 return new WP_REST_Response($response, 200); 161 } 162 163 function create_ios_order(string $product_id, $request_body) 164 { 165 // data from apple 166 $receipt_data = $request_body['receiptData']; 167 $native_product_id = $request_body['nativeProductId']; 168 $current_user = wp_get_current_user(); 169 170 // check type of receipt data is string 171 if (!is_string($receipt_data)) { 172 return new WP_REST_Response(array('error' => 'Invalid Request'), 400); 173 } 174 $is_valid = $this->verify_apple_payment($native_product_id, $receipt_data); 175 if (!$is_valid) { 176 return new WP_REST_Response(array('error' => 'Invalid Request'), 400); 177 } 178 179 $order = wc_create_order(); 180 $order->add_product(wc_get_product($product_id)); 143 $order->add_product($product_data); 181 144 $order->set_payment_method('webtonative'); 182 145 $order->set_status('wc-completed'); … … 185 148 $order->save(); 186 149 150 $order_id = $order->get_id(); 151 152 // Connect this order to TutorLMS 153 $tutor_product = $product_data->get_meta('_tutor_product'); 154 if ($tutor_product === 'yes') { 155 $tutor_connected = $this->connect_order_to_tutor($order_id, $product_id); 156 error_log('TutorLMS connection status: ' . ($tutor_connected ? 'Connected' : 'Not Connected')); 157 } 158 187 159 $note = 'Webtonative Payment Data <br>'; 188 160 foreach ($native_order as $key => $value) { 161 $note .= $key . ' : ' . $value . '<br>'; 162 } 163 $order->add_order_note($note); 164 165 update_post_meta($order_id, '_wtn_payment_data', json_encode($native_order)); 166 167 $response = array( 168 'status' => 'success', 169 ); 170 return new WP_REST_Response($response, 200); 171 } 172 173 function create_ios_order(string $product_id, $request_body) 174 { 175 // data from apple 176 $receipt_data = $request_body['receiptData']; 177 $native_product_id = $request_body['nativeProductId']; 178 $current_user = wp_get_current_user(); 179 180 // check type of receipt data is string 181 if (!is_string($receipt_data)) { 182 return new WP_REST_Response(array('error' => 'Invalid Request'), 400); 183 } 184 $is_valid = $this->verify_apple_payment($native_product_id, $receipt_data); 185 if (!$is_valid) { 186 return new WP_REST_Response(array('error' => 'Invalid Request'), 400); 187 } 188 189 $product_data = wc_get_product($product_id); 190 $order = wc_create_order(); 191 $order->add_product($product_data); 192 $order->set_payment_method('webtonative'); 193 $order->set_status('wc-completed'); 194 $order->set_customer_id($current_user->ID); 195 $order->calculate_totals(); 196 $order->save(); 197 198 $order_id = $order->get_id(); 199 200 // Connect this order to TutorLMS 201 $tutor_product = $product_data->get_meta('_tutor_product'); 202 if ($tutor_product === 'yes') { 203 $tutor_connected = $this->connect_order_to_tutor($order_id, $product_id); 204 error_log('TutorLMS connection status: ' . ($tutor_connected ? 'Connected' : 'Not Connected')); 205 } 206 207 $note = 'Webtonative Payment Data <br>'; 189 208 $native_order = array( 190 209 'productId' => $native_product_id, … … 196 215 } 197 216 $order->add_order_note($note); 198 $order_id = $order->get_id();199 217 update_post_meta($order_id, '_wtn_payment_data', json_encode($native_order)); 200 218 … … 239 257 function create_woocommerce_order(WP_REST_Request $request) 240 258 { 259 if (!function_exists('WC')) { 260 return new WP_REST_Response(array('error' => 'WooCommerce not loaded'), 500); 261 } 262 241 263 $request_body = json_decode($request->get_body(), true); 242 264 … … 252 274 $product_id = $request_body['productId']; 253 275 276 // Initialize WooCommerce session 277 if (!WC()->session) { 278 // Start a new session 279 WC()->session = new WC_Session_Handler(); 280 WC()->session->init(); 281 } 282 254 283 if ($platform === 'ANDROID') { 255 284 return $this->create_android_order($product_id, $request_body); … … 261 290 262 291 return new WP_REST_Response(array('error' => 'Invalid Request'), 400); 292 } 293 294 function get_course_id_from_product($product_id) 295 { 296 global $wpdb; 297 298 // Query to find course associated with this product 299 $course_id = $wpdb->get_var($wpdb->prepare( 300 "SELECT post_id FROM {$wpdb->postmeta} 301 WHERE meta_key = '_tutor_course_product_id' 302 AND meta_value = %d", 303 $product_id 304 )); 305 306 return $course_id ? (int)$course_id : false; 307 } 308 309 /** 310 * Connect WooCommerce order to TutorLMS 311 * 312 * @param int $order_id The WooCommerce order ID 313 * @param int $product_id The WooCommerce product ID 314 * @return bool Success status 315 */ 316 function connect_order_to_tutor($order_id, $product_id) 317 { 318 // Check if this product is associated with a TutorLMS course 319 $course_id = $this->get_course_id_from_product($product_id); 320 if (!$course_id) { 321 error_log("No TutorLMS course found for product ID: $product_id"); 322 return false; 323 } 324 325 // Get the user ID from the order 326 $order = wc_get_order($order_id); 327 $user_id = $order->get_user_id(); 328 329 // Enroll the user in the course 330 if (function_exists('tutor_utils')) { 331 $enrolled_id = tutor_utils()->do_enroll($course_id, $order_id, $user_id); 332 if ($enrolled_id) { 333 // Update the post status to 'completed' 334 $updated = wp_update_post(array( 335 'ID' => $enrolled_id, 336 'post_status' => 'completed', 337 )); 338 339 if (is_wp_error($updated)) { 340 error_log("Failed to update enrollment status for enrollment ID $enrolled_id: " . $updated->get_error_message()); 341 return false; 342 } 343 344 // Add meta data to the order 345 update_post_meta($order_id, '_is_tutor_order_for_course', true); 346 update_post_meta($order_id, '_tutor_order_for_course_id_' . $course_id, $enrolled_id); 347 348 return true; 349 } else { 350 error_log("Failed to enroll user $user_id in course $course_id via order $order_id"); 351 return false; 352 } 353 } else { 354 error_log("TutorLMS utils not available for enrollment"); 355 return false; 356 } 357 } 358 359 /** 360 * Get the order item ID for a specific product in an order 361 * 362 * @param int $order_id WooCommerce order ID 363 * @param int $product_id WooCommerce product ID 364 * @return int|null Order item ID or null if not found 365 */ 366 function get_order_item_id($order_id, $product_id) 367 { 368 $order = wc_get_order($order_id); 369 if (!$order) return null; 370 371 foreach ($order->get_items() as $item_id => $item) { 372 $item_product_id = $item['product_id'] ?? null; 373 374 if ($item_product_id == $product_id) { 375 return $item_id; 376 } 377 } 378 return null; 263 379 } 264 380 -
webtonative/trunk/index.php
r3290467 r3294585 3 3 Plugin Name: webtonative 4 4 Description: webtonative Plugin 5 Version: 2. 7.65 Version: 2.8.0 6 6 Author: webtonative 7 7 */ -
webtonative/trunk/website/iap.php
r3290467 r3294585 3 3 if (!defined('ABSPATH')) { 4 4 exit(); 5 } // Exit if accessed directly 6 5 } 7 6 8 7 require_once __DIR__ . '/../admin/iap/rest.php'; 9 10 8 11 9 class WebtonativeIAPWebsite … … 23 21 $webtonative_settings = get_option('woocommerce_webtonative_settings'); 24 22 25 wp_register_script('webtonative-iap', plugins_url('scripts/woocommerce.js?var= 2', __FILE__), array('webtonative', 'jquery'), filemtime(plugin_dir_path(__FILE__) . 'scripts/woocommerce.js'), true);23 wp_register_script('webtonative-iap', plugins_url('scripts/woocommerce.js?var=6.13', __FILE__), array('webtonative', 'jquery'), filemtime(plugin_dir_path(__FILE__) . 'scripts/woocommerce.js'), true); 26 24 wp_localize_script('webtonative-iap', 'wtn_biometric_settings', array( 27 25 'btn_proccessing_text' => $saved_settings['processing_button_text'], … … 29 27 'btn_payment_completed_text' => $saved_settings['payment_completed_button_text'], 30 28 'btn_buy_now_text' => $saved_settings['cart_button_text'], 29 'iap_enabled' => $saved_settings['iap_enabled'], 31 30 'isLoggedIn' => is_user_logged_in(), 32 31 'iosSecretKey' => $webtonative_settings['appStoreSecret'], -
webtonative/trunk/website/scripts/woocommerce.js
r3290302 r3294585 1 1 (async function ($) { 2 if (!WTN.isAndroidApp && !WTN.isIosApp) { 3 return; 4 } 2 if (!WTN.isAndroidApp && !WTN.isIosApp) return; 5 3 6 4 const nonce = webtonative_payment_settings.nonce; 7 5 const resturl = webtonative_payment_settings.rest_url; 8 9 6 const isLoggedIn = wtn_biometric_settings.isLoggedIn === '1'; 10 7 if (!isLoggedIn) return; 11 8 9 const isIAPEnabled = wtn_biometric_settings.iap_enabled === '1'; 10 if (!isIAPEnabled) return; 11 12 12 const iosSecretKey = wtn_biometric_settings.iosSecretKey; 13 const btnTexts = { 14 buyNow: wtn_biometric_settings?.btn_buy_now_text ?? 'Buy Now', 15 processing: wtn_biometric_settings?.btn_processing_text ?? 'Processing...', 16 success: wtn_biometric_settings?.btn_payment_completed_text ?? 'Payment completed', 17 failed: wtn_biometric_settings?.btn_failed_text ?? 'Payment failed', 18 }; 13 19 14 const btnBuyNowText = wtn_biometric_settings?.btn_buy_now_text ?? 'Buy Now'; 15 const btnProcessingText = wtn_biometric_settings?.btn_processing_text ?? 'Processing...'; 16 const btnSuccessText = wtn_biometric_settings?.btn_payment_completed_text ?? 'Payment completed'; 17 const btnFailedText = wtn_biometric_settings?.btn_failed_text ?? 'Payment failed'; 20 let productListCache = null; 18 21 19 const response = await fetch(resturl + '/list'); 20 const data = await response.json(); 22 async function fetchProductList() { 23 if (productListCache) return productListCache; 24 const response = await fetch(`${resturl}/list`); 25 productListCache = await response.json(); 26 return productListCache; 27 } 21 28 22 29 function getProductIds(productId) { 23 return new Promise((res, rej) => { 24 $.ajax({ 25 url: resturl + '/product', 26 type: 'POST', 27 contentType: 'application/json', 28 data: JSON.stringify({ 29 productId: productId, 30 platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS', 31 }), 32 headers: { 33 'X-WP-Nonce': nonce, 34 }, 35 success: function (response) { 36 const dataToProcess = { 37 productId: response.productId, 38 productType: response.productType === 'SUBS' ? 'SUBS' : 'INAPP', 39 isConsumable: response.isConsumable === 'yes', 40 }; 41 if (!dataToProcess.productId || !dataToProcess.productType) { 42 rej(); 43 return; 44 } 45 res(dataToProcess); 46 }, 47 error: function () { 48 rej(); 49 }, 50 }); 30 return $.ajax({ 31 url: `${resturl}/product`, 32 type: 'POST', 33 contentType: 'application/json', 34 data: JSON.stringify({ 35 productId, 36 platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS', 37 }), 38 headers: { 'X-WP-Nonce': nonce }, 39 }).then((response) => { 40 const processedData = { 41 productId: response.productId, 42 productType: response.productType === 'SUBS' ? 'SUBS' : 'INAPP', 43 isConsumable: response.isConsumable === 'yes', 44 }; 45 if (!processedData.productId || !processedData.productType) throw new Error('Invalid product data'); 46 return processedData; 51 47 }); 52 48 } 53 49 54 async function processPayment() { 55 const button = $(this); 56 const productId = button.data('product_id') || button.val(); 57 if (!productId) { 58 return; 59 } 50 function toggleButtonState(button, isProcessing, text) { 51 button.text(text); 52 button.prop('disabled', isProcessing); 53 } 60 54 61 function createOrder(data, dataToProcess) { 62 let dataToSend = { 63 productId: productId, 64 platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS', 65 nativeProductId: dataToProcess.productId, 66 }; 67 if (WTN.isIosApp) { 68 dataToSend.receiptData = data; 69 } 70 if (WTN.isAndroidApp) { 71 dataToSend = { 72 ...dataToSend, 73 ...data, 74 }; 75 } 76 $.ajax({ 77 url: resturl + '/order', 78 type: 'POST', 79 contentType: 'application/json', 80 data: JSON.stringify(dataToSend), 81 headers: { 82 'X-WP-Nonce': nonce, 83 }, 84 success: function (response) { 85 if (response.status === 'success') { 86 button.text(btnSuccessText); 87 return; 88 } 89 }, 90 error: function (error) { 91 alert(btnFailedText + JSON.stringify(error)); 92 button.text(btnFailedText); 93 }, 94 }); 95 } 55 async function createOrder(orderData) { 56 return $.ajax({ 57 url: `${resturl}/order`, 58 type: 'POST', 59 contentType: 'application/json', 60 data: JSON.stringify(orderData), 61 headers: { 'X-WP-Nonce': nonce }, 62 }); 63 } 96 64 97 button.text(btnProcessingText); 98 button.prop('disabled', true); 65 async function processPayment(button) { 66 const productId = button.attr('data-product_id') || button.data('product_id') || button.val(); 67 if (!productId) return; 68 69 toggleButtonState(button, true, btnTexts.processing); 99 70 100 71 try { 101 72 const productData = await getProductIds(productId); 102 // alert('Product Data: ' + JSON.stringify(productData));103 73 WTN.inAppPurchase({ 104 74 ...productData, 105 callback: function (data) { 106 // paymentCallback(data, dataToProcess); 107 button.prop('disabled', false); 75 callback: async (data) => { 108 76 if (!data.isSuccess) { 109 alert(btn FailedText);110 button.text(btnBuyNowText);77 alert(btnTexts.failed); 78 toggleButtonState(button, false, btnTexts.buyNow); 111 79 return; 112 80 } 113 const receiptData = data.receiptData; 114 createOrder(receiptData, { ...productData }); 81 82 const orderPayload = { 83 productId, 84 platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS', 85 nativeProductId: productData.productId, 86 ...(WTN.isIosApp ? { receiptData: data.receiptData } : data), 87 }; 88 89 try { 90 const orderResponse = await createOrder(orderPayload); 91 if (orderResponse.status === 'success') { 92 toggleButtonState(button, false, btnTexts.success); 93 } 94 } catch (orderError) { 95 toggleButtonState(button, false, btnTexts.failed); 96 } 115 97 }, 116 98 }); 117 button.prop('disabled', true);118 99 } catch (error) { 119 console.log({ error }); 100 console.error('Error in processPayment:', error); 101 toggleButtonState(button, false, btnTexts.failed); 120 102 } 121 122 return false;123 103 } 124 104 125 // Loop through the buttons and update their text if `nativeProductId` exists 126 $('.single_add_to_cart_button, .add_to_cart_button').each(function () { 105 async function setupButtons() { 106 const productList = await fetchProductList(); 107 108 $('.single_add_to_cart_button, .add_to_cart_button').each(function () { 109 const button = $(this); 110 const productId = button.data('product_id') || button.val(); 111 const nativeProductId = productList?.[productId]?.[WTN.isIosApp ? 'appStore' : 'googlePlay']; 112 113 if (nativeProductId) { 114 const existingClasses = button.attr('class').replace('ajax_add_to_cart', '') || ''; 115 const newButton = $('<button>') 116 .addClass(`${existingClasses} webtonative-iap`) 117 .text(btnTexts.buyNow) 118 .attr('style', button.attr('style')) 119 .attr('data-product_id', productId) 120 .data('product_id', productId); 121 122 button.replaceWith(newButton); 123 } 124 }); 125 } 126 127 $(document).on('click', '.webtonative-iap', function (e) { 128 e.preventDefault(); 129 e.stopPropagation(); 130 e.stopImmediatePropagation(); 131 127 132 const button = $(this); 128 const productId = button.data('product_id') || button.val(); 129 const nativeProductId = data?.[productId.toString()]?.[WTN.isIosApp ? 'appStore' : 'googlePlay']; 133 const productId = button.attr('data-product_id') || button.data('product_id') || button.val(); 130 134 131 if (nativeProductId) { 132 // Create a new <button> element 133 const newButton = $('<button>') 134 .addClass(button.attr('class')) // Copy classes from the original anchor tag 135 .text(btnBuyNowText) 136 .attr('style', button.attr('style')) // Copy inline styles 137 .data('product_id', productId); // Preserve data attributes 138 139 // Replace the anchor tag with the new button 140 button.replaceWith(newButton); 141 } 142 }); 143 144 // Event delegation for dynamically added buttons 145 $(document).on('click', '.single_add_to_cart_button, .add_to_cart_button', async function (e) { 146 const button = $(this); 147 const productId = button.data('product_id') || button.val(); 148 const nativeProductId = data?.[productId.toString()]?.[WTN.isIosApp ? 'appStore' : 'googlePlay']; 135 // const productList = await fetchProductList(); 136 const nativeProductId = productListCache?.[productId]?.[WTN.isIosApp ? 'appStore' : 'googlePlay']; 149 137 if (!nativeProductId) return; 150 151 e.stopPropagation();152 e.preventDefault();153 e.stopImmediatePropagation();154 138 155 139 if (!isLoggedIn) { … … 157 141 return; 158 142 } 143 159 144 if (WTN.isIosApp && !iosSecretKey) { 160 145 alert('Please configure App store secret key in settings'); … … 162 147 } 163 148 164 await processPayment.call(this);149 processPayment(button); 165 150 }); 151 152 // Initialize 153 await setupButtons(); 166 154 })(jQuery); -
webtonative/trunk/webtonative-biometric/assets/biometric.js
r3286216 r3294585 44 44 45 45 // Set checkbox state based on userSecret 46 enableBiometric.checked = !!WTN_Biometric_Settings.userSecret && !!data.hasSecret;47 46 if (!enableBiometric) return; 47 enableBiometric.checked = !!WTN_Biometric_Settings?.userSecret && !!data?.hasSecret; 48 48 enableBiometric.addEventListener('change', handleBiometricToggle); 49 49 } else { … … 124 124 // handle promt on app open 125 125 if (!isSessionExpired() && WTN_Biometric_Settings.biometricEnabled && WTN_Biometric_Settings.promptOnOpen) { 126 window.WTN.appFirstLoad().then(function (value) { 126 if (typeof window.WTN.appFirstLoad === 'undefined') return; 127 window?.WTN?.appFirstLoad()?.then(function (value) { 127 128 if (!value.result) return; 128 129 showBiometricPrompt(); -
webtonative/trunk/webtonative-biometric/webtonative-biometric.php
r3216434 r3294585 13 13 function wtn_biometric_enqueue_scripts() 14 14 { 15 wp_enqueue_script('wtn-biometric-js', plugins_url('assets/biometric.js ', __FILE__), ['jquery'], '1.0.0', true);15 wp_enqueue_script('wtn-biometric-js', plugins_url('assets/biometric.js?ver=1.3', __FILE__), ['jquery'], '1.0.0', true); 16 16 17 17 wp_localize_script('wtn-biometric-js', 'WTN_Biometric_Settings', [ … … 64 64 65 65 if ($action === 'enable') { 66 error_log("enableddddddddddd");67 66 update_user_meta($user_id, 'wtn_biometric_secret', $secret); 68 67 return ['success' => true, 'message' => 'Biometric authentication enabled']; 69 68 } elseif ($action === 'disable') { 70 error_log("disableddddddddddd");71 69 delete_user_meta($user_id, 'wtn_biometric_secret'); 72 70 return ['success' => true, 'message' => 'Biometric authentication disabled']; … … 191 189 $user_id = get_current_user_id(); 192 190 $is_biometric_enabled = get_user_meta($user_id, 'wtn_biometric_secret', true); 193 191 // only print is_biometric_enabled 194 192 ob_start(); 195 193 ?>
Note: See TracChangeset
for help on using the changeset viewer.