1var ppecHandler = function( data ) { 2 this.data = data; 3 this.actions = {}; 4 5 const parent = this; 6 7 // TODO: Need to convert this to vanilla js 8 this.processPayment = function( payment, action ) { 9 jQuery.post( ppecFrontVars.ajaxUrl, { 10 'action': action, 11 wp_ppdg_payment: payment, 12 data: parent.data, 13 nonce: parent.data.nonce 14 } ) 15 .done( function( data ) { 16 parent.completePayment( data ); 17 } ); 18 }; 19 20 this.completePayment = function( data ) { 21 let ret = true; 22 let dlgTitle = ppecFrontVars.str.paymentCompleted; 23 let dlgMsg = ppecFrontVars.str.redirectMsg; 24 let redirect_url; 25 if ( data.redirect_url ) { 26 redirect_url = data.redirect_url; 27 } else { 28 dlgTitle = ppecFrontVars.str.errorOccurred; 29 dlgMsg = data; 30 ret = false; 31 } 32 document.querySelector( '.wp-ppec-overlay[data-ppec-button-id="' + parent.data.id + '"]' ).style.display = 'none'; 33 34 const dialogMsgParagraph = document.createElement('p'); 35 dialogMsgParagraph.id = 'wp-ppdg-dialog-msg'; 36 dialogMsgParagraph.textContent = dlgMsg; 37 const dialog = document.createElement('div'); 38 dialog.id = 'wp-ppdg-dialog-message'; 39 dialog.title = dlgTitle; 40 dialog.appendChild(dialogMsgParagraph); 41 42 parent.scCont?.querySelector('.wp-ppec-button-container')?.insertAdjacentElement('afterend', dialog); 43 44 // Fade in dialog 45 dialog.style.display = 'none'; 46 dialog.style.opacity = 0; 47 dialog.style.transition = 'opacity 0.5s'; 48 dialog.style.display = 'block'; 49 setTimeout(() => { 50 dialog.style.opacity = 1; 51 }, 10); 52 53 if ( redirect_url ) { 54 location.href = redirect_url; 55 } 56 return ret; 57 }; 58 59 this.ValidatorTos = function( input ) { 60 return input.checked !== true ? ppecFrontVars.str.acceptTos : ''; 61 }; 62 63 this.ValidatorBilling = function( input ) { 64 let val; 65 if (input.type === 'checkbox'){ 66 val = input.checked; 67 } else { 68 val = Boolean(input.value.trim()) 69 } 70 71 return parent.isElementVisible(input) && !val ? ppecFrontVars.str.required : null; 72 }; 73 74 this.isValidTotal = function() { 75 parent.calcTotal(); 76 return !!parent.data.total; 77 }; 78 79 this.ValidatorQuantity = function( input ) { 80 const val_orig = input.value; 81 const val = parseInt( val_orig ); 82 let error = false; 83 if ( isNaN( val ) || ( val_orig % 1 !== 0 ) || val <= 0 ) { 84 error = ppecFrontVars.str.enterQuantity; 85 } else if ( parent.data.stock_enabled && val > parent.data.stock_items ) { 86 error = ppecFrontVars.str.stockErr.replace( '%d', parent.data.stock_items ); 87 } else { 88 parent.data.quantity = val; 89 } 90 return error; 91 }; 92 93 this.ValidatorAmount = function( input ) { 94 const val_orig = input.value; 95 const val = parseFloat( val_orig ); 96 const min_val = input.getAttribute( 'min' ); 97 let error = false; 98 const errMsg = ppecFrontVars.str.enterAmount; 99 if ( !isNaN( val ) && min_val <= val ) { 100 parent.data.orig_price = val; 101 parent.data.price = parent.applyVariations( val ); 102 } else { 103 error = errMsg; 104 } 105 return error; 106 }; 107 108 if ( this.data.btnStyle.layout === 'horizontal' ) { 109 this.data.btnStyle.tagline = false; 110 } 111 112 this.clientVars = {}; 113 114 this.clientVars[ this.data.env ] = this.data.client_id; 115 116 this.inputs = []; 117 118 this.validateInput = function( input, validator ) { 119 if ( !input ) { 120 return false; 121 } 122 123 this.inputs.push( [ input, validator ] ); 124 125 input.addEventListener('change', function(e) { 126 parent.displayInputError( this, validator ); 127 parent.validateOrder(); 128 }) 129 }; 130 131 this.validateOrder = function() { 132 let enable_actions = true; 133 134 document.querySelectorAll('#wpec_billing_' + parent.data.id + ', #place-order-' + parent.data.id ).forEach(el => { 135 parent.toggleVisibility(el, 'block', !parent.isValidTotal()); 136 }) 137 138 parent.toggleVisibility( 139 document.getElementById( parent.data.id ), 140 'block', 141 !parent.isElementVisible(document.getElementById( 'place-order-' + parent.data.id )) 142 ); 143 144 parent.inputs.forEach( function( inputArr ) { 145 const input = inputArr[ 0 ]; 146 const validator = inputArr[ 1 ]; 147 if ( validator( input ) ) { 148 enable_actions = false; 149 } 150 } ); 151 152 if ( !parent.isValidTotal() ) { 153 enable_actions = false; 154 } 155 156 if ( enable_actions ) { 157 parent.actions.enable(); 158 } else { 159 parent.actions.disable(); 160 } 161
162 parent.updateAllAmounts(); 163 164 // Dispatch a custom event for addons to listen on 'validateOrder' function run. 165 document.dispatchEvent(new CustomEvent('wpec_validate_order', { 166 detail: { 167 ppecHandler: parent 168 } 169 })) 170 jQuery( document ).trigger( 'wpec_validate_order', [ parent ] ); // TODO: Need to remove this jquery version after all addon code is updated to the vanilla version. 171 }; 172 173 this.displayInputError = function( input, validator ) { 174 const error = validator( input ); 175 const errMsgCont = input.type === 'checkbox' || input.type === 'number' ? input.parentNode.parentNode.querySelector( '.wp-ppec-form-error-msg' ) : input.parentNode.querySelector( '.wp-ppec-form-error-msg' ); 176 177 if (error && error.length){ 178 input.classList.add('hasError'); 179 errMsgCont.style.display = 'block'; 180 errMsgCont.innerText = error; 181 }else { 182 input.classList.remove('hasError'); 183 errMsgCont.style.display = 'none'; 184 errMsgCont.innerText = ''; 185 } 186 }; 187 188 this.displayErrors = function() { 189 parent.inputs.forEach( function( input ) { 190 parent.displayInputError( input[ 0 ], input[ 1 ] ); 191 } ); 192 }; 193 194 this.scCont = document.querySelector( '.wp-ppec-shortcode-container[data-ppec-button-id="' + parent.data.id + '"]' ); 195 196 this.buttonArgs = { 197 env: parent.data.env, 198 client: parent.clientVars, 199 style: parent.data.btnStyle, 200 commit: true, 201 onInit: function( data, actions ) { 202 parent.actions = actions; 203 parent.validateInput( document.getElementById( 'wpec-tos-' + parent.data.id ), parent.ValidatorTos ); 204 parent.validateInput( document.querySelector( '#wp-ppec-custom-quantity[data-ppec-button-id="' + parent.data.id + '"]' ), parent.ValidatorQuantity ); 205 parent.validateInput( document.querySelector( '#wp-ppec-custom-amount[data-ppec-button-id="' + parent.data.id + '"]' ), parent.ValidatorAmount ); 206 document.querySelectorAll( '#wpec_billing_' + parent.data.id + ' .wpec_required' ).forEach( function(element) { 207 parent.validateInput( element, parent.ValidatorBilling ); 208 } ); 209 parent.data.orig_price = parseFloat( parent.data.price ); 210 parent.scCont.querySelectorAll( 'select.wpec-product-variations-select, input.wpec-product-variations-select-radio' )?.forEach(function (item){ 211 item.addEventListener('change', function (e){ 212 const grpId = e.currentTarget.getAttribute( 'data-wpec-variations-group-id' ); 213 const varId = e.currentTarget.value; 214 if ( Object.getOwnPropertyNames( parent.data.variations ).length !== 0 ) { 215 if ( !parent.data.variations.applied ) { 216 parent.data.variations.applied = []; 217 } 218 parent.data.variations.applied[ grpId ] = varId; 219 parent.data.price = parent.applyVariations( parent.data.orig_price ); 220 parent.validateOrder(); 221 } 222 }) 223 }); 224 parent.scCont.querySelectorAll( 'select.wpec-product-variations-select, input.wpec-product-variations-select-radio:checked' )?.forEach(el => { 225 // Dispatch change event to trigger any 'change' event listeners on this element 226 el.dispatchEvent( new Event('change') ); 227 }) 228 229 parent.scCont.querySelector( '.wpec_product_shipping_enable' )?.addEventListener( 'change', function() { 230 parent.toggleVisibility(parent.scCont.querySelector( '.wpec_shipping_address_container' ), 'inherit'); 231 parent.scCont.querySelector( '.wpec_address_wrap' )?.classList.toggle('shipping_enabled'); 232 } ); 233 234 document.getElementById( 'place-order-' + parent.data.id )?.addEventListener('click', function() { 235 parent.buttonArgs.onClick(); 236 } ); 237 238 document.getElementById( 'wpec-redeem-coupon-btn-' + parent.data.id )?.addEventListener('click', async function (e) { 239 e.preventDefault(); 240 const wpecCouponBtn = e.currentTarget; 241 const wpecCouponInputField = document.getElementById('wpec-coupon-field-' + parent.data.id) 242 const couponCode = wpecCouponInputField?.value; 243 if (couponCode.trim() === '') { 244 return false; 245 } 246 const wpecCouponSpinner = wpecCouponBtn.querySelector('svg'); 247 wpecCouponBtn.setAttribute('disabled', true); 248 wpecCouponSpinner.style.display = 'inline'; 249 250 const ajaxData = new URLSearchParams({ 251 'action': 'wpec_check_coupon', 252 'product_id': parent.data.product_id, 253 'coupon_code': couponCode, 254 'curr': parent.data.currency, 255 /*'amount': parent.data.item_price, 256 'tax': parent.data.tax, 257 'shipping': parent.data.shipping*/ 258 });
259 try { 260 const response = await fetch(ppecFrontVars.ajaxUrl, { 261 method: "post", 262 headers: { 263 'Content-Type': 'application/x-www-form-urlencoded' 264 }, 265 body: ajaxData 266 }); 267 268 const response_data = await response.json(); 269 270 if (response_data.success) { 271 parent.data.discount = response_data.discount; 272 parent.data.discountType = response_data.discountType; 273 parent.data.couponCode = response_data.code; 274 275 document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = '<span class="wpec_coupon_code">' + response_data.discountStr + '</span><button class="wpec_coupon_apply_btn" id="wpec-remove-coupon-' + parent.data.id + '" title="' + ppecFrontVars.str.strRemoveCoupon + '">' + ppecFrontVars.str.strRemove + '</button>' 276 document.getElementById('wpec-redeem-coupon-btn-' + parent.data.id).style.display = 'none'; 277 document.getElementById('wpec-coupon-field-' + parent.data.id).style.display = 'none'; 278 279 const totalContainers = parent.getPriceContainer(); 280 // Note: there is more than one price container in a single product shortcode. 281 totalContainers.forEach(function (totalCont){ 282 let totCurr; 283 let totNew; 284 if (totalCont.querySelector('.wpec_price_full_total')) { 285 totCurr = totalCont.querySelector('span.wpec_tot_current_price') 286 totCurr?.classList.add('wpec_line_through'); 287 totNew = totalCont.querySelector('span.wpec_tot_new_price'); 288 } else { 289 totCurr = totalCont.querySelector('span.wpec_price_amount') 290 totCurr?.classList.add('wpec_line_through'); 291 totNew = totalCont.querySelector('span.wpec_new_price_amount'); 292 } 293 294 const priceCurr = totalCont.querySelector('span.wpec-price-amount') 295 priceCurr?.classList.add('wpec_line_through'); 296 297 const priceNew = totalCont.querySelector('span.wpec-new-price-amount'); 298 parent.validateOrder(); 299 300 document.getElementById('wpec-remove-coupon-' + parent.data.id)?.addEventListener('click', function (e) { 301 e.preventDefault(); 302 document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = ''; 303 document.getElementById('wpec-coupon-field-' + parent.data.id).value = ''; 304 document.getElementById('wpec-coupon-field-' + parent.data.id).style.display = 'block'; 305 document.getElementById('wpec-redeem-coupon-btn-' + parent.data.id).style.display = 'block'; 306 totCurr?.classList.remove('wpec_line_through'); 307 priceCurr?.classList.remove('wpec_line_through'); 308 if (totNew) { 309 totNew.innerHTML = ''; 310 } 311 if (priceNew) { 312 priceNew.innerHTML = ''; 313 } 314 delete parent.data.discount; 315 delete parent.data.discountType; 316 delete parent.data.couponCode; 317 delete parent.data.discountAmount; 318 delete parent.data.newPrice; 319 parent.validateOrder(); 320 }); 321 }) 322 } else { 323 document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = response_data.msg 324 } 325 wpecCouponSpinner.style.display = 'none'; 326 wpecCouponBtn.removeAttribute('disabled'); 327 328 } catch (err) { 329 alert(err.message); 330 } 331 }) 332 333 const couponField = document.getElementById( 'wpec-coupon-field-' + parent.data.id ); 334 couponField?.addEventListener( 'keydown', function( keyboardEvent ) { 335 if ( keyboardEvent.code === 13 ) { 336 keyboardEvent.preventDefault(); 337 document.getElementById( 'wpec-redeem-coupon-btn-' + parent.data.id ).click(); 338 return false; 339 } 340 } ); 341 342 parent.validateOrder(); 343 344 if ( parent.data.errors ) { 345 const errorEl = document.createElement('div'); 346 errorEl.className = 'wp-ppec-form-error-msg'; 347 errorEl.innerHTML = parent.data.errors; 348 349 parent.scCont.appendChild( errorEl ); 350 errorEl.style.display = 'block'; 351 } 352 353 document.querySelectorAll( '.wpec-button-placeholder' ).forEach( function (el) { 354 el.remove() 355 }) 356 }, 357 onClick: function() { 358 const wpecPlaceOrderButtonSection = document.getElementById("place-order-"+parent.data.id); 359 if (wpecPlaceOrderButtonSection){ 360 wpecPlaceOrderButtonSection.querySelector("button>svg").style.display = 'inline'; 361 wpecPlaceOrderButtonSection.querySelector("button").setAttribute("disabled", true); 362 } 363 364 parent.displayErrors(); 365 // Get first error input if there is any. 366 const errInput = parent.scCont.querySelector( '.hasError' ); 367 if ( errInput ) { 368 errInput.focus(); 369 errInput.dispatchEvent( new Event('change') ); 370 371 if (wpecPlaceOrderButtonSection) { 372 wpecPlaceOrderButtonSection.querySelector("button>svg").style.display = 'none'; 373 wpecPlaceOrderButtonSection.querySelector("button").removeAttribute("disabled"); 374 } 375 } else if ( !parent.data.total ) { 376 const same_shipping_billing_address_enabled = parent.scCont.querySelector( '.wpec_product_shipping_enable' )?.checked; 377 const billing_address = { 378 address_line_1: document.getElementById( 'wpec_billing_address-' + parent.data.id )?.value, 379 admin_area_1: document.getElementById( 'wpec_billing_state-' + parent.data.id )?.value, 380 admin_area_2: document.getElementById( 'wpec_billing_city-'
380+ parent.data.id )?.value, 381 postal_code: document.getElementById( 'wpec_billing_postal_code-' + parent.data.id )?.value, 382 country_code: document.getElementById( 'wpec_billing_country-' + parent.data.id )?.value, 383 }; 384 const shipping_address = same_shipping_billing_address_enabled ? billing_address : { 385 address_line_1: document.getElementById( 'wpec_shipping_address-' + parent.data.id )?.value, 386 admin_area_1: document.getElementById( 'wpec_shipping_state-' + parent.data.id )?.value, 387 admin_area_2: document.getElementById( 'wpec_shipping_city-' + parent.data.id )?.value, 388 postal_code: document.getElementById( 'wpec_shipping_postal_code-' + parent.data.id )?.value, 389 country_code: document.getElementById( 'wpec_shipping_country-' + parent.data.id )?.value, 390 }; 391 392 const paymentData = { 393 payer: { 394 name: { 395 given_name: document.getElementById( 'wpec_billing_first_name-' + parent.data.id )?.value, 396 surname: document.getElementById( 'wpec_billing_last_name-' + parent.data.id )?.value, 397 }, 398 email_address: document.getElementById( 'wpec_billing_email-' + parent.data.id )?.value, 399 address: billing_address, 400 shipping_address: shipping_address, 401 } 402 } 403 404 document.dispatchEvent(new CustomEvent('wpec_process_free_payment', { 405 detail: { 406 paymentData, 407 data: parent.data, 408 } 409 })); 410 411 parent.processPayment(paymentData, 'wpec_process_empty_payment' ); 412 } 413 }, 414 createOrder: async function( data, actions ) { 415 parent.calcTotal(); 416 417 //We need to round to 2 decimal places to make sure that the API call will not fail. 418 let itemTotalValueRounded = (parent.data.price * parent.data.quantity).toFixed(2); 419 let itemTotalValueRoundedAsNumber = parseFloat(itemTotalValueRounded); 420 //console.log('Item total value rounded: ' + itemTotalValueRoundedAsNumber); 421 //console.log(parent.data); 422 423 // Checking if shipping will be required 424 let shipping_pref = 'NO_SHIPPING'; // Default value 425 if (parent.data.shipping !== "" || parent.data.shipping_enable === true) { 426 console.log("The physical product checkbox is enabled or there is shipping value has been configured. Setting the shipping preference to collect shipping."); 427 shipping_pref = 'GET_FROM_FILE'; 428 } 429 430 // Create order_data object to be sent to the server. 431 const order_data = { 432 intent: 'CAPTURE', 433 payment_source: { 434 paypal: { 435 experience_context: { 436 payment_method_preference: 'IMMEDIATE_PAYMENT_REQUIRED', 437 shipping_preference: shipping_pref, 438 user_action: 'PAY_NOW', 439 } 440 } 441 }, 442 purchase_units: [ { 443 amount: { 444 value: parent.data.total, 445 currency_code: parent.data.currency, 446 breakdown: { 447 item_total: { 448 currency_code: parent.data.currency, 449 value: itemTotalValueRoundedAsNumber 450 } 451 } 452 }, 453 items: [ { 454 name: parent.data.name, 455 quantity: parent.data.quantity, 456 unit_amount: { 457 value: parent.data.price, 458 currency_code: parent.data.currency 459 } 460 } ] 461 } ] 462 }; 463 464 if ( parent.data.tax ) { 465 order_data.purchase_units[ 0 ].amount.breakdown.tax_total = { 466 currency_code: parent.data.currency, 467 value: parent.PHP_round( parent.data.tax_amount * parent.data.quantity, parent.data.dec_num ) 468 }; 469 order_data.purchase_units[ 0 ].items[ 0 ].tax = { 470 currency_code: parent.data.currency, 471 value: parent.data.tax_amount 472 }; 473 } 474 if ( parent.data.shipping ) { 475 order_data.purchase_units[ 0 ].amount.breakdown.shipping = { 476 currency_code: parent.data.currency, 477 value: parent.getTotalShippingCost(), 478 }; 479 } 480 if ( parent.data.discount ) { 481 order_data.purchase_units[ 0 ].amount.breakdown.discount = { 482 currency_code: parent.data.currency, 483 value: parseFloat( parent.data.discountAmount ) 484 }; 485 } 486 //End of create order_data object. 487 console.log('Order data: ', order_data); 488 489 let nonce = wpec_create_order_vars.nonce; 490 491 let wpec_data_for_create = parent.data;//parent.data is the data object that was passed to the ppecHandler constructor. 492 console.log('WPEC data for create-order: ', wpec_data_for_create); 493 494 let post_data = 'action=wpec_pp_create_order&data=' + encodeURIComponent(JSON.stringify(order_data)) + '&wpec_data=' + encodeURIComponent(JSON.stringify(wpec_data_for_create)) + '&_wpnonce=' + nonce; 495 try { 496 // Using fetch for AJAX request. This is supported in all modern browsers.
497 const response = await fetch( ppecFrontVars.ajaxUrl, { 498 method: "post", 499 headers: { 500 'Content-Type': 'application/x-www-form-urlencoded' 501 }, 502 body: post_data 503 }); 504 505 const response_data = await response.json(); 506 507 if (response_data.order_id) { 508 console.log('Create-order API call to PayPal completed successfully.'); 509 return response_data.order_id; 510 } else { 511 const error_message = response_data.err_msg 512 console.error('Error occurred during create-order call to PayPal. ', error_message); 513 throw new Error(error_message);//This will trigger the alert in the "catch" block below. 514 } 515 } catch (error) { 516 console.error(error.message); 517 alert('Could not initiate PayPal Checkout...\n\n' + error.message); 518 } 519 }, 520 onApprove: async function( data, actions ) { 521 document.querySelector( 'div.wp-ppec-overlay[data-ppec-button-id="' + parent.data.id + '"]' ).style.display = 'flex'; 522 523 console.log('Setting up the AJAX request for capture-order call.'); 524 525 // Create the data object to be sent to the server. 526 let pp_bn_data = {}; 527 pp_bn_data.order_id = data.orderID;//The orderID is the ID of the order that was created in the createOrder method. 528 let wpec_data = parent.data;//parent.data is the data object that was passed to the ppecHandler constructor. 529 //console.log('WPEC data (JSON): ' + JSON.stringify(wpec_data)); 530 531 let nonce = wpec_on_approve_vars.nonce; 532 let post_data = 'action=wpec_pp_capture_order&data=' + encodeURIComponent(JSON.stringify(pp_bn_data)) + '&wpec_data=' + encodeURIComponent(JSON.stringify(wpec_data)) + '&_wpnonce=' + nonce; 533 try { 534 const response = await fetch( ppecFrontVars.ajaxUrl, { 535 method: "post", 536 headers: { 537 'Content-Type': 'application/x-www-form-urlencoded' 538 }, 539 body: post_data 540 }); 541 542 const response_data = await response.json(); 543 544 console.log('Capture-order API call to PayPal completed successfully.'); 545 546 // Call the completePayment method to do any redirection or display a message to the user. 547 parent.completePayment(response_data); 548 549 } catch (error) { 550 console.error(error); 551 alert('PayPal returned an error! Transaction could not be processed. Enable the debug logging feature to get more details...\n\n' + JSON.stringify(error)); 552 } 553 554 }, 555 onError: function( err ) { 556 document.getElementById("place-order-"+parent.data.id).querySelector("button>svg").style.display = 'none'; 557 document.getElementById("place-order-"+parent.data.id).querySelector("button").removeAttribute("disabled"); 558 alert( err ); 559 } 560 }; 561 562 // TODO: Need to convert this to vanilla js 563 this.formatMoney = function( n ) { 564 var decimalPlaces = isNaN(decimalPlaces = Math.abs(parent.data.dec_num)) ? 2 : parent.data.dec_num; 565 var decimalSeparator = parent.data.dec_sep || "."; 566 var thousandSeparator = parent.data.thousand_sep || ","; 567 var sign = n < 0 ? "-" : ""; 568 var integerPart = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(decimalPlaces))); 569 var integerPartLength = integerPart.length; 570 var remainderLength = integerPartLength > 3 ? integerPartLength % 3 : 0; 571 572 var formattedIntegerPart = sign + 573 (remainderLength ? integerPart.substr(0, remainderLength) + thousandSeparator : "") + 574 integerPart.substr(remainderLength).replace(/(\d{3})(?=\d)/g, "$1" + thousandSeparator) + 575 (decimalPlaces ? decimalSeparator + Math.abs(n - integerPart).toFixed(decimalPlaces).slice(2) : ""); 576 577 var formats = { 578 left: '{symbol}{price}', 579 left_space: '{symbol} {price}', 580 right: '{price}{symbol}', 581 right_space: '{price} {symbol}' 582 }; 583 584 var formattedPrice = formats[parent.data.curr_pos] 585 .replace('{symbol}', parent.data.currency_symbol) 586 .replace('{price}', formattedIntegerPart); 587 588 return formattedPrice; 589 }; 590 591 this.getPriceContainer = function() { 592 // Here, we're using 'querySelectorAll' instead of 'querySelector', because there is more than one element. 593 return document.querySelectorAll( '.' + parent.scCont.getAttribute( 'data-price-class' ) ); 594 }; 595 596 this.getProductItemContainer = function() { 597 // Get the product item container using the price container, so that we don't select other product item container when more than one same product shortcode in a page. 598 const priceContainer = document.querySelector( '.' + parent.scCont.getAttribute( 'data-
598price-class' ) ); 599 600 // Selector of the container may vary across different templates. Select the one it can find. 601 602 // For template 1,3 603 let productItemContainer = parent.closestParent(priceContainer, '.wpec-product-item-' + parent.data.product_id ); 604 605 // For template 2 606 if (!productItemContainer){ 607 productItemContainer = parent.closestParent(priceContainer, '.wpec-post-item-' + parent.data.product_id ); 608 } 609 610 // For url payment form 611 if (!productItemContainer){ 612 productItemContainer = parent.closestParent(priceContainer, '.wpec-modal-product-' + parent.data.product_id ); 613 } 614 615 return productItemContainer; 616 }; 617 618 this.updateAllAmounts = function() { 619 parent.calcTotal(); 620 621 const productItemContainer = parent.getProductItemContainer(); 622 623 if ( productItemContainer ){ 624 productItemContainer.querySelectorAll( '.wpec-price-amount' ).forEach(el => el.textContent = parent.formatMoney( parent.data.price )) 625 626 if ( parent.data.quantity > 1 ) { 627 productItemContainer.querySelectorAll( '.wpec-quantity' ).forEach(el => el.style.display = 'inline') 628 productItemContainer.querySelectorAll( '.wpec-quantity-val' ).forEach(el => el.textContent = parent.data.quantity) 629 } else { 630 productItemContainer.querySelectorAll( '.wpec-quantity' ).forEach(el => el.style.display = 'none'); 631 } 632 633 const total = productItemContainer.querySelectorAll( '.wpec_tot_current_price' ); 634 const tot_new = productItemContainer.querySelectorAll( '.wpec_tot_new_price' ); 635 const price_new = productItemContainer.querySelectorAll( '.wpec-new-price-amount' ); 636 637 if ( typeof parent.data.discountAmount !== "undefined" ) { 638 price_new.forEach(el => el.textContent = parent.formatMoney( parent.data.newPrice ) ); 639 tot_new.forEach(el => el.textContent = parent.formatMoney( parent.data.total ) ); 640 total.forEach(el => el.textContent = parent.formatMoney( parent.data.subtotal )); 641 } else if ( total.length > 0 ) { 642 total.forEach(el => el.textContent = parent.formatMoney( parent.data.total )); 643 } 644 645 // Update the total shipping cost. 646 const shipping_new = productItemContainer.querySelectorAll( '.wpec_price_shipping_amount' ); 647 shipping_new.forEach(el => el.textContent = parent.formatMoney(parent.getTotalShippingCost())); 648 649 // Update the total tax cost. 650 const tax_val = productItemContainer.querySelectorAll( '.wpec-tax-val' ); 651 tax_val.forEach(el => el.textContent = parent.formatMoney( parent.data.tax_amount * parent.data.quantity ) ); 652 } 653 654 // TODO: Need to convert this to vanilla js 655 jQuery( document ).trigger( 'wpec_after_update_all_amounts', [ parent ] ); 656 }; 657 658 this.calcTotal = function() { 659 let itemSubt = parseFloat( parent.data.price ); 660 let quantity = parseInt( parent.data.quantity ); 661 let tAmount = itemSubt * quantity; 662 //We need to round to 2 decimal places to make sure that the API call will not fail. 663 let roundedTotal = tAmount.toFixed(2);//round to 2 decimal places 664 let roundedTotalAsNumber = parseFloat(roundedTotal);//convert to number 665 tAmount = roundedTotalAsNumber; 666 let subtotal = tAmount; 667 668 parent.data.newPrice = itemSubt; 669 670 if ( typeof parent.data.discount !== "undefined" ) { 671 let discountAmount = 0; 672 if ( parent.data.discountType === 'perc' ) { 673 discountAmount = parent.PHP_round( tAmount * parent.data.discount / 100, parent.data.dec_num ); 674 } else { 675 discountAmount = data.discount; 676 } 677 if ( discountAmount > tAmount ) { 678 discountAmount = tAmount; 679 } 680 tAmount = tAmount - discountAmount; 681 parent.data.discountAmount = parent.PHP_round( discountAmount, parent.data.dec_num ); 682 parent.data.newPrice = parent.PHP_round( itemSubt - discountAmount / quantity, parent.data.dec_num ); 683 } 684 685 if ( parent.data.tax ) { 686 let tax = parent.PHP_round( tAmount / quantity * parent.data.tax / 100, parent.data.dec_num ); 687 parent.data.tax_amount = tax; 688 tAmount += tax * parent.data.quantity; 689 subtotal += parent.PHP_round( subtotal / quantity * parent.data.tax / 100, parent.data.dec_num ) * parent.data.quantity; 690 } 691 692 const totalShippingCost = parent.getTotalShippingCost(); 693 if ( totalShippingCost ) { 694 tAmount += parseFloat( totalShippingCost ); 695 subtotal += parseFloat( totalShippingCost ); 696 } 697 parent.data.total = parent.PHP_round( tAmount, parent.data.dec_num ); 698 parent.data.subtotal = parent.PHP_round( subtotal, parent.data.dec_num ); 699 }; 700 701 this.PHP_round = function( num, dec ) { 702 return Math.round( num * Math.pow( 10, dec ) ) / Math.pow( 10, dec ); 703 }; 704 705 this.applyVariations = function( amount ) { 706 let grpId; 707 if ( parent.data.variations.applied ) { 708 for ( grpId = 0; grpId < parent.data.variations.applied.length; ++grpId ) { 709 let variation = parent.data.variations[ grpId ]; 710 amount = amount + parseFloat( variation.prices[ parent.data.variations.applied[ grpId ] ] ); 711 } 712 } 713 return parent.PHP_round( amount, parent.data.dec_num ); 714 }; 715 716 /** 717 * Calculates the total shipping cost. 718 * It calculates the per quantity shipping cost along with the base shipping cost of that product. 719 * 720 * @returns {float} The total shipping cost in float of two decimal places. 721 */ 722 this.getTotalShippingCost = function() { 723 let total = 0; 724 const quantity = parent.data.quantity ? parseInt(parent.data.quantity) : 1; 725 const baseShipping = parent.data.shipping ? parseFloat( parent.data.shipping ) : 0; 726 const shippingPerQuantity = parent.data.shipping_per_quantity ? parseFloat( parent.data.shipping_per_quantity ) : 0; 727 728 total = baseShipping + (shippingPerQuantity * quantity); 729 // Round to 2 decimal places. 730 total = parseFloat(total.toFixed(2)) 731 return total; 732 } 733 734 this.toggleVisibility = function (element, displayVisibilityType = 'block', condition = null) { 735 if (!element){ 736 return; 737 } 738 739 // if no condition provided, then the condition will be based on whether the element is visible or not. 740 if (condition === null) { 741 // No specific condition provided. 742 // The content will only visible/invisible if its currently invisible/visible respectively. 743 condition = !parent.isElementVisible(element); 744 } 745 746 // Show / hide the element based on condition. 747 if (condition === true) { 748 element.style.display = displayVisibilityType; 749 } else { 750 element.style.display = 'none'; 751 } 752 } 753 754 this.isElementVisible = function (element) { 755 // Check if element exists in the DOM and is an Element node 756 if (!(element instanceof Element)) { 757 return false;
758 } 759 760 // Check if the element has a non-zero bounding box (displayed and has size) 761 const style = getComputedStyle(element); 762 const hasVisibleDisplay = style.display !== 'none'; 763 const hasVisibleVisibility = style.visibility !== 'hidden'; 764 const hasNonZeroSize = element.offsetWidth > 0 && element.offsetHeight > 0; 765 766 return hasVisibleDisplay && hasVisibleVisibility && hasNonZeroSize; 767 } 768 769 this.closestParent = function (element, selector) { 770 // Traverse up the DOM tree 771 while (element) { 772 // Check if the current element matches the selector 773 if (element.matches(selector)) { 774 return element; 775 } 776 // Move to the parent element 777 element = element.parentElement; 778 } 779 // Return null if no matching parent is found 780 return null; 781 } 782 783 // TODO: Need to convert this to vanilla js 784 jQuery( document ).trigger( 'wpec_before_render_button', [ this ] ); 785 786 paypal.Buttons( this.buttonArgs ).render( '#' + parent.data.id ); 787 788 // Handle manual checkout functionality if enabled. 789 if (Boolean(parent.data.is_manual_checkout_enabled)){ 790 new WpecManualCheckout(parent); 791 } 792}; 793 794const wpecModal = function( $ ) { 795 let openModalId = false; 796 797 document.querySelectorAll( '.wpec-modal-open, .wpec-modal-overlay, .wpec-modal-close' ).forEach(function (el){ 798 el.addEventListener( 'click', function( e ) { 799 e.preventDefault(); 800 toggleModal( this.getAttribute( 'data-wpec-modal' ) ); 801 } ) 802 }); 803 804 document.onkeydown = function( evt ) { 805 evt = evt || window.event; 806 var isEscape = false;
807 if ( "key" in evt ) { 808 isEscape = ( evt.key === "Escape" || evt.key === "Esc" ); 809 } else { 810 isEscape = ( evt.keyCode === 27 ); 811 } 812 if ( isEscape && openModalId ) { 813 toggleModal(); 814 } 815 }; 816 817 function toggleModal( modalId ) { 818 modalId = modalId ? modalId : openModalId; 819 openModalId = modalId; 820 var modal = document.getElementById( modalId ); 821 modal.classList.toggle( 'wpec-opacity-0' ); 822 modal.classList.toggle( 'wpec-pointer-events-none' ); 823 } 824 825 document.querySelectorAll('.wpec-custom-number-input button').forEach(button => { 826 button.addEventListener('click', function() { 827 const increment = this.getAttribute('data-action') === 'increment' ? 1 : -1; 828 const customQuantityInput = this.parentElement.querySelector('input'); 829 const step = customQuantityInput.getAttribute('step') ? Number(customQuantityInput.getAttribute('step')) : 1; 830 customQuantityInput.value = Number(customQuantityInput.value) + increment * step; 831 customQuantityInput.dispatchEvent(new Event('change')); 832 }); 833 }); 834 835 // TODO: Need to convert this to vanilla js 836 //shortcode_wpec_show_all_products 837 $("#wpec-sort-by").change(function(){ 838 $("#wpec-sort-by-form").submit(); 839 }); 840}; 841 842jQuery( wpecModal ); 843 844class WpecManualCheckout { 845 846 constructor(parent) { 847 this.parent = parent; 848 this.buttonId = parent.data.id; 849 this.mcProceedBtn = document.getElementById('wpec-proceed-manual-checkout-'+ this.buttonId); 850 this.mcForm = document.getElementById( 'wpec-manual-checkout-form-' + this.buttonId ); 851 852 this.tosCheckbox = document.getElementById( 'wpec-tos-' + this.buttonId ); 853 this.customQuantityInput = document.querySelector( '#wp-ppec-custom-quantity[data-ppec-button-id="' + this.buttonId + '"]' ); 854 this.customAmountInput = document.querySelector( '#wp-ppec-custom-amount[data-ppec-button-id="' + this.buttonId + '"]' ); 855 856 document.addEventListener('wpec_validate_order', (e) => { 857 this.parent.toggleVisibility( 858 document.getElementById( 'wpec-manual-checkout-section-'+ this.buttonId ), 859 'block', 860 !this.parent.isElementVisible(document.getElementById( 'place-order-' + parent.data.id )) 861 ); 862 }) 863 864 this.init(); 865 } 866
867 init(){ 868 this.mcForm?.querySelector( '.wpec_same_billing_shipping_enable' )?.addEventListener( 'change', () => { 869 const wpec_address_wrap = this.mcForm?.querySelector( '.wpec_address_wrap' ); 870 this.parent.toggleVisibility(wpec_address_wrap?.querySelector( '.wpec_shipping_address_container' ), 'inherit'); 871 this.mcForm?.querySelector( '.wpec_address_wrap' )?.classList.toggle('shipping_enabled'); 872 } ); 873 874 this.parent.validateInput( this.tosCheckbox, this.parent.ValidatorTos ); 875 this.parent.validateInput( this.customQuantityInput, this.parent.ValidatorQuantity ); 876 this.parent.validateInput( this.customAmountInput, this.parent.ValidatorAmount ); 877 this.mcForm?.querySelectorAll( '.wpec_required' ).forEach( (element) => { 878 this.parent.validateInput( element, this.parent.ValidatorBilling ); 879 } ); 880 881 this.mcForm?.addEventListener('submit', (e) => { 882 e.preventDefault(); 883 884 const form = e.target; 885 form.setAttribute("disabled", true); 886 887 const formData = new FormData(form); 888 889 this.parent.displayErrors(); 890 891 const mcFormSubmitBtn = form.querySelector('button.wpec-place-order-btn'); 892 if (mcFormSubmitBtn){ 893 mcFormSubmitBtn.querySelector("svg").style.display = 'inline'; 894 mcFormSubmitBtn.setAttribute("disabled", true); 895 } 896 897 // Get first error input if there is any. 898 const errInput = this.getErrorInput(); 899 900 if ( errInput ) { 901 errInput.focus(); 902 errInput.dispatchEvent( new Event('change') ); 903 904 form.removeAttribute("disabled"); 905 if (mcFormSubmitBtn) { 906 mcFormSubmitBtn.querySelector("svg").style.display = 'none'; 907 mcFormSubmitBtn.removeAttribute("disabled"); 908 } 909 910 // Don't continue form submission. 911 return; 912 } 913 914 const billing_address = { 915 address_line_1: formData.get('wpec_billing_address'), 916 admin_area_1: formData.get('wpec_billing_city'), 917 admin_area_2: formData.get('wpec_billing_country'), 918 postal_code: formData.get('wpec_billing_state'), 919 country_code: formData.get('wpec_billing_postal_code'), 920 }; 921 const shipping_address = formData.get('wpec_same_billing_shipping_enable') ? billing_address : { 922 address_line_1: formData.get('wpec_shipping_address'), 923 admin_area_1: formData.get('wpec_shipping_city'), 924 admin_area_2: formData.get('wpec_shipping_country'), 925 postal_code: formData.get('wpec_shipping_state'), 926 country_code: formData.get('wpec_shipping_postal_code'), 927 }; 928 929 const paymentData = { 930 payer: { 931 name: { 932 given_name: formData.get('wpec_billing_first_name'), 933 surname: formData.get('wpec_billing_last_name'), 934 }, 935 email_address: formData.get('wpec_billing_email'), 936 phone: formData.get('wpec_billing_phone'), 937 address: billing_address, 938 shipping_address: shipping_address, 939 } 940 } 941 942 document.dispatchEvent(new CustomEvent('wpec_process_manual_checkout', { 943 detail: { 944 paymentData, 945 data: this.parent.data, 946 } 947 })); 948 949 this.parent.processPayment(paymentData, 'wpec_process_manual_checkout' ); 950 } ); 951 952 this.mcForm?.addEventListener('reset', (e)=>{ 953 const form = e.target; 954 form.removeAttribute("disabled"); 955 const mcFormSubmitBtn = form.querySelector('button.wpec-place-order-btn'); 956 if (mcFormSubmitBtn){ 957 mcFormSubmitBtn.querySelector("svg").style.display = 'none'; 958 mcFormSubmitBtn.removeAttribute("disabled"); 959 } 960 961 this.toggleManualCheckout(); 962 }) 963 964 this.mcProceedBtn?.addEventListener('click', (e) =>{ 965 this.toggleManualCheckout(); 966 }); 967 } 968 969 getErrorInput(){ 970 if (this.tosCheckbox?.classList.contains('hasError')){ 971 return this.tosCheckbox; 972 } else if (this.customQuantityInput?.classList.contains('hasError')){ 973 return this.customQuantityInput; 974 } else if (this.customAmountInput?.classList.contains('hasError')){ 975 return this.customAmountInput; 976 } else { 977 return this.mcForm?.querySelector( '.hasError' ); 978 } 979 } 980 981 toggleManualCheckout(){ 982 this.parent.toggleVisibility(this.mcForm, 'inherit'); 983 this.parent.toggleVisibility(this.mcProceedBtn, 'inherit'); 984 985 // Toggle paypal btn 986 // this.parent.toggleVisibility( 987 // document.getElementById(this.buttonId), 988 // 'inherit', 989 // !this.parent.isElementVisible(this.mcForm) 990 // ); 991 992 document.dispatchEvent(new CustomEvent('wpec_toggle_manual_checkout_form', { 993 detail: { 994 wpecManualCheckout: this 995 } 996 })); 997 } 998}
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.