1function debounce(func, wait) { 2 let timeout; 3 return function (...args) { 4 clearTimeout(timeout); 5 timeout = setTimeout(() => func.apply(this, args), wait); 6 }; 7} 8 9(async function ($) { 10 if (!WTN.isAndroidApp && !WTN.isIosApp) return; 11 12 const nonce = webtonative_payment_settings.nonce; 13 const resturl = webtonative_payment_settings.rest_url; 14 const isLoggedIn = wtn_biometric_settings.isLoggedIn === '1'; 15 // if (!isLoggedIn) return; 16 17 const isIAPEnabled = wtn_biometric_settings.iap_enabled === '1'; 18 if (!isIAPEnabled) return; 19 20 const appStoreBundleId = wtn_biometric_settings.appStoreBundleId; 21 const appStorePrivateKey = wtn_biometric_settings.appStorePrivateKey; 22 const appStoreIssuerId = wtn_biometric_settings.appStoreIssuerId; 23 const appStoreKeyId = wtn_biometric_settings.appStoreKeyId; 24 25 const btnTexts = { 26 buyNow: wtn_biometric_settings?.btn_buy_now_text ?? 'Buy Now', 27 processing: wtn_biometric_settings?.btn_proccessing_text ?? 'Processing...', 28 success: wtn_biometric_settings?.btn_payment_completed_text ?? 'Payment completed', 29 failed: wtn_biometric_settings?.btn_failed_text ?? 'Payment failed', 30 }; 31 32 let productListCache = null; 33 let isIAPInitialized = false; 34 let isSettingUpButtons = false; 35 36 async function fetchProductList() { 37 if (productListCache) return productListCache; 38 const response = await fetch(`${resturl}/list`); 39 productListCache = await response.json(); 40 return productListCache; 41 } 42 43 function toggleButtonState(button, isProcessing, text) { 44 button.text(text); 45 button.prop('disabled', isProcessing); 46 } 47 48 async function createOrder(orderData) { 49 return $.ajax({ 50 url: `${resturl}/order`, 51 type: 'POST', 52 contentType: 'application/json', 53 data: JSON.stringify(orderData), 54 headers: { 'X-WP-Nonce': nonce }, 55 }); 56 } 57 58 async function processPayment(button) { 59 const productId = button.attr('data-product_id') || button.data('product_id') || button.val(); 60 const nativeProductId = button.attr('data-native_product_id'); 61 62 if (!productId || !nativeProductId) return; 63 64 const productType = button.attr('data-product_type'); 65 const isConsumable = button.attr('data-is_consumable'); 66 const variantId = button.attr('data-variant_id'); 67 68 toggleButtonState(button, true, btnTexts.processing); 69 70 try { 71 isIAPInitialized = true; 72 const productData = { productId: nativeProductId, productType, isConsumable }; 73 74 WTN.inAppPurchase({ 75 ...productData, 76 callback: async (data) => { 77 if (!data.isSuccess) { 78 alert(btnTexts.failed); 79 toggleButtonState(button, false, btnTexts.buyNow); 80 isIAPInitialized = false; 81 return; 82 } 83 84 if (data?.isAlreadyPurchased) { 85 alert('Already purchased'); 86 toggleButtonState(button, false, btnTexts.buyNow); 87 isIAPInitialized = false; 88 return; 89 } 90 91 const orderPayload = { 92 productId, 93 platform: WTN.isAndroidApp ? 'ANDROID' : 'IOS', 94 nativeProductId: nativeProductId, 95 productType: productType, 96 variantId, 97 ...(WTN.isIosApp ? { receiptData: data.receiptData, transactionId: data.transactionId } : data), 98 }; 99 100 try { 101 const orderResponse = await createOrder(orderPayload); 102 if (orderResponse?.success) { 103 toggleButtonState(button, false, btnTexts.success); 104 } 105 isIAPInitialized = false; 106 } catch (orderError) { 107 toggleButtonState(button, false, btnTexts.failed); 108 109 let errorMessage = btnTexts.failed; // Default message 110 if (orderError.status !== undefined) { 111 if (orderError.responseText) { 112 try { 113 const errorData = JSON.parse(orderError.responseText); 114 errorMessage = errorData?.message || errorData?.error || errorMessage; 115 } catch (e) { 116 // If response is not JSON, use it as is (if it's short enough) 117 if (orderError.responseText.length < 50) { 118 errorMessage = orderError.responseText; 119 } 120 } 121 } 122 123 if (orderError.status === 400) { 124 alert(errorMessage); 125 toggleButtonState(button, false, btnTexts.buyNow); 126 } 127 } 128 129 isIAPInitialized = false;
130 } 131 }, 132 }); 133 } catch (error) { 134 toggleButtonState(button, false, btnTexts.failed); 135 isIAPInitialized = false; 136 } 137 } 138 139 async function setupButtons() { 140 if (isSettingUpButtons) return; 141 isSettingUpButtons = true; 142 143 const productList = await fetchProductList(); 144 145 try { 146 $('.single_add_to_cart_button, .add_to_cart_button, .tutor-add-to-cart-button').each(function () { 147 const button = $(this); 148 let productId = button.data('product_id') || button.val(); 149 let nativeProductId = null; 150 let productType = null; 151 let isConsumable = null; 152 let variantId = null; 153 let isVariant = false; 154 155 // Try to get base product data (simple product) 156 const baseProdData = productList?.[productId]; 157 if (baseProdData) { 158 nativeProductId = baseProdData[WTN.isIosApp ? 'appStore' : 'googlePlay']; 159 productType = baseProdData.productType; 160 isConsumable = baseProdData.isConsumable; 161 } 162 163 // If not found, try to get from variant meta (variable product) 164 if (typeof window.wtn_variant_meta !== 'undefined') { 165 const $form = button.closest('form.variations_form'); 166 if ($form.length) { 167 const variation_id = $form.find('input[name="variation_id"]').val(); 168 if (variation_id && window.wtn_variant_meta[variation_id]) { 169 const variantMeta = window.wtn_variant_meta[variation_id]; 170 nativeProductId = WTN.isIosApp ? variantMeta.appStore : variantMeta.googlePlay; 171 productType = variantMeta.productType; 172 isConsumable = variantMeta.isConsumable; 173 productId = variantMeta.baseProductId; // Use base product ID for reference 174 variantId = variation_id; 175 isVariant = true; 176 } 177 } 178 } 179 180 // Only create the button if we have a native product ID 181 if (nativeProductId) { 182 const existingClasses = button.attr('class').replace('ajax_add_to_cart', '') || ''; 183 const newButton = $('<button type="button">') 184 .addClass(`${existingClasses} webtonative-iap`) 185 .text(btnTexts.buyNow) 186 .attr('style', button.attr('style')) 187 .attr('data-product_id', productId) 188 .attr('data-native_product_id', nativeProductId) 189 .attr('data-product_type', productType) 190 .attr('data-is_consumable', isConsumable); 191 192 // If this is a variant, add variant-specific attributes 193 if (isVariant && variantId) { 194 newButton.attr('data-variant_id', variantId).attr('data-variant_name', window.wtn_variant_meta[variantId].name); 195 } 196 197 button.replaceWith(newButton); 198 } 199 }); 200 } finally { 201 isSettingUpButtons = false; 202 } 203 } 204 205 $(document).on('click', '.webtonative-iap', function (e) { 206 e.preventDefault(); 207 e.stopPropagation(); 208 e.stopImmediatePropagation(); 209 210 const button = $(this); 211 212 if (!isLoggedIn) { 213 alert('Please login to continue'); 214 return; 215 } 216 217 if (WTN.isIosApp && (!appStoreBundleId || !appStorePrivateKey || !appStoreIssuerId || !appStoreKeyId)) { 218 alert('Please configure App store settings in settings'); 219 return; 220 } 221 222 processPayment(button); 223 }); 224 225 // Initialize 226 await setupButtons(); 227 228 $(document).ajaxComplete(async function () { 229 if (isIAPInitialized) return; 230 debounce(setupButtons, 300)(); 231 }); 232 233 // On variant change, update meta for selected variant 234 $(document).on('found_variation', 'form.variations_form', async function (event, variation) { 235 await setupButtons(); 236 }); 237})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.