Plugin Directory

Changeset 3294585


Ignore:
Timestamp:
05/16/2025 06:39:52 AM (10 months ago)
Author:
webtonative
Message:

Deploy plugin version 2.8.0

Location:
webtonative/trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • webtonative/trunk/README.md

    r3290467 r3294585  
    44Requires at least: 2.0.2
    55Tested up to: 6.7.1
    6 Stable tag: 2.7.6
     6Stable tag: 2.8.0
    77License: GPLv2 or later
    88
  • webtonative/trunk/admin/iap/index.php

    r3227974 r3294585  
    1414  {
    1515    $defaults = array(
     16      'iap_enabled' => false,
    1617      'cart_button_text' => 'Buy Now',
    1718      'processing_button_text' => 'Processing...',
     
    7576    $settings = WebtonativeIAPUtil::getIAPSettings();
    7677
     78    $iapEnabled = !empty($settings['iap_enabled']) ? true : false;
    7779    $cartButtonText = $settings['cart_button_text'];
    7880    $processingButtonText = $settings['processing_button_text'];
     
    8991      // Save settings
    9092      $newSettings = array(
     93        'iap_enabled' => isset($_POST['iap_enabled']) ? true : false,
    9194        'cart_button_text' => sanitize_text_field($_POST['cart_button_text']),
    9295        'processing_button_text' => sanitize_text_field($_POST['processing_button_text']),
     
    106109
    107110        <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>
    108120          <tr>
    109121            <th scope="row">
  • webtonative/trunk/admin/iap/rest.php

    r3227974 r3294585  
    139139    );
    140140
     141    $product_data = wc_get_product($product_id);
    141142    $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);
    181144    $order->set_payment_method('webtonative');
    182145    $order->set_status('wc-completed');
     
    185148    $order->save();
    186149
     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
    187159    $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>';
    189208    $native_order = array(
    190209      'productId' => $native_product_id,
     
    196215    }
    197216    $order->add_order_note($note);
    198     $order_id = $order->get_id();
    199217    update_post_meta($order_id, '_wtn_payment_data', json_encode($native_order));
    200218
     
    239257  function create_woocommerce_order(WP_REST_Request $request)
    240258  {
     259    if (!function_exists('WC')) {
     260      return new WP_REST_Response(array('error' => 'WooCommerce not loaded'), 500);
     261    }
     262
    241263    $request_body = json_decode($request->get_body(), true);
    242264
     
    252274    $product_id = $request_body['productId'];
    253275
     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
    254283    if ($platform === 'ANDROID') {
    255284      return $this->create_android_order($product_id, $request_body);
     
    261290
    262291    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;
    263379  }
    264380
  • webtonative/trunk/index.php

    r3290467 r3294585  
    33  Plugin Name: webtonative
    44  Description: webtonative Plugin
    5   Version: 2.7.6
     5  Version: 2.8.0
    66  Author: webtonative
    77*/
  • webtonative/trunk/website/iap.php

    r3290467 r3294585  
    33if (!defined('ABSPATH')) {
    44  exit();
    5 } // Exit if accessed directly
    6 
     5}
    76
    87require_once __DIR__ . '/../admin/iap/rest.php';
    9 
    108
    119class WebtonativeIAPWebsite
     
    2321    $webtonative_settings = get_option('woocommerce_webtonative_settings');
    2422
    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);
    2624    wp_localize_script('webtonative-iap', 'wtn_biometric_settings', array(
    2725      'btn_proccessing_text' => $saved_settings['processing_button_text'],
     
    2927      'btn_payment_completed_text' => $saved_settings['payment_completed_button_text'],
    3028      'btn_buy_now_text' => $saved_settings['cart_button_text'],
     29      'iap_enabled' => $saved_settings['iap_enabled'],
    3130      'isLoggedIn'     => is_user_logged_in(),
    3231      'iosSecretKey' => $webtonative_settings['appStoreSecret'],
  • webtonative/trunk/website/scripts/woocommerce.js

    r3290302 r3294585  
    11(async function ($) {
    2   if (!WTN.isAndroidApp && !WTN.isIosApp) {
    3     return;
    4   }
     2  if (!WTN.isAndroidApp && !WTN.isIosApp) return;
    53
    64  const nonce = webtonative_payment_settings.nonce;
    75  const resturl = webtonative_payment_settings.rest_url;
    8 
    96  const isLoggedIn = wtn_biometric_settings.isLoggedIn === '1';
    107  if (!isLoggedIn) return;
    118
     9  const isIAPEnabled = wtn_biometric_settings.iap_enabled === '1';
     10  if (!isIAPEnabled) return;
     11
    1212  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  };
    1319
    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;
    1821
    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  }
    2128
    2229  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;
    5147    });
    5248  }
    5349
    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  }
    6054
    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  }
    9664
    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);
    9970
    10071    try {
    10172      const productData = await getProductIds(productId);
    102       // alert('Product Data: ' + JSON.stringify(productData));
    10373      WTN.inAppPurchase({
    10474        ...productData,
    105         callback: function (data) {
    106           // paymentCallback(data, dataToProcess);
    107           button.prop('disabled', false);
     75        callback: async (data) => {
    10876          if (!data.isSuccess) {
    109             alert(btnFailedText);
    110             button.text(btnBuyNowText);
     77            alert(btnTexts.failed);
     78            toggleButtonState(button, false, btnTexts.buyNow);
    11179            return;
    11280          }
    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          }
    11597        },
    11698      });
    117       button.prop('disabled', true);
    11899    } catch (error) {
    119       console.log({ error });
     100      console.error('Error in processPayment:', error);
     101      toggleButtonState(button, false, btnTexts.failed);
    120102    }
    121 
    122     return false;
    123103  }
    124104
    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
    127132    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();
    130134
    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'];
    149137    if (!nativeProductId) return;
    150 
    151     e.stopPropagation();
    152     e.preventDefault();
    153     e.stopImmediatePropagation();
    154138
    155139    if (!isLoggedIn) {
     
    157141      return;
    158142    }
     143
    159144    if (WTN.isIosApp && !iosSecretKey) {
    160145      alert('Please configure App store secret key in settings');
     
    162147    }
    163148
    164     await processPayment.call(this);
     149    processPayment(button);
    165150  });
     151
     152  // Initialize
     153  await setupButtons();
    166154})(jQuery);
  • webtonative/trunk/webtonative-biometric/assets/biometric.js

    r3286216 r3294585  
    4444
    4545        // 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;
    4848        enableBiometric.addEventListener('change', handleBiometricToggle);
    4949      } else {
     
    124124  // handle promt on app open
    125125  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) {
    127128      if (!value.result) return;
    128129      showBiometricPrompt();
  • webtonative/trunk/webtonative-biometric/webtonative-biometric.php

    r3216434 r3294585  
    1313function wtn_biometric_enqueue_scripts()
    1414{
    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);
    1616
    1717    wp_localize_script('wtn-biometric-js', 'WTN_Biometric_Settings', [
     
    6464
    6565    if ($action === 'enable') {
    66         error_log("enableddddddddddd");
    6766        update_user_meta($user_id, 'wtn_biometric_secret', $secret);
    6867        return ['success' => true, 'message' => 'Biometric authentication enabled'];
    6968    } elseif ($action === 'disable') {
    70         error_log("disableddddddddddd");
    7169        delete_user_meta($user_id, 'wtn_biometric_secret');
    7270        return ['success' => true, 'message' => 'Biometric authentication disabled'];
     
    191189    $user_id = get_current_user_id();
    192190    $is_biometric_enabled = get_user_meta($user_id, 'wtn_biometric_secret', true);
    193 
     191    // only print is_biometric_enabled
    194192    ob_start();
    195193?>
Note: See TracChangeset for help on using the changeset viewer.