1/* global wc_stripe_params */ 2 3jQuery( function( $ ) { 4 'use strict'; 5 6 try { 7 var stripe = Stripe( wc_stripe_params.key, { 8 locale: wc_stripe_params.stripe_locale || 'auto', 9 } ); 10 } catch( error ) { 11 console.log( error ); 12 return; 13 } 14 15 var stripe_elements_options = Object.keys( wc_stripe_params.elements_options ).length ? wc_stripe_params.elements_options : {}, 16 sepa_elements_options = Object.keys( wc_stripe_params.sepa_elements_options ).length ? wc_stripe_params.sepa_elements_options : {}, 17 elements = stripe.elements( stripe_elements_options ), 18 iban = elements.create( 'iban', sepa_elements_options ), 19 stripe_card, 20 stripe_exp, 21 stripe_cvc; 22 23 /** 24 * Object to handle Stripe elements payment form. 25 */ 26 var wc_stripe_form = { 27 /** 28 * Get WC AJAX endpoint URL. 29 * 30 * @param {String} endpoint Endpoint. 31 * @return {String} 32 */ 33 getAjaxURL: function( endpoint ) { 34 return wc_stripe_params.ajaxurl 35 .toString() 36 .replace( '%%endpoint%%', 'wc_stripe_' + endpoint ); 37 }, 38 39 /** 40 * Unmounts all Stripe elements when the checkout page is being updated. 41 */ 42 unmountElements: function() { 43 if ( 'yes' === wc_stripe_params.inline_cc_form ) { 44 stripe_card.unmount( '#stripe-card-element' ); 45 } else { 46 stripe_card.unmount( '#stripe-card-element' ); 47 stripe_exp.unmount( '#stripe-exp-element' ); 48 stripe_cvc.unmount( '#stripe-cvc-element' ); 49 } 50 }, 51 52 /** 53 * Mounts all elements to their DOM nodes on initial loads and updates. 54 */ 55 mountElements: function() { 56 if ( ! $( '#stripe-card-element' ).length ) { 57 return; 58 } 59 60 if ( 'yes' === wc_stripe_params.inline_cc_form ) { 61 stripe_card.mount( '#stripe-card-element' ); 62 return; 63 } 64 65 stripe_card.mount( '#stripe-card-element' ); 66 stripe_exp.mount( '#stripe-exp-element' ); 67 stripe_cvc.mount( '#stripe-cvc-element' ); 68 }, 69 70 /** 71 * Creates all Stripe elements that will be used to enter cards or IBANs. 72 */ 73 createElements: function() { 74 var elementStyles = { 75 base: { 76 iconColor: '#666EE8', 77 color: '#31325F', 78 fontSize: '15px', 79 '::placeholder': { 80 color: '#CFD7E0', 81 } 82 } 83 }; 84 85 var elementClasses = { 86 focus: 'focused', 87 empty: 'empty', 88 invalid: 'invalid', 89 }; 90 91 elementStyles = wc_stripe_params.elements_styling ? wc_stripe_params.elements_styling : elementStyles; 92 elementClasses = wc_stripe_params.elements_classes ? wc_stripe_params.elements_classes : elementClasses; 93 94 if ( 'yes' === wc_stripe_params.inline_cc_form ) { 95 stripe_card = elements.create( 'card', { style: elementStyles, hidePostalCode: true } ); 96 97 stripe_card.addEventListener( 'change', function( event ) { 98 wc_stripe_form.onCCFormChange(); 99 100 if ( event.error ) { 101 $( document.body ).trigger( 'stripeError', event ); 102 } 103 } ); 104 } else { 105 stripe_card = elements.create( 'cardNumber', { style: elementStyles, classes: elementClasses } ); 106 stripe_exp = elements.create( 'cardExpiry', { style: elementStyles, classes: elementClasses } ); 107 stripe_cvc = elements.create( 'cardCvc', { style: elementStyles, classes: elementClasses } ); 108 109 stripe_card.addEventListener( 'change', function( event ) { 110 wc_stripe_form.onCCFormChange(); 111 112 wc_stripe_form.updateCardBrand( event.brand ); 113 114 if ( event.error ) { 115 $( document.body ).trigger( 'stripeError', event ); 116 } 117 } ); 118 119 stripe_exp.addEventListener( 'change', function( event ) { 120 wc_stripe_form.onCCFormChange(); 121 122 if ( event.error ) { 123 $( document.body ).trigger( 'stripeError', event ); 124 } 125 } ); 126 127 stripe_cvc.addEventListener( 'change', function( event ) { 128 wc_stripe_form.onCCFormChange(); 129 130 if ( event.error ) { 131 $( document.body ).trigger( 'stripeError', event ); 132 } 133 } ); 134 } 135 136 /** 137 * Only in checkout page we need to delay the mounting of the 138 * card as some AJAX process needs to happen before we do. 139 */ 140 if ( 'yes' === wc_stripe_params.is_checkout ) { 141 $( document.body ).on( 'updated_checkout', function() {
142 // Don't re-mount if already mounted in DOM. 143 if ( $( '#stripe-card-element' ).children().length ) { 144 return; 145 } 146 147 // Unmount prior to re-mounting. 148 if ( stripe_card ) { 149 wc_stripe_form.unmountElements(); 150 } 151 152 wc_stripe_form.mountElements(); 153 154 if ( $( '#stripe-iban-element' ).length ) { 155 iban.mount( '#stripe-iban-element' ); 156 } 157 } ); 158 } else if ( $( 'form#add_payment_method' ).length || $( 'form#order_review' ).length ) { 159 wc_stripe_form.mountElements(); 160 161 if ( $( '#stripe-iban-element' ).length ) { 162 iban.mount( '#stripe-iban-element' ); 163 } 164 } 165 }, 166 167 /** 168 * Updates the card brand logo with non-inline CC forms. 169 * 170 * @param {string} brand The identifier of the chosen brand. 171 */ 172 updateCardBrand: function( brand ) { 173 var brandClass = { 174 'visa': 'stripe-visa-brand', 175 'mastercard': 'stripe-mastercard-brand', 176 'amex': 'stripe-amex-brand', 177 'discover': 'stripe-discover-brand', 178 'diners': 'stripe-diners-brand', 179 'jcb': 'stripe-jcb-brand', 180 'unknown': 'stripe-credit-card-brand' 181 }; 182 183 var imageElement = $( '.stripe-card-brand' ), 184 imageClass = 'stripe-credit-card-brand'; 185 186 if ( brand in brandClass ) { 187 imageClass = brandClass[ brand ]; 188 } 189 190 // Remove existing card brand class. 191 $.each( brandClass, function( index, el ) { 192 imageElement.removeClass( el ); 193 } ); 194 195 imageElement.addClass( imageClass ); 196 }, 197 198 /** 199 * Initialize event handlers and UI state. 200 */ 201 init: function() { 202 // Initialize tokenization script if on change payment method page and pay for order page. 203 if ( 'yes' === wc_stripe_params.is_change_payment_page || 'yes' === wc_stripe_params.is_pay_for_order_page ) { 204 $( document.body ).trigger( 'wc-credit-card-form-init' ); 205 } 206 207 // checkout page 208 if ( $( 'form.woocommerce-checkout' ).length ) { 209 this.form = $( 'form.woocommerce-checkout' ); 210 } 211 212 $( 'form.woocommerce-checkout' ) 213 .on( 214 'checkout_place_order_stripe checkout_place_order_stripe_bancontact checkout_place_order_stripe_sofort checkout_place_order_stripe_giropay checkout_place_order_stripe_ideal checkout_place_order_stripe_alipay checkout_place_order_stripe_sepa checkout_place_order_stripe_boleto checkout_place_order_stripe_oxxo', 215 this.onSubmit 216 ); 217 218 // pay order page 219 if ( $( 'form#order_review' ).length ) { 220 this.form = $( 'form#order_review' ); 221 } 222 223 $( 'form#order_review, form#add_payment_method' ) 224 .on( 225 'submit', 226 this.onSubmit 227 ); 228 229 // add payment method page 230 if ( $( 'form#add_payment_method' ).length ) { 231 this.form = $( 'form#add_payment_method' ); 232 } 233 234 $( 'form.woocommerce-checkout' ) 235 .on( 236 'change', 237 this.reset 238 ); 239 240 $( document ) 241 .on( 242 'stripeError', 243 this.onError 244 ) 245 .on( 246 'checkout_error', 247 this.reset 248 ); 249 250 // SEPA IBAN. 251 iban.on( 'change', 252 this.onSepaError 253 ); 254 255 // Subscription early renewals modal. 256 if ($('#early_renewal_modal_submit[data-payment-method]').length) { 257 $('#early_renewal_modal_submit[data-payment-method=stripe]').on('click', this.onEarlyRenewalSubmit); 258 } else { 259 $('#early_renewal_modal_submit').on('click', this.onEarlyRenewalSubmit); 260 } 261 262 wc_stripe_form.createElements(); 263 264 // Listen for hash changes in order to handle payment intents 265 window.addEventListener( 'hashchange', wc_stripe_form.onHashChange ); 266 wc_stripe_form.maybeConfirmIntent(); 267 268 //Mask CPF/CNPJ field when using Boleto 269 $( document ).on( 'change', '.wc_payment_methods', function () { 270 if ( ! $( '#stripe_boleto_tax_id' ).length ) { 271 return; 272 } 273 274 var TaxIdMaskBehavior = function ( val ) { 275 return val.replace( /\D/g, '' ).length >= 12 ? '00.000.000/0000-00' : '000.000.000-009999'; 276 }, 277 spOptions = { 278 onKeyPress: function ( val, e, field, options ) { 279 field.mask( TaxIdMaskBehavior.apply( {}, arguments ), options ); 280 } 281 }; 282 283 $( '#stripe_boleto_tax_id' ).mask( TaxIdMaskBehavior, spOptions ); 284 }); 285 }, 286 287 /** 288 * Check to see if Stripe in general is being used for checkout. 289 * 290 * @return {boolean} 291 */ 292 isStripeChosen: function() { 293 return $( '#payment_method_stripe, #payment_method_stripe_bancontact, #payment_method_stripe_sofort, #payment_method_stripe_giropay, #payment_method_stripe_ideal, #payment_method_stripe_alipay, #payment_method_stripe_sepa, #payment_method_stripe_eps, #payment_method_stripe_multibanco, #payment_method_stripe_boleto, #payment_method_stripe_oxxo' ).is( ':checked' ) || ( $( '#payment_method_stripe' ).is( ':checked' ) && 'new' === $( 'input[name="wc-stripe-payment-token"]:checked' ).val() ) || ( $( '#payment_method_stripe_sepa' ).is( ':checked' ) && 'new' === $( 'input[name="wc-stripe-payment-token"]:checked' ).val() ); 294 }, 295 296 /**
297 * Currently only support saved cards via credit cards and SEPA. No other payment method. 298 * 299 * @return {boolean} 300 */ 301 isStripeSaveCardChosen: function() { 302 return ( 303 $( '#payment_method_stripe' ).is( ':checked' ) && 304 $( 'input[name="wc-stripe-payment-token"]' ).is( ':checked' ) && 305 'new' !== $( 'input[name="wc-stripe-payment-token"]:checked' ).val() 306 ) || ( 307 $( '#payment_method_stripe_sepa' ).is( ':checked' ) && 308 $( 'input[name="wc-stripe_sepa-payment-token"]' ).is( ':checked' ) && 309 'new' !== $( 'input[name="wc-stripe_sepa-payment-token"]:checked' ).val() 310 ); 311 }, 312 313 /** 314 * Check if Stripe credit card is being used. 315 * 316 * @return {boolean} 317 */ 318 isStripeCardChosen: function() { 319 return $( '#payment_method_stripe' ).is( ':checked' ); 320 }, 321 322 /** 323 * Check if Stripe Bancontact is being used. 324 * 325 * @return {boolean} 326 */ 327 isBancontactChosen: function() { 328 return $( '#payment_method_stripe_bancontact' ).is( ':checked' ); 329 }, 330 331 /** 332 * Check if Stripe giropay is being used. 333 * 334 * @return {boolean} 335 */ 336 isGiropayChosen: function() { 337 return $( '#payment_method_stripe_giropay' ).is( ':checked' ); 338 }, 339 340 /** 341 * Check if Stripe iDEAL is being used. 342 * 343 * @return {boolean} 344 */ 345 isIdealChosen: function() { 346 return $( '#payment_method_stripe_ideal' ).is( ':checked' ); 347 }, 348 349 /** 350 * Check if Stripe Sofort is being used. 351 * 352 * @return {boolean} 353 */ 354 isSofortChosen: function() { 355 return $( '#payment_method_stripe_sofort' ).is( ':checked' ); 356 }, 357 358 /** 359 * Check if Stripe Alipay is being used. 360 * 361 * @return {boolean} 362 */ 363 isAlipayChosen: function() { 364 return $( '#payment_method_stripe_alipay' ).is( ':checked' ); 365 }, 366 367 /** 368 * Check if Stripe SEPA Direct Debit is being used. 369 * 370 * @return {boolean} 371 */ 372 isSepaChosen: function() { 373 return $( '#payment_method_stripe_sepa' ).is( ':checked' ); 374 }, 375 376 /** 377 * Check if Stripe P24 is being used. 378 * 379 * @return {boolean} 380 */ 381 isP24Chosen: function() { 382 return $( '#payment_method_stripe_p24' ).is( ':checked' ); 383 }, 384 385 /** 386 * Check if Stripe EPS is being used. 387 * 388 * @return {boolean} 389 */ 390 isEpsChosen: function() { 391 return $( '#payment_method_stripe_eps' ).is( ':checked' ); 392 }, 393 394 /** 395 * Check if Stripe Multibanco is being used. 396 * 397 * @return {boolean} 398 */ 399 isMultibancoChosen: function() { 400 return $( '#payment_method_stripe_multibanco' ).is( ':checked' ); 401 }, 402 403 /** 404 * Check if Stripe Boleto is being used. 405 * 406 * @return {boolean} 407 */ 408 isBoletoChosen: function() { 409 return $( '#payment_method_stripe_boleto' ).is( ':checked' ); 410 }, 411 412 /** 413 * Check if Stripe OXXO is being used. 414 * 415 * @return {boolean} 416 */ 417 isOxxoChosen: function() { 418 return $( '#payment_method_stripe_oxxo' ).is( ':checked' ); 419 }, 420 421 /** 422 * Checks if a source ID is present as a hidden input. 423 * Only used when SEPA Direct Debit is chosen. 424 * 425 * @return {boolean} 426 */ 427 hasSource: function() { 428 return 0 < $( 'input.stripe-source' ).length; 429 }, 430 431 /** 432 * Check whether a mobile device is being used. 433 * 434 * @return {boolean} 435 */ 436 isMobile: function() { 437 if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ) ) { 438 return true; 439 } 440 441 return false; 442 }, 443 444 /** 445 * Blocks payment forms with an overlay while being submitted. 446 */ 447 block: function() { 448 if ( ! wc_stripe_form.isMobile() ) { 449 wc_stripe_form.form.block( { 450 message: null, 451 overlayCSS: { 452 background: '#fff', 453 opacity: 0.6 454 } 455 } ); 456 } 457 }, 458 459 /** 460 * Removes overlays from payment forms. 461 */ 462 unblock: function() { 463 wc_stripe_form.form && wc_stripe_form.form.unblock(); 464 }, 465 466 /** 467 * Returns the selected payment method HTML element. 468 * 469 * @return {HTMLElement} 470 */ 471 getSelectedPaymentElement: function() { 472 return $( '.payment_methods input[name="payment_method"]:checked' ); 473 }, 474 475 /** 476 * Retrieves "owner" data from either the billing fields in a form or preset settings. 477 * 478 * @return {Object} 479 */ 480 getOwnerDetails: function() { 481 var first_name = $( '#billing_first_name' ).length ? $( '#billing_first_name' ).val() : wc_stripe_params.billing_first_name, 482 last_name = $( '#billing_last_name' ).length ? $( '#billing_last_name' ).val() : wc_stripe_params.billing_last_name, 483 owner = { name: '', address: {}, email: '', phone: '' }; 484 485 owner.name = first_name; 486 487 if ( first_name && last_name ) { 488 owner.name = first_name + ' ' + last_name; 489 } else { 490 owner.name = $( '#stripe-payment-data' ).data( 'full-name' ); 491 } 492 493 owner.email = $( '#billing_email' ).val(); 494 owner.phone = $( '#billing_phone' ).val(); 495 496 /* Stripe does not like empty string values so 497 * we need to remove the parameter if we're not 498 * passing any value. 499 */ 500 if ( typeof owner.phone === 'undefined' || 0 >= owner.phone.length ) { 501 delete owner.phone; 502 } 503 504 if ( typeof owner.email === 'undefined' || 0 >= owner.email.length ) { 505 if ( $( '#stripe-payment-data' ).data( 'email' ).length ) { 506 owner.email = $( '#stripe-payment-data' ).data( 'email' ); 507 } else { 508 delete owner.email; 509 } 510 } 511 512 if ( typeof owner.name === 'undefined' || 0 >= owner.name.length ) { 513 delete owner.name; 514 } 515 516 owner.address.line1 = $( '#billing_address_1' ).val() || wc_stripe_params.billing_address_1; 517 owner.address.line2 = $( '#billing_address_2' ).val() || wc_stripe_params.billing_address_2; 518 owner.address.state = $( '#billing_state' ).val() || wc_stripe_params.billing_state; 519 owner.address.city = $( '#billing_city' ).val() || wc_stripe_params.billing_city; 520 owner.address.postal_code = $( '#billing_postcode' ).val() || wc_stripe_params.billing_postcode; 521 owner.address.country = $( '#billing_country' ).val() || wc_stripe_params.billing_country; 522 523 return { 524 owner: owner, 525 }; 526 }, 527 528 /**
529 * Initiates the creation of a Source object. 530 * 531 * Currently this is only used for credit cards and SEPA Direct Debit, 532 * all other payment methods work with redirects to create sources. 533 */ 534 createSource: function() { 535 var extra_details = wc_stripe_form.getOwnerDetails(); 536 537 // Handle SEPA Direct Debit payments. 538 if ( wc_stripe_form.isSepaChosen() ) { 539 extra_details.currency = $( '#stripe-sepa_debit-payment-data' ).data( 'currency' ); 540 extra_details.mandate = { notification_method: wc_stripe_params.sepa_mandate_notification }; 541 extra_details.type = 'sepa_debit'; 542 543 return stripe.createSource( iban, extra_details ).then( wc_stripe_form.sourceResponse ); 544 } 545 546 // Handle card payments. 547 return stripe.createSource( stripe_card, extra_details ) 548 .then( wc_stripe_form.sourceResponse ); 549 }, 550 551 /** 552 * Handles responses, based on source object. 553 * 554 * @param {Object} response The `stripe.createSource` response. 555 */ 556 sourceResponse: function( response ) { 557 if ( response.error ) { 558 $( document.body ).trigger( 'stripeError', response ); 559 return; 560 } 561 562 wc_stripe_form.reset(); 563 564 wc_stripe_form.form.append( 565 $( '<input type="hidden" />' ) 566 .addClass( 'stripe-source' ) 567 .attr( 'name', 'stripe_source' ) 568 .val( response.source.id ) 569 ); 570 571 if ( $( 'form#add_payment_method' ).length || $( '#wc-stripe-change-payment-method' ).length ) { 572 wc_stripe_form.sourceSetup( response ); 573 return; 574 } 575 576 wc_stripe_form.form.trigger( 'submit' ); 577 }, 578 579 /** 580 * Authenticate Source if necessary by creating and confirming a SetupIntent. 581 * 582 * @param {Object} response The `stripe.createSource` response. 583 */ 584 sourceSetup: function( response ) { 585 var apiError = { 586 error: { 587 type: 'api_connection_error' 588 } 589 }; 590 591 $.post( { 592 url: wc_stripe_form.getAjaxURL( 'create_setup_intent'), 593 dataType: 'json', 594 data: { 595 stripe_source_id: response.source.id, 596 nonce: wc_stripe_params.add_card_nonce, 597 }, 598 error: function() { 599 $( document.body ).trigger( 'stripeError', apiError ); 600 } 601 } ).done( function( serverResponse ) { 602 if ( 'success' === serverResponse.status ) { 603 if ( $( 'form#add_payment_method' ).length ) { 604 $( wc_stripe_form.form ).off( 'submit', wc_stripe_form.form.onSubmit ); 605 } 606 wc_stripe_form.form.trigger( 'submit' ); 607 return; 608 } else if ( 'requires_action' !== serverResponse.status ) { 609 $( document.body ).trigger( 'stripeError', serverResponse ); 610 return; 611 } 612 613 stripe.confirmCardSetup( serverResponse.client_secret, { payment_method: response.source.id } ) 614 .then( function( result ) { 615 if ( result.error ) { 616 $( document.body ).trigger( 'stripeError', result ); 617 return; 618 } 619 620 if ( $( 'form#add_payment_method' ).length ) { 621 $( wc_stripe_form.form ).off( 'submit', wc_stripe_form.form.onSubmit ); 622 } 623 wc_stripe_form.form.trigger( 'submit' ); 624 } ) 625 .catch( function( err ) { 626 console.log( err ); 627 $( document.body ).trigger( 'stripeError', { error: err } ); 628 } ); 629 } ); 630 }, 631 632 /** 633 * Performs payment-related actions when a checkout/payment form is being submitted. 634 * 635 * @return {boolean} An indicator whether the submission should proceed. 636 * WooCommerce's checkout.js stops only on `false`, so this needs to be explicit. 637 */ 638 onSubmit: function() { 639 if ( ! wc_stripe_form.isStripeChosen() ) { 640 return true; 641 } 642 643 // If a source is already in place, submit the form as usual. 644 if ( wc_stripe_form.isStripeSaveCardChosen() || wc_stripe_form.hasSource() ) { 645 return true; 646 } 647 648 // For methods that needs redirect, we will create the source server side so we can obtain the order ID. 649 if ( 650 wc_stripe_form.isBancontactChosen() || 651 wc_stripe_form.isGiropayChosen() || 652 wc_stripe_form.isIdealChosen() || 653 wc_stripe_form.isAlipayChosen() || 654 wc_stripe_form.isSofortChosen() || 655 wc_stripe_form.isP24Chosen() || 656 wc_stripe_form.isEpsChosen() || 657 wc_stripe_form.isMultibancoChosen() 658 ) { 659 return true; 660 } 661 662 wc_stripe_form.block(); 663 664 if( wc_stripe_form.isBoletoChosen() ) { 665 if( ! $( '#stripe_boleto_tax_id' ).val() ) { 666 wc_stripe_form.submitError( wc_stripe_params.cpf_cnpj_required_msg ); 667 wc_stripe_form.unblock(); 668 return false;
669 } 670 671 wc_stripe_form.handleBoleto(); 672 } else if ( wc_stripe_form.isOxxoChosen() ) { 673 wc_stripe_form.handleOxxo(); 674 } else { 675 wc_stripe_form.createSource(); 676 } 677 678 return false; 679 }, 680 681 /** 682 * Will show a modal for printing the Boleto. 683 * After the customer closes the modal proceeds with checkout normally 684 */ 685 handleBoleto: function () { 686 wc_stripe_form.executeCheckout( 'boleto', function ( checkout_response ) { 687 stripe.confirmBoletoPayment( 688 checkout_response.client_secret, 689 checkout_response.confirm_payment_data 690 ) 691 .then(function ( response ) { 692 wc_stripe_form.handleConfirmResponse( checkout_response, response ); 693 }); 694 } ); 695 }, 696 697 /** 698 * Executes the checkout and then execute the callback instead of redirect to success page 699 * @param callback 700 */ 701 executeCheckout: function ( payment_method, callback ) { 702 const formFields = wc_stripe_form.form.serializeArray().reduce( ( obj, field ) => { 703 obj[ field.name ] = field.value; 704 return obj; 705 }, {} ); 706 707 if( wc_stripe_form.form.attr('id') === 'order_review' ) { 708 formFields._ajax_nonce = wc_stripe_params.updatePaymentIntentNonce; 709 formFields.order_id = wc_stripe_params.orderId; 710 711 $.ajax( { 712 url: wc_stripe_form.getAjaxURL( payment_method + '_update_payment_intent' ), 713 type: 'POST', 714 data: formFields, 715 success: function ( response ) { 716 717 if( 'success' !== response.result ) { 718 wc_stripe_form.submitError( response.messages ); 719 wc_stripe_form.unblock(); 720 return; 721 } 722 723 callback( response ); 724 } 725 } ); 726 727 } else { 728 $.ajax( { 729 url: wc_stripe_params.checkout_url, 730 type: 'POST', 731 data: formFields, 732 success: function ( checkout_response ) { 733 734 if( 'success' !== checkout_response.result ) { 735 wc_stripe_form.submitError( checkout_response.messages, true ); 736 wc_stripe_form.unblock(); 737 return; 738 } 739 740 callback( checkout_response ); 741 } 742 } ); 743 } 744 }, 745 746 /** 747 * Handles response of the Confirm<payment_method>Payment like confirmBoletoPayment and confirmOxxoPayment 748 * @param checkout_response 749 * @param response 750 */ 751 handleConfirmResponse: function ( checkout_response, response ) { 752 if ( response.error ) { 753 $( document.body ).trigger( 'stripeError', response ); 754 return; 755 } 756 757 if ( -1 === checkout_response.redirect.indexOf( 'https://' ) || -1 === checkout_response.redirect.indexOf( 'http://' ) ) { 758 window.location = checkout_response.redirect; 759 } else { 760 window.location = decodeURI( checkout_response.redirect ); 761 } 762 }, 763 764 /** 765 * Will show a modal for printing the OXXO Voucher. 766 * After the customer closes the modal proceeds with checkout normally 767 */ 768 handleOxxo: function () { 769 wc_stripe_form.executeCheckout( 'oxxo', function ( checkout_response ) { 770 stripe.confirmOxxoPayment( 771 checkout_response.client_secret, 772 checkout_response.confirm_payment_data 773 ) 774 .then(function (response) { 775 wc_stripe_form.handleConfirmResponse( checkout_response, response ); 776 } ); 777 } ); 778 }, 779 780 /** 781 * If a new credit card is entered, reset sources. 782 */ 783 onCCFormChange: function() { 784 wc_stripe_form.reset(); 785 }, 786 787 /** 788 * Removes all Stripe errors and hidden fields with IDs from the form. 789 */ 790 reset: function() { 791 $( '.wc-stripe-error, .stripe-source' ).remove(); 792 }, 793 794 /** 795 * Displays a SEPA-specific error message. 796 * 797 * @param {Event} e The event with the error. 798 */ 799 onSepaError: function( e ) { 800 var errorContainer = wc_stripe_form.getSelectedPaymentElement().parents( 'li' ).eq( 0 ).find( '.stripe-source-errors' ); 801 802 if ( ! e.error ) { 803 $( errorContainer ).html( '' ); 804 return; 805 } 806 807 console.log( e.error.message ); // Leave for troubleshooting. 808 $( errorContainer ).html( '<ul class="woocommerce_error woocommerce-error wc-stripe-error"><li /></ul>' ); 809 $( errorContainer ).find( 'li' ).text( e.error.message ); // Prevent XSS 810 }, 811 812 /** 813 * Displays stripe-related errors. 814 * 815 * @param {Event} e The jQuery event. 816 * @param {Object} result The result of Stripe call. 817 */ 818 onError: function( e, result ) { 819 var message = result.error.message; 820 var selectedMethodElement = wc_stripe_form.getSelectedPaymentElement().closest( 'li' ); 821 var savedTokens = selectedMethodElement.find( '.woocommerce-SavedPaymentMethods-tokenInput' ); 822 var errorContainer; 823 824 var prButtonClicked = $( 'body' ).hasClass( 'woocommerce-stripe-prb-clicked' ); 825 if ( prButtonClicked ) { 826 // If payment was initiated with a payment request button, display errors in the notices div. 827 $( 'body' ).removeClass( 'woocommerce-stripe-prb-clicked' ); 828 errorContainer = $( 'div.woocommerce-notices-wrapper' ).first(); 829 } else if ( savedTokens.length ) { 830 // In case there are saved cards too, display the message next to the correct one. 831 var selectedToken = savedTokens.filter( ':checked' ); 832 833 if ( selectedToken.closest( '.woocommerce-SavedPaymentMethods-new' ).length ) { 834 // Display the error next to the CC fields if a new card is being entered. 835 errorContainer = $( '#wc-stripe-cc-form .stripe-source-errors' ); 836 } else { 837 // Display the error next to the chosen saved card. 838 errorContainer = selectedToken.closest( 'li' ).find( '.stripe-source-errors' ); 839 } 840 } else { 841 // When no saved cards are available, display the error next to CC fields. 842 errorContainer = selectedMethodElement.find( '.stripe-source-errors' ); 843 } 844 845 /* 846 * If payment method is SEPA and owner name is not completed, 847 * source cannot be created. So we need to show the normal 848 * Billing name is required error message on top of form instead 849 * of inline. 850 */ 851 if ( wc_stripe_form.isSepaChosen() ) {
852 if ( 'invalid_owner_name' === result.error.code && wc_stripe_params.hasOwnProperty( result.error.code ) ) { 853 wc_stripe_form.submitError( wc_stripe_params[ result.error.code ] ); 854 return; 855 } 856 } 857 858 // Notify users that the email is invalid. 859 if ( 'email_invalid' === result.error.code ) { 860 message = wc_stripe_params.email_invalid; 861 } else if ( 862 /* 863 * Customers do not need to know the specifics of the below type of errors 864 * therefore return a generic localizable error message. 865 */ 866 'invalid_request_error' === result.error.type || 867 'api_connection_error' === result.error.type || 868 'api_error' === result.error.type || 869 'authentication_error' === result.error.type || 870 'rate_limit_error' === result.error.type 871 ) { 872 message = wc_stripe_params.invalid_request_error; 873 } 874 875 if ( wc_stripe_params.hasOwnProperty(result.error.code) ) { 876 message = wc_stripe_params[ result.error.code ]; 877 } 878 879 wc_stripe_form.reset(); 880 $( '.woocommerce-NoticeGroup-checkout' ).remove(); 881 console.log( result.error.message ); // Leave for troubleshooting. 882 $( errorContainer ).html( '<ul class="woocommerce_error woocommerce-error wc-stripe-error"><li /></ul>' ); 883 $( errorContainer ).find( 'li' ).text( message ); // Prevent XSS 884 885 if ( $( '.wc-stripe-error' ).length ) { 886 $( 'html, body' ).animate({ 887 scrollTop: ( $( '.wc-stripe-error' ).offset().top - 200 ) 888 }, 200 ); 889 } 890 wc_stripe_form.unblock(); 891 $.unblockUI(); // If arriving via Payment Request Button. 892 }, 893 894 /** 895 * Displays an error message in the beginning of the form and scrolls to it. 896 * 897 * @param {Object} error_message An error message jQuery object. 898 */ 899 submitError: function( error_message, is_html = false ) { 900 if( ! is_html ) { 901 var error = $( '<div><ul class="woocommerce-error"><li /></ul></div>' ); 902 error.find( 'li' ).text( error_message ); // Prevent XSS 903 error_message = error.html(); 904 } 905 906 $( '.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message' ).remove(); 907 wc_stripe_form.form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout">' + error_message + '</div>' ); 908 wc_stripe_form.form.removeClass( 'processing' ).unblock(); 909 wc_stripe_form.form.find( '.input-text, select, input:checkbox' ).trigger( 'blur' ); 910 911 var selector = ''; 912 913 if ( $( '#add_payment_method' ).length ) { 914 selector = $( '#add_payment_method' ); 915 } 916 917 if ( $( '#order_review' ).length ) { 918 selector = $( '#order_review' ); 919 } 920 921 if ( $( 'form.checkout' ).length ) { 922 selector = $( 'form.checkout' ); 923 } 924 925 if ( selector.length ) { 926 $( 'html, body' ).animate({ 927 scrollTop: ( selector.offset().top - 100 ) 928 }, 500 ); 929 } 930 931 $( document.body ).trigger( 'checkout_error' ); 932 wc_stripe_form.unblock(); 933 }, 934 935 /** 936 * Handles changes in the hash in order to show a modal for PaymentIntent/SetupIntent confirmations. 937 * 938 * Listens for `hashchange` events and checks for a hash in the following format: 939 * #confirm-pi-<intentClientSecret>:<successRedirectURL> 940 * 941 * If such a hash appears, the partials will be used to call `stripe.handleCardPayment` 942 * in order to allow customers to confirm an 3DS/SCA authorization, or stripe.handleCardSetup if 943 * what needs to be confirmed is a SetupIntent. 944 * 945 * Those redirects/hashes are generated in `WC_Gateway_Stripe::process_payment`. 946 */ 947 onHashChange: function() { 948 var partials = window.location.hash.match( /^#?confirm-(pi|si)-([^:]+):(.+)$/ ); 949 950 if ( ! partials || 4 > partials.length ) { 951 return; 952 } 953 954 var type = partials[1]; 955 var intentClientSecret = partials[2]; 956 var redirectURL = decodeURIComponent( partials[3] ); 957 958 // Cleanup the URL 959 window.location.hash = ''; 960 961 wc_stripe_form.openIntentModal( intentClientSecret, redirectURL, false, 'si' === type ); 962 }, 963 964 maybeConfirmIntent: function() { 965 if ( ! $( '#stripe-intent-id' ).length || ! $( '#stripe-intent-return' ).length ) { 966 return; 967 } 968 969 var intentSecret = $( '#stripe-intent-id' ).val(); 970 var returnURL = $( '#stripe-intent-return' ).val(); 971 972 wc_stripe_form.openIntentModal( intentSecret, returnURL, true, false ); 973 }, 974 975 /** 976 * Opens the modal for PaymentIntent authorizations. 977 * 978 * @param {string} intentClientSecret The client secret of the intent. 979 * @param {string} redirectURL The URL to ping on fail or redirect to on success. 980 * @param {boolean} alwaysRedirect If set to true, an immediate redirect will happen no matter the result. 981 * If not, an error will be displayed on failure. 982 * @param {boolean} isSetupIntent If set to true, ameans that the flow is handling a Setup Intent. 983 * If false, it's a Payment Intent. 984 */ 985 openIntentModal: function( intentClientSecret, redirectURL, alwaysRedirect, isSetupIntent ) { 986 stripe[ isSetupIntent ? 'handleCardSetup' : 'handleCardPayment' ]( intentClientSecret ) 987 .then( function( response ) { 988 if ( response.error ) { 989 throw response.error; 990 } 991 992 var intent = response[ isSetupIntent ? 'setupIntent' : 'paymentIntent' ]; 993 if ( 'requires_capture' !== intent.status && 'succeeded' !== intent.status ) { 994 return; 995 } 996 997 window.location = redirectURL; 998 } ) 999 .catch( function( error ) { 1000 if ( alwaysRedirect ) { 1001 window.location = redirectURL; 1002 return; 1003 } 1004 1005 $( document.body ).trigger( 'stripeError', { error: error } ); 1006 wc_stripe_form.form && wc_stripe_form.form.removeClass( 'processing' ); 1007 1008 // Report back to the server. 1009 $.get( redirectURL + '&is_ajax' ); 1010 } ); 1011 }, 1012 1013 /** 1014 * Prevents the standard behavior of the "Renew Now" button in the 1015 * early renewals modal by using AJAX instead of a simple redirect. 1016 * 1017 * @param {Event} e The event that occured. 1018 */ 1019 onEarlyRenewalSubmit: function( e ) { 1020 e.preventDefault(); 1021 1022 $.ajax( { 1023 url: $( '#early_renewal_modal_submit' ).attr( 'href' ), 1024 method: 'get', 1025 success: function( html ) { 1026 var response = JSON.parse( html ); 1027 1028 if ( response.stripe_sca_required ) { 1029 wc_stripe_form.openIntentModal( response.intent_secret, response.redirect_url, true, false ); 1030 } else { 1031 window.location = response.redirect_url; 1032 } 1033 }, 1034 } ); 1035 1036 return false;
1037 }, 1038 }; 1039 1040 wc_stripe_form.init(); 1041} );
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.