Changeset 3322172
- Timestamp:
- 07/04/2025 09:06:47 AM (8 months ago)
- Location:
- webtonative/trunk
- Files:
-
- 6 edited
-
README.md (modified) (1 diff)
-
admin/iap/rest.php (modified) (9 diffs)
-
admin/iap/woo-commerce.php (modified) (2 diffs)
-
index.php (modified) (1 diff)
-
website/iap.php (modified) (3 diffs)
-
website/scripts/woocommerce.js (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
webtonative/trunk/README.md
r3313015 r3322172 4 4 Requires at least: 2.0.2 5 5 Tested up to: 6.7.1 6 Stable tag: 2.8. 76 Stable tag: 2.8.8 7 7 License: GPLv2 or later 8 8 -
webtonative/trunk/admin/iap/rest.php
r3313015 r3322172 117 117 $expiration_ms = intval($latest['expires_date_ms'] ?? 0); 118 118 $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) { 121 152 return [ 122 153 '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', 126 155 'data' => $latest, 127 156 ]; 128 157 } 129 158 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 130 169 return [ 131 170 'success' => true, 132 'message' => ' ',171 'message' => 'New subscription validated', 133 172 'data' => $latest, 173 'transaction_id' => $transaction_id, 174 'original_transaction_id' => $original_transaction_id, 134 175 ]; 135 176 } else { … … 177 218 178 219 // data from google play 220 $variant_id = $request_body['variantId']; 179 221 $native_order_id = $request_body['receiptData']['orderId']; 180 222 $native_product_id = $request_body['nativeProductId']; … … 202 244 'customer_id' => $current_user->ID, 203 245 )); 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); 205 253 $order->set_payment_method('webtonative'); 206 254 $order->set_status('wc-completed'); … … 227 275 228 276 $response = array( 229 'status' => 'success', 277 'success' => true, 278 'message' => 'ok' 230 279 ); 231 280 return new WP_REST_Response($response, 200); … … 235 284 { 236 285 // data from apple 286 $variant_id = $request_body['variantId']; 237 287 $receipt_data = $request_body['receiptData']; 238 288 $native_product_id = $request_body['nativeProductId']; … … 260 310 } 261 311 312 $verify_data = $is_valid['data'] ?? []; 262 313 $product_data = wc_get_product($product_id); 263 314 $order = wc_create_order(array( 264 315 'customer_id' => $current_user->ID, 265 316 )); 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); 267 324 $order->set_payment_method('webtonative'); 268 325 $order->set_status('wc-completed'); … … 283 340 $native_order = array( 284 341 'productId' => $native_product_id, 285 'receiptData' => $receipt_data,342 'receiptData' => is_scalar($verify_data) ? strval($verify_data) : json_encode($verify_data), 286 343 'platform' => 'IOS', 287 344 ); … … 291 348 $order->add_order_note($note); 292 349 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 } 293 356 294 357 $response = ['success' => true, 'message' => $is_valid['message']]; … … 481 544 return new WP_REST_Response($arr, 200); 482 545 } 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 } 483 564 } -
webtonative/trunk/admin/iap/woo-commerce.php
r3034141 r3322172 11 11 { 12 12 add_action('woocommerce_product_options_general_product_data', array($this, 'add_fields')); 13 13 14 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); 14 68 } 15 69 … … 142 196 * You will need it if you want your custom credit card form 143 197 */ 144 public function payment_fields() 145 { 146 } 198 public function payment_fields() {} 147 199 148 200 /* 149 201 * Custom CSS and JS, in most cases required only when you decided to go with a custom credit card form 150 202 */ 151 public function payment_scripts() 152 { 153 } 203 public function payment_scripts() {} 154 204 155 205 /* 156 206 * Fields validation 157 207 */ 158 public function validate_fields() 159 { 160 } 208 public function validate_fields() {} 161 209 162 210 /* 163 211 * We're processing the payments here 164 212 */ 165 public function process_payment($order_id) 166 { 167 } 213 public function process_payment($order_id) {} 168 214 169 215 /* 170 216 * In case you need a webhook 171 217 */ 172 public function webhook() 173 { 174 } 218 public function webhook() {} 175 219 } 176 220 } -
webtonative/trunk/index.php
r3313015 r3322172 3 3 Plugin Name: webtonative 4 4 Description: webtonative Plugin 5 Version: 2.8. 75 Version: 2.8.8 6 6 Author: webtonative 7 7 */ -
webtonative/trunk/website/iap.php
r3301312 r3322172 12 12 { 13 13 add_action('wp_enqueue_scripts', array($this, 'load_scripts')); 14 add_action('wp_footer', [$this, 'output_variant_meta_js']); 14 15 add_shortcode('webtonative_buy_now', array($this, 'shortcode')); 15 16 new WebtonativeIAPRestController(); … … 21 22 $webtonative_settings = get_option('woocommerce_webtonative_settings'); 22 23 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); 24 25 wp_localize_script('webtonative-iap', 'wtn_biometric_settings', array( 25 26 'btn_proccessing_text' => $saved_settings['processing_button_text'], … … 50 51 return $output; 51 52 } 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 } 52 95 } -
webtonative/trunk/website/scripts/woocommerce.js
r3301312 r3322172 28 28 let productListCache = null; 29 29 let isIAPInitialized = false; 30 let isSettingUpButtons = false; 30 31 31 32 async function fetchProductList() { … … 34 35 productListCache = await response.json(); 35 36 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 });57 37 } 58 38 … … 74 54 async function processPayment(button) { 75 55 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'); 77 63 78 64 toggleButtonState(button, true, btnTexts.processing); … … 80 66 try { 81 67 isIAPInitialized = true; 82 const productData = await getProductIds(productId);68 const productData = { productId: nativeProductId, productType, isConsumable }; 83 69 84 70 WTN.inAppPurchase({ … … 102 88 productId, 103 89 platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS', 104 nativeProductId: productData.productId, 105 productType: productData.productType, 90 nativeProductId: nativeProductId, 91 productType: productType, 92 variantId, 106 93 ...(WTN.isIosApp ? { receiptData: data.receiptData } : data), 107 94 }; … … 114 101 isIAPInitialized = false; 115 102 } catch (orderError) { 116 console.log('Order Error:', orderError);117 103 toggleButtonState(button, false, btnTexts.failed); 118 104 … … 142 128 }); 143 129 } catch (error) { 144 console.error('Error in processPayment:', error);145 130 toggleButtonState(button, false, btnTexts.failed); 146 131 isIAPInitialized = false; … … 149 134 150 135 async function setupButtons() { 136 if (isSettingUpButtons) return; 137 isSettingUpButtons = true; 138 151 139 const productList = await fetchProductList(); 152 140 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 } 170 199 } 171 200 … … 176 205 177 206 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;183 207 184 208 if (!isLoggedIn) { … … 202 226 debounce(setupButtons, 300)(); 203 227 }); 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 }); 204 233 })(jQuery);
Note: See TracChangeset
for help on using the changeset viewer.