Plugin Directory

Changeset 3322172


Ignore:
Timestamp:
07/04/2025 09:06:47 AM (8 months ago)
Author:
webtonative
Message:

Deploy plugin version 2.8.8

Location:
webtonative/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • webtonative/trunk/README.md

    r3313015 r3322172  
    44Requires at least: 2.0.2
    55Tested up to: 6.7.1
    6 Stable tag: 2.8.7
     6Stable tag: 2.8.8
    77License: GPLv2 or later
    88
  • webtonative/trunk/admin/iap/rest.php

    r3313015 r3322172  
    117117      $expiration_ms = intval($latest['expires_date_ms'] ?? 0);
    118118      $now_ms = round(microtime(true) * 1000);
    119 
    120       if ($expiration_ms > $now_ms) {
     119      $transaction_id = $latest['transaction_id'] ?? '';
     120      $original_transaction_id = $latest['original_transaction_id'] ?? '';
     121
     122      // Check if this transaction is already recorded in the database
     123      $existing_order = $this->get_order_by_transaction_id($original_transaction_id);
     124
     125      if ($existing_order) {
     126        // If the transaction exists, check if it's a renewal or an active subscription
     127        $existing_expiration = get_post_meta($existing_order->get_id(), '_wtn_subscription_expiration_ms', true);
     128        if ($expiration_ms > $existing_expiration) {
     129          // This is a renewal: update the existing order
     130          update_post_meta($existing_order->get_id(), '_wtn_subscription_expiration_ms', $expiration_ms);
     131          update_post_meta($existing_order->get_id(), '_wtn_subscription_transaction_id', $transaction_id);
     132          return [
     133            'success' => true,
     134            'message' => 'Subscription renewed',
     135            'data' => $latest,
     136            'order_id' => $existing_order->get_id(),
     137          ];
     138        } else {
     139          // Subscription is already active and not renewed
     140          return [
     141            'success' => false,
     142            'isAlreadyPurchased' => true,
     143            'message' => 'Subscription already active',
     144            'expiration' => gmdate("Y-m-d\TH:i:s\Z", $expiration_ms / 1000),
     145            'data' => $latest,
     146          ];
     147        }
     148      }
     149
     150      // No existing order found, this is a new subscription
     151      if ($expiration_ms <= $now_ms) {
    121152        return [
    122153          'success' => false,
    123           'isAlreadyPurchased' => true,
    124           'message' => "Subscription already purchased",
    125           'expiration' => gmdate("Y-m-d\TH:i:s\Z", $expiration_ms / 1000),
     154          'message' => 'Subscription has expired',
    126155          'data' => $latest,
    127156        ];
    128157      }
    129158
     159      // if ($expiration_ms > $now_ms) {
     160      //   return [
     161      //     'success' => false,
     162      //     'isAlreadyPurchased' => true,
     163      //     'message' => "Subscription already purchased",
     164      //     'expiration' => gmdate("Y-m-d\TH:i:s\Z", $expiration_ms / 1000),
     165      //     'data' => $latest,
     166      //   ];
     167      // }
     168
    130169      return [
    131170        'success' => true,
    132         'message' => '',
     171        'message' => 'New subscription validated',
    133172        'data' => $latest,
     173        'transaction_id' => $transaction_id,
     174        'original_transaction_id' => $original_transaction_id,
    134175      ];
    135176    } else {
     
    177218
    178219    // data from google play
     220    $variant_id = $request_body['variantId'];
    179221    $native_order_id = $request_body['receiptData']['orderId'];
    180222    $native_product_id = $request_body['nativeProductId'];
     
    202244      'customer_id' => $current_user->ID,
    203245    ));
    204     $order->add_product($product_data);
     246    if ($product_data->is_type('variable') && $variant_id) {
     247      $variation_product = wc_get_product($variant_id);
     248      $order->add_product($variation_product);
     249    } else {
     250      $order->add_product($product_data);
     251    }
     252    // $order->add_product($product_data);
    205253    $order->set_payment_method('webtonative');
    206254    $order->set_status('wc-completed');
     
    227275
    228276    $response = array(
    229       'status' => 'success',
     277      'success' => true,
     278      'message' => 'ok'
    230279    );
    231280    return new WP_REST_Response($response, 200);
     
    235284  {
    236285    // data from apple
     286    $variant_id = $request_body['variantId'];
    237287    $receipt_data = $request_body['receiptData'];
    238288    $native_product_id = $request_body['nativeProductId'];
     
    260310    }
    261311
     312    $verify_data = $is_valid['data'] ?? [];
    262313    $product_data = wc_get_product($product_id);
    263314    $order = wc_create_order(array(
    264315      'customer_id' => $current_user->ID,
    265316    ));
    266     $order->add_product($product_data);
     317    if ($product_data->is_type('variable') && $variant_id) {
     318      $variation_product = wc_get_product($variant_id);
     319      $order->add_product($variation_product);
     320    } else {
     321      $order->add_product($product_data);
     322    }
     323    // $order->add_product($product_data);
    267324    $order->set_payment_method('webtonative');
    268325    $order->set_status('wc-completed');
     
    283340    $native_order = array(
    284341      'productId' => $native_product_id,
    285       'receiptData' => $receipt_data,
     342      'receiptData' => is_scalar($verify_data) ? strval($verify_data) : json_encode($verify_data),
    286343      'platform' => 'IOS',
    287344    );
     
    291348    $order->add_order_note($note);
    292349    update_post_meta($order_id, '_wtn_payment_data', json_encode($native_order));
     350
     351    if ($product_type === 'SUBS') {
     352      update_post_meta($order_id, '_wtn_subscription_transaction_id', $verify_data['transaction_id'] ?? '');
     353      update_post_meta($order_id, '_wtn_subscription_original_transaction_id', $verify_data['original_transaction_id'] ?? '');
     354      update_post_meta($order_id, '_wtn_subscription_expiration_ms', $verify_data['expires_date_ms'] ?? '');
     355    }
    293356
    294357    $response = ['success' => true, 'message' => $is_valid['message']];
     
    481544    return new WP_REST_Response($arr, 200);
    482545  }
     546
     547  function get_order_by_transaction_id(string $original_transaction_id)
     548  {
     549    $args = [
     550      'post_type' => 'shop_order',
     551      'post_status' => array_keys(wc_get_order_statuses()),
     552      'meta_query' => [
     553        [
     554          'key' => '_wtn_subscription_original_transaction_id',
     555          'value' => $original_transaction_id,
     556          'compare' => '=',
     557        ],
     558      ],
     559    ];
     560
     561    $orders = wc_get_orders($args);
     562    return !empty($orders) ? $orders[0] : null;
     563  }
    483564}
  • webtonative/trunk/admin/iap/woo-commerce.php

    r3034141 r3322172  
    1111  {
    1212    add_action('woocommerce_product_options_general_product_data', array($this, 'add_fields'));
     13
    1314    add_action('woocommerce_process_product_meta', array($this, 'save_fields'));
     15
     16    add_action('woocommerce_variation_options', function ($loop, $variation_data, $variation) {
     17      // Google Play Product ID
     18      woocommerce_wp_text_input([
     19        'id' => '_wtn_google_play_product_id[' . $variation->ID . ']',
     20        'label' => __('Google Play Product ID', 'woocommerce'),
     21        'desc_tip' => 'true',
     22        'value' => get_post_meta($variation->ID, '_wtn_google_play_product_id', true),
     23      ]);
     24      // App Store Product ID
     25      woocommerce_wp_text_input([
     26        'id' => '_wtn_app_store_product_id[' . $variation->ID . ']',
     27        'label' => __('App Store Product ID', 'woocommerce'),
     28        'desc_tip' => 'true',
     29        'value' => get_post_meta($variation->ID, '_wtn_app_store_product_id', true),
     30      ]);
     31      // Product Type
     32      woocommerce_wp_select([
     33        'id' => '_wtn_product_type[' . $variation->ID . ']',
     34        'label' => __('Product Type', 'woocommerce'),
     35        'options' => [
     36          'INAPP' => __('In-App Purchase', 'woocommerce'),
     37          'SUBS' => __('Subscription', 'woocommerce'),
     38        ],
     39        'value' => get_post_meta($variation->ID, '_wtn_product_type', true),
     40      ]);
     41      // Is Consumable
     42      woocommerce_wp_checkbox([
     43        'id' => '_wtn_is_consumable[' . $variation->ID . ']',
     44        'label' => __('Is Consumable', 'woocommerce'),
     45        'desc_tip' => 'true',
     46        'value' => get_post_meta($variation->ID, '_wtn_is_consumable', true),
     47      ]);
     48    }, 10, 3);
     49
     50    add_action('woocommerce_save_product_variation', function ($variation_id, $i) {
     51      // Google Play Product ID
     52      if (isset($_POST['_wtn_google_play_product_id'][$variation_id])) {
     53        update_post_meta($variation_id, '_wtn_google_play_product_id', sanitize_text_field($_POST['_wtn_google_play_product_id'][$variation_id]));
     54      }
     55      // App Store Product ID
     56      if (isset($_POST['_wtn_app_store_product_id'][$variation_id])) {
     57        update_post_meta($variation_id, '_wtn_app_store_product_id', sanitize_text_field($_POST['_wtn_app_store_product_id'][$variation_id]));
     58      }
     59      // Product Type
     60      if (isset($_POST['_wtn_product_type'][$variation_id])) {
     61        update_post_meta($variation_id, '_wtn_product_type', sanitize_text_field($_POST['_wtn_product_type'][$variation_id]));
     62      }
     63      // Is Consumable
     64      // Checkbox: if not set, set to 'no'
     65      $is_consumable = isset($_POST['_wtn_is_consumable'][$variation_id]) ? 'yes' : 'no';
     66      update_post_meta($variation_id, '_wtn_is_consumable', $is_consumable);
     67    }, 10, 2);
    1468  }
    1569
     
    142196     * You will need it if you want your custom credit card form
    143197     */
    144     public function payment_fields()
    145     {
    146     }
     198    public function payment_fields() {}
    147199
    148200    /*
    149201     * Custom CSS and JS, in most cases required only when you decided to go with a custom credit card form
    150202     */
    151     public function payment_scripts()
    152     {
    153     }
     203    public function payment_scripts() {}
    154204
    155205    /*
    156206     * Fields validation
    157207     */
    158     public function validate_fields()
    159     {
    160     }
     208    public function validate_fields() {}
    161209
    162210    /*
    163211     * We're processing the payments here
    164212     */
    165     public function process_payment($order_id)
    166     {
    167     }
     213    public function process_payment($order_id) {}
    168214
    169215    /*
    170216     * In case you need a webhook
    171217     */
    172     public function webhook()
    173     {
    174     }
     218    public function webhook() {}
    175219  }
    176220}
  • webtonative/trunk/index.php

    r3313015 r3322172  
    33  Plugin Name: webtonative
    44  Description: webtonative Plugin
    5   Version: 2.8.7
     5  Version: 2.8.8
    66  Author: webtonative
    77*/
  • webtonative/trunk/website/iap.php

    r3301312 r3322172  
    1212  {
    1313    add_action('wp_enqueue_scripts', array($this, 'load_scripts'));
     14    add_action('wp_footer', [$this, 'output_variant_meta_js']);
    1415    add_shortcode('webtonative_buy_now', array($this, 'shortcode'));
    1516    new WebtonativeIAPRestController();
     
    2122    $webtonative_settings = get_option('woocommerce_webtonative_settings');
    2223
    23     wp_register_script('webtonative-iap', plugins_url('scripts/woocommerce.js?var=6.14.5', __FILE__), array('webtonative', 'jquery'), filemtime(plugin_dir_path(__FILE__) . 'scripts/woocommerce.js'), true);
     24    wp_register_script('webtonative-iap', plugins_url('scripts/woocommerce.js?var=6.14.6', __FILE__), array('webtonative', 'jquery'), filemtime(plugin_dir_path(__FILE__) . 'scripts/woocommerce.js'), true);
    2425    wp_localize_script('webtonative-iap', 'wtn_biometric_settings', array(
    2526      'btn_proccessing_text' => $saved_settings['processing_button_text'],
     
    5051    return $output;
    5152  }
     53
     54  public function output_variant_meta_js_old()
     55  {
     56    if (!is_product()) return;
     57    global $product;
     58    if ($product && $product->is_type('variable')) {
     59      $variant_ids = $product->get_children();
     60      $variant_meta = [];
     61      foreach ($variant_ids as $vid) {
     62        $variant_meta[$vid] = [
     63          'googlePlay'   => get_post_meta($vid, '_wtn_google_play_product_id', true),
     64          'appStore'     => get_post_meta($vid, '_wtn_app_store_product_id', true),
     65          'productType'  => get_post_meta($vid, '_wtn_product_type', true),
     66          'isConsumable' => get_post_meta($vid, '_wtn_is_consumable', true),
     67        ];
     68      }
     69      echo '<script>window.wtn_variant_meta = ' . json_encode($variant_meta) . ';</script>';
     70    }
     71  }
     72
     73  public function output_variant_meta_js()
     74  {
     75    if (!is_product()) return;
     76    global $product;
     77    if ($product && $product->is_type('variable')) {
     78      $base_product_id = $product->get_id();
     79      $variant_ids = $product->get_children();
     80      $variant_meta = [];
     81      foreach ($variant_ids as $vid) {
     82        $variation = wc_get_product($vid);
     83        $variant_meta[$vid] = [
     84          'baseProductId' => $base_product_id,
     85          'name'         => $variation ? $variation->get_name() : '',
     86          'googlePlay'   => get_post_meta($vid, '_wtn_google_play_product_id', true),
     87          'appStore'     => get_post_meta($vid, '_wtn_app_store_product_id', true),
     88          'productType'  => get_post_meta($vid, '_wtn_product_type', true),
     89          'isConsumable' => get_post_meta($vid, '_wtn_is_consumable', true),
     90        ];
     91      }
     92      echo '<script>window.wtn_variant_meta = ' . json_encode($variant_meta) . ';</script>';
     93    }
     94  }
    5295}
  • webtonative/trunk/website/scripts/woocommerce.js

    r3301312 r3322172  
    2828  let productListCache = null;
    2929  let isIAPInitialized = false;
     30  let isSettingUpButtons = false;
    3031
    3132  async function fetchProductList() {
     
    3435    productListCache = await response.json();
    3536    return productListCache;
    36   }
    37 
    38   function getProductIds(productId) {
    39     return $.ajax({
    40       url: `${resturl}/product`,
    41       type: 'POST',
    42       contentType: 'application/json',
    43       data: JSON.stringify({
    44         productId,
    45         platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS',
    46       }),
    47       headers: { 'X-WP-Nonce': nonce },
    48     }).then((response) => {
    49       const processedData = {
    50         productId: response.productId,
    51         productType: response.productType === 'SUBS' ? 'SUBS' : 'INAPP',
    52         isConsumable: response.isConsumable === 'yes',
    53       };
    54       if (!processedData.productId || !processedData.productType) throw new Error('Invalid product data');
    55       return processedData;
    56     });
    5737  }
    5838
     
    7454  async function processPayment(button) {
    7555    const productId = button.attr('data-product_id') || button.data('product_id') || button.val();
    76     if (!productId) return;
     56    const nativeProductId = button.attr('data-native_product_id');
     57
     58    if (!productId || !nativeProductId) return;
     59
     60    const productType = button.attr('data-product_type');
     61    const isConsumable = button.attr('data-is_consumable');
     62    const variantId = button.attr('data-variant_id');
    7763
    7864    toggleButtonState(button, true, btnTexts.processing);
     
    8066    try {
    8167      isIAPInitialized = true;
    82       const productData = await getProductIds(productId);
     68      const productData = { productId: nativeProductId, productType, isConsumable };
    8369
    8470      WTN.inAppPurchase({
     
    10288            productId,
    10389            platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS',
    104             nativeProductId: productData.productId,
    105             productType: productData.productType,
     90            nativeProductId: nativeProductId,
     91            productType: productType,
     92            variantId,
    10693            ...(WTN.isIosApp ? { receiptData: data.receiptData } : data),
    10794          };
     
    114101            isIAPInitialized = false;
    115102          } catch (orderError) {
    116             console.log('Order Error:', orderError);
    117103            toggleButtonState(button, false, btnTexts.failed);
    118104
     
    142128      });
    143129    } catch (error) {
    144       console.error('Error in processPayment:', error);
    145130      toggleButtonState(button, false, btnTexts.failed);
    146131      isIAPInitialized = false;
     
    149134
    150135  async function setupButtons() {
     136    if (isSettingUpButtons) return;
     137    isSettingUpButtons = true;
     138
    151139    const productList = await fetchProductList();
    152140
    153     $('.single_add_to_cart_button, .add_to_cart_button, .tutor-add-to-cart-button').each(function () {
    154       const button = $(this);
    155       const productId = button.data('product_id') || button.val();
    156       const nativeProductId = productList?.[productId]?.[WTN.isIosApp ? 'appStore' : 'googlePlay'];
    157 
    158       if (nativeProductId) {
    159         const existingClasses = button.attr('class').replace('ajax_add_to_cart', '') || '';
    160         const newButton = $('<button type="button">')
    161           .addClass(`${existingClasses} webtonative-iap`)
    162           .text(btnTexts.buyNow)
    163           .attr('style', button.attr('style'))
    164           .attr('data-product_id', productId)
    165           .data('product_id', productId);
    166 
    167         button.replaceWith(newButton);
    168       }
    169     });
     141    try {
     142      $('.single_add_to_cart_button, .add_to_cart_button, .tutor-add-to-cart-button').each(function () {
     143        const button = $(this);
     144        let productId = button.data('product_id') || button.val();
     145        let nativeProductId = null;
     146        let productType = null;
     147        let isConsumable = null;
     148        let variantId = null;
     149        let isVariant = false;
     150
     151        // Try to get base product data (simple product)
     152        const baseProdData = productList?.[productId];
     153        if (baseProdData) {
     154          nativeProductId = baseProdData[WTN.isIosApp ? 'appStore' : 'googlePlay'];
     155          productType = baseProdData.productType;
     156          isConsumable = baseProdData.isConsumable;
     157        }
     158
     159        // If not found, try to get from variant meta (variable product)
     160        if (typeof window.wtn_variant_meta !== 'undefined') {
     161          const $form = button.closest('form.variations_form');
     162          if ($form.length) {
     163            const variation_id = $form.find('input[name="variation_id"]').val();
     164            if (variation_id && window.wtn_variant_meta[variation_id]) {
     165              const variantMeta = window.wtn_variant_meta[variation_id];
     166              nativeProductId = WTN.isIosApp ? variantMeta.appStore : variantMeta.googlePlay;
     167              productType = variantMeta.productType;
     168              isConsumable = variantMeta.isConsumable;
     169              productId = variantMeta.baseProductId; // Use base product ID for reference
     170              variantId = variation_id;
     171              isVariant = true;
     172            }
     173          }
     174        }
     175
     176        // Only create the button if we have a native product ID
     177        if (nativeProductId) {
     178          const existingClasses = button.attr('class').replace('ajax_add_to_cart', '') || '';
     179          const newButton = $('<button type="button">')
     180            .addClass(`${existingClasses} webtonative-iap`)
     181            .text(btnTexts.buyNow)
     182            .attr('style', button.attr('style'))
     183            .attr('data-product_id', productId)
     184            .attr('data-native_product_id', nativeProductId)
     185            .attr('data-product_type', productType)
     186            .attr('data-is_consumable', isConsumable);
     187
     188          // If this is a variant, add variant-specific attributes
     189          if (isVariant && variantId) {
     190            newButton.attr('data-variant_id', variantId).attr('data-variant_name', window.wtn_variant_meta[variantId].name);
     191          }
     192
     193          button.replaceWith(newButton);
     194        }
     195      });
     196    } finally {
     197      isSettingUpButtons = false;
     198    }
    170199  }
    171200
     
    176205
    177206    const button = $(this);
    178     const productId = button.attr('data-product_id') || button.data('product_id') || button.val();
    179 
    180     // const productList = await fetchProductList();
    181     const nativeProductId = productListCache?.[productId]?.[WTN.isIosApp ? 'appStore' : 'googlePlay'];
    182     if (!nativeProductId) return;
    183207
    184208    if (!isLoggedIn) {
     
    202226    debounce(setupButtons, 300)();
    203227  });
     228
     229  // On variant change, update meta for selected variant
     230  $(document).on('found_variation', 'form.variations_form', async function (event, variation) {
     231    await setupButtons();
     232  });
    204233})(jQuery);
Note: See TracChangeset for help on using the changeset viewer.