swissChili | f0cbdc3 | 2023-01-05 17:21:38 -0500 | [diff] [blame] | 1 | /* global wc_stripe_params */ |
| 2 | |
| 3 | jQuery( 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 | } ); |