1(function(){ 2 3 // CSRF Token Helper 4 function getCsrfToken() { 5 return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''; 6 } 7 8 // Utility functions 9 function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m])); } 10 11 function getVariantSummaryText(){ 12 const vs = document.getElementById('pd-variant-summary'); 13 if (!vs) return ''; 14 const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean); 15 return items.length ? items.join(' / ') : ''; 16 } 17 18 function getCurrentProductData(){ 19 const form = document.getElementById('pd-buy-form'); 20 if (!form) return null; 21 22 const productId = parseInt(form.dataset.pid || '0', 10); 23 const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1; 24 const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0; 25 26 if (!productId || productId <= 0) { 27 throw new Error('Product ID tidak valid'); 28 } 29 30 if (qty <= 0) { 31 throw new Error('Quantity harus lebih dari 0'); 32 } 33 34 return { productId, qty, variantId }; 35 } 36 37 function createBuyNowPayload(productId, qty, variantId) { 38 // Get product metadata 39 const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || ''); 40 41 // Get correct price, SKU, and weight from selected variant data instead of DOM elements 42 let price = 0; 43 let sku = ''; 44 let weight = 0; 45 let dimensions = null; 46 47 if (variantId > 0 && typeof window.variants !== 'undefined' && Array.isArray(window.variants)) { 48 // Find the selected variant in the variants array 49 const selectedVariant = window.variants.find(v => v.id === variantId); 50 if (selectedVariant) { 51 price = selectedVariant.final || 0; 52 sku = selectedVariant.sku || ''; 53 weight = selectedVariant.weight || 0; 54 55 // Include dimensions if available 56 if (selectedVariant.length_cm || selectedVariant.width_cm || selectedVariant.height_cm) { 57 dimensions = { 58 length_cm: selectedVariant.length_cm || 0, 59 width_cm: selectedVariant.width_cm || 0, 60 height_cm: selectedVariant.height_cm || 0 61 }; 62 } 63 64 // Debug info to verify fix is working 65 if (window.console && window.console.debug) { 66 console.debug('BuyNow: Using variant data', { 67 variantId: variantId, 68 price: price, 69 sku: sku, 70 weight: weight, 71 dimensions: dimensions, 72 variantData: selectedVariant 73 }); 74 } 75 } 76 } 77 78 // Fallback to DOM elements if variant data not found 79 if (price === 0) { 80 const priceTxt = document.getElementById('pd-price-current')?.textContent || ''; 81 price = parseInt(String(priceTxt).replace(/[^\d]/g,''), 10) || 0; 82 83 if (window.console && window.console.debug) { 84 console.debug('BuyNow: Using DOM price fallback', { 85 priceTxt: priceTxt, 86 price: price, 87 variantId: variantId, 88 reason: variantId > 0 ? 'variant_data_not_found' : 'no_variant_selected' 89 }); 90 } 91 } 92 93 if (!sku) { 94 const skuEl = document.getElementById('pd-sku'); 95 sku = skuEl ? skuEl.textContent.trim() : (window.PD_SKU || ''); 96 } 97 98 const imgEl = document.getElementById('pd-main-img'); 99 const image_url = imgEl ? (imgEl.getAttribute('src') || '') : ''; 100 101 const variantText = getVariantSummaryText(); 102 103 // Get pre-order data if product is pre-order 104 const preorderData = window.PD_PREORDER_DATA || null; 105 106 return { 107 product_id: productId, 108 qty: qty, 109 variant_id: variantId, 110 meta: { 111 name, 112 sku, 113 price, 114 image_url, 115 variant_text: variantText, 116 weight: weight, 117 dimensions: dimensions 118 }, 119 is_preorder: preorderData ? 'Y' : 'N', 120 preorder_estimated_arrival: preorderData ? preorderData.estimated_arrival : null, 121 preorder_note: preorderData ? preorderData.note : '' 122 }; 123 } 124 125 /** 126 * Main Buy Now function
127 * Performs validation and redirects to checkout with buy_now mode 128 */ 129 window.buyNow = async function(e) { 130 if (e) e.preventDefault(); 131 132 try { 133 // Check user login status 134 const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' || 135 document.body.classList.contains('logged-in') || 136 window.userLoggedIn === true || 137 document.body.dataset.userLoggedIn === 'true'; 138 139 if (!isLoggedIn) { 140 // Redirect to login with return URL to current page 141 const currentUrl = encodeURIComponent(window.location.pathname + window.location.search); 142 window.location.href = '/login?return=' + currentUrl; 143 return; 144 } 145 146 // Get current product data 147 const { productId, qty, variantId } = getCurrentProductData(); 148 149 // Create payload for buy now 150 const payload = createBuyNowPayload(productId, qty, variantId); 151 152 // Store buy now data in sessionStorage for checkout page 153 sessionStorage.setItem('buyNowData', JSON.stringify(payload)); 154 155 // Redirect to checkout with buy_now mode 156 window.location.href = '/checkout?mode=buy_now'; 157 158 } catch (err) { 159 console.error('Buy Now error:', err); 160 161 if (window.showToast) { 162 window.showToast({ 163 type: 'warn', 164 title: 'Gagal memproses', 165 message: String(err?.message || 'Terjadi kesalahan saat memproses pembelian'), 166 duration: 4500 167 }); 168 } else { 169 alert(err.message || 'Terjadi kesalahan saat memproses pembelian'); 170 } 171 } 172 }; 173 174 /** 175 * Alternative Buy Now with confirmation dialog 176 */ 177 window.buyNowWithConfirm = async function(e) { 178 if (e) e.preventDefault(); 179 180 try { 181 const { productId, qty, variantId } = getCurrentProductData(); 182 183 const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || ''); 184 const variantText = getVariantSummaryText(); 185 const priceTxt = document.getElementById('pd-price-current')?.textContent || 'Rp0'; 186 187 const confirmMessage = `Lanjut ke checkout?\n\nProduk: ${name}${variantText ? `\nVariant: ${variantText}` : ''}\nQty: ${qty}\nHarga: ${priceTxt}`; 188 189 if (!confirm(confirmMessage)) { 190 return; 191 } 192 193 // Proceed with buy now 194 await window.buyNow(e); 195 196 } catch (err) { 197 console.error('Buy Now with confirm error:', err); 198 alert(err.message || 'Terjadi kesalahan'); 199 } 200 }; 201 202 /** 203 * Attach event listener for Buy with Voucher button 204 */ 205 function setupBuyButtons() { 206 // Setup desktop buy button (regular buy now without voucher) 207 const buyBtn = document.getElementById('btn-buy'); 208 if (buyBtn) { 209 buyBtn.addEventListener('click', function(e) { 210 e.preventDefault(); 211 if (buyBtn.disabled) return; 212 if (window.buyNow) { 213 window.buyNow(e); 214 } 215 }); 216 } 217 218 // Setup desktop buy with voucher button 219 const voucherBtn = document.getElementById('btn-buy-voucher'); 220 if (voucherBtn) { 221 voucherBtn.addEventListener('click', function(e) { 222 e.preventDefault(); 223 224 // Validate stock availability before proceeding 225 if (this.disabled || this.getAttribute('data-stock-blocked') === 'true') { 226 const stockReason = this.getAttribute('data-stock-reason') || 'Stok tidak tersedia'; 227 228 if (window.showToast) { 229 window.showToast({ 230 type: 'warn', 231 title: 'Stok Tidak Tersedia', 232 message: stockReason, 233 duration: 3500 234 }); 235 } else { 236 alert(stockReason); 237 } 238 return; 239 } 240 241 const voucherCode = this.dataset.voucherCode; 242 if (voucherCode) { 243 window.buyNowWithVoucher(e, voucherCode); 244 } 245 }); 246 } 247 248 } 249 250 // CRITICAL: Attach mobile button handler at DOCUMENT level with CAPTURE phase 251 // This is the ONLY way to guarantee it fires regardless of other scripts 252 document.addEventListener('click', function(e) { 253 // Check if click is on mobile buy button or its children 254 const target = e.target.closest('#mf-buy'); 255 if (!target) return; // Not our button 256 257 e.preventDefault(); 258 e.stopPropagation(); 259 e.stopImmediatePropagation(); 260 261 if (target.disabled) return; 262
263 // Get voucher code from parent FAB's data attribute 264 const mobileFab = document.getElementById('pd-mobile-fab'); 265 const voucherCode = mobileFab ? (mobileFab.getAttribute('data-voucher-code') || '') : ''; 266 267 if (voucherCode && voucherCode.trim() !== '' && window.buyNowWithVoucher) { 268 window.buyNowWithVoucher(e, voucherCode); 269 } else if (window.buyNow) { 270 window.buyNow(e); 271 } 272 }, true); // CAPTURE PHASE - fires BEFORE any bubble phase handlers 273 274 // Execute immediately if DOM already loaded, otherwise wait 275 if (document.readyState === 'loading') { 276 document.addEventListener('DOMContentLoaded', setupBuyButtons); 277 } else { 278 setupBuyButtons(); 279 } 280 281 /** 282 * Buy Now with Auto-Applied Voucher 283 * Applies voucher to session before redirecting to checkout (like cart does) 284 * 285 * @param {Event} e - Click event 286 * @param {string} voucherCode - Voucher code to apply 287 */ 288 window.buyNowWithVoucher = async function(e, voucherCode) { 289 if (e) e.preventDefault(); 290 291 console.log('[buyNowWithVoucher] Called with voucher code:', voucherCode); 292 293 try { 294 // CRITICAL: Check stock availability first 295 const voucherBtn = document.getElementById('btn-buy-voucher'); 296 if (voucherBtn) { 297 if (voucherBtn.disabled || voucherBtn.getAttribute('data-stock-blocked') === 'true') { 298 const stockReason = voucherBtn.getAttribute('data-stock-reason') || 'Stok tidak tersedia'; 299 300 if (window.showToast) { 301 window.showToast({ 302 type: 'warn', 303 title: 'Stok Tidak Tersedia', 304 message: stockReason, 305 duration: 3500 306 }); 307 } else { 308 alert(stockReason); 309 } 310 return; 311 } 312 } 313 314 // Check login status first 315 const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' || 316 document.body.classList.contains('logged-in') || 317 window.userLoggedIn === true || 318 document.body.dataset.userLoggedIn === 'true'; 319 320 console.log('[buyNowWithVoucher] User logged in:', isLoggedIn); 321 322 if (!isLoggedIn) { 323 const currentUrl = encodeURIComponent(window.location.pathname + window.location.search); 324 window.location.href = '/login?return=' + currentUrl; 325 return; 326 } 327 328 // Get current product data 329 const { productId, qty, variantId } = getCurrentProductData(); 330 const payload = createBuyNowPayload(productId, qty, variantId); 331 332 console.log('[buyNowWithVoucher] Product data:', { productId, qty, variantId }); 333 334 // Get category path for voucher validation 335 const categoryPath = window.PD_CATEGORY_PATH || ''; 336 337 // Step 1: Clear any existing voucher first (important for clean state) 338 console.log('[buyNowWithVoucher] Step 1: Clearing existing voucher...'); 339 try { 340 await fetch('/api/voucher/remove.php', { 341 method: 'POST', 342 headers: { 343 'X-CSRF-Token': getCsrfToken() 344 }, 345 body: JSON.stringify({ 346 csrf_token: getCsrfToken() 347 }), 348 credentials: 'same-origin' 349 }); 350 } catch (e) { 351 // Ignore error if no voucher exists 352 console.debug('No existing voucher to remove or removal failed'); 353 } 354 355 // Step 2: Apply voucher to session (like cart does) 356 console.log('[buyNowWithVoucher] Step 2: Applying voucher to session...'); 357 const applyRes = await fetch('/api/voucher/apply.php', { 358 method: 'POST', 359 headers: { 360 'Content-Type': 'application/json', 361 'X-CSRF-Token': getCsrfToken() 362 }, 363 credentials: 'same-origin', 364 body: JSON.stringify({ 365 csrf_token: getCsrfToken(), 366 code: voucherCode, 367 items: [{ 368 pid: productId, 369 price: payload.meta.price, 370 qty: qty, 371 category_path: categoryPath 372 }], 373 shipping_cost: null // Will be calculated in checkout 374 }) 375 }); 376 377 const applyData = await applyRes.json(); 378 379 console.log('[buyNowWithVoucher] Voucher apply response:', applyData); 380 381 if (!applyData.success) { 382 // Voucher invalid - show error and fallback to regular buy now 383 console.warn('[buyNowWithVoucher] Voucher application failed:', applyData.message); 384 if (window.showToast) { 385 window.showToast({ 386 type: 'error', 387 title: 'Voucher Tidak Valid', 388 message: applyData.message || 'Voucher tidak dapat diterapkan untuk produk ini', 389 duration: 4000 390 }); 391 } else { 392 alert(applyData.message || 'Voucher tidak dapat diterapkan'); 393 } 394 395 // Fallback to regular buy now without voucher 396 console.log('Voucher failed, proceeding with regular buy now'); 397 return window.buyNow(e); 398 } 399 400 // Step 3: Store buy now data in sessionStorage 401 console.log('[buyNowWithVoucher] Step 3: Storing buy now data in sessionStorage'); 402 sessionStorage.setItem('buyNowData', JSON.stringify(payload)); 403 404 // Step 4: Redirect to checkout 405 // Voucher sudah di session, checkout akan auto-load via loadAppliedVoucher() 406 console.log('[buyNowWithVoucher] Step 4: Redirecting to checkout with voucher applied'); 407 window.location.href = '/checkout?mode=buy_now'; 408 409 } catch (err) { 410 console.error('Buy Now with Voucher error:', err); 411 412 if (window.showToast) { 413 window.showToast({ 414 type: 'error', 415 title: 'Terjadi Kesalahan', 416 message: 'Gagal memproses pembelian dengan voucher. Silakan coba lagi.', 417 duration: 3000 418 }); 419 } else { 420 alert('Gagal memproses pembelian. Silakan coba lagi.'); 421 } 422 } 423 }; 424})();
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.