1/** 2 * Stripe payment form handler. 3 */ 4jQuery(($) => { 5 6 'use strict'; 7 8 /** 9 * Payment form handler. 10 * 11 * Interacts with Stripe Elements to process a checkout payment form. 12 */ 13 window.MWCPaymentsStripePaymentFormHandler = class MWCPaymentsStripePaymentFormHandler { 14 15 /** 16 * Instantiates the payment form handler. 17 * 18 * Loads the payment handler and intercepts form submissions to process the payment using Stripe Elements. 19 * 20 * @param {Object} args form handler arguments 21 */ 22 constructor(args) { 23 this.appInfo = args.appInfo; 24 this.appearance = args.appearance; 25 this.publishableKey = args.publishableKey; 26 this.billingDetails = args.billingDetails; 27 this.isLoggingEnabled = args.isLoggingEnabled; 28 this.redirectUrl = args.redirectUrl; 29 this.genericError = args.genericError; 30 this.isDetailedDecline = args.isDetailedDecline; 31 this.reusablePaymentMethodTypes = args.reusablePaymentMethodTypes; 32 33 // bail if no Stripe wrappers exist on the page 34 if (! $('#mwc-payments-stripe-form').length) { 35 return; 36 } 37 38 this.initialize(); 39 40 if ( $( 'form.checkout' ).length ) { 41 this.form = $( 'form.checkout' ); 42 this.handleCheckout(); 43 } else if ( $( 'form#order_review' ).length ) { 44 this.form = $( 'form#order_review' ); 45 this.handlePayPage(); 46 } else if ( $( 'form#add_payment_method' ).length ) { 47 this.form = $( 'form#add_payment_method' ); 48 this.handleMyAccount(); 49 } else { 50 this.debugLog('No payment form available'); 51 } 52 } 53 54 /** 55 * Initializes the Stripe SDK and Elements 56 */ 57 initialize() { 58 this.stripe = Stripe(this.publishableKey); 59 60 this.stripe.registerAppInfo(this.appInfo); 61 62 this.refreshForm() 63 } 64 65 /** 66 * Initializes Stripe Elements 67 */ 68 initElements() { 69 70 this.clientSecret = $('#mwc-payments-stripe-client-secret').val(); 71 72 this.elements = this.stripe.elements({ 73 clientSecret: this.clientSecret, 74 appearance: this.appearance, 75 }) 76 } 77 78 /** 79 * Initializes the payment element 80 */ 81 initPaymentElement() { 82 83 this.paymentElement = this.elements.create('payment', { 84 fields: { 85 billingDetails: { 86 name: this.getFormFieldState(this.billingDetails.name, '#billing_first_name', '#billing_last_name' ), 87 email: this.getFormFieldState(this.billingDetails.email, '#billing_email'), 88 phone: this.getFormFieldState(this.billingDetails.phone, '#billing_phone'), 89 address: { 90 line1: this.getFormFieldState(this.billingDetails.address.line1, '#billing_address_1'), 91 line2: this.getFormFieldState(this.billingDetails.address.line1, '#billing_address_2'), 92 city: this.getFormFieldState(this.billingDetails.address.city, '#billing_city'), 93 state: this.getFormFieldState(this.billingDetails.address.state, '#billing_state'), 94 country: this.getFormFieldState(this.billingDetails.address.country, '#billing_country'), 95 postalCode: this.getFormFieldState(this.billingDetails.address.postal_code, '#billing_postcode'), 96 }, 97 } 98 } 99 }) 100 } 101 102 /** 103 * Handles the saved payment methods 104 */ 105 handleSavedPaymentMethods() { 106 107 let $newMethodForm = $('.mwc-payments-stripe-new-payment-method-form'); 108 109 $('input.mwc-payments-stripe-payment-method').change( () => { 110 111 if ( $( "input.mwc-payments-stripe-payment-method:checked" ).val() ) { 112 $newMethodForm.slideUp( 200 ); 113 } else { 114 $newMethodForm.slideDown( 200 ); 115 } 116 117 } ).change(); 118 119 $('input#createaccount').change((event) => { 120 this.toggleSaveMethod($(event.target).is(':checked')); 121 }); 122 123 if (! $('input#createaccount').is(':checked')) { 124 $('input#createaccount').change(); 125 } 126 127 // when the save method checkbox is checked, ensure the payment element displays the terms 128 $('input.mwc-payments-tokenize-payment-method').change((event) => { 129 130 let termsValue = $(event.target).is(':checked') ? 'always' : 'auto'; 131 132 this.paymentElement.update({ 133 terms: { 134 auBecsDebit: termsValue, 135 bancontact: termsValue, 136 card: termsValue, 137 ideal: termsValue, 138 sepaDebit: termsValue, 139 sofort: termsValue, 140 usBankAccount: termsValue, 141 } 142 }); 143 }); 144 } 145 146 /** 147 * Toggles the "Save method" checkbox availability. 148 * 149 * @param {Boolean} isAvailable 150 */ 151 toggleSaveMethod(isAvailable) { 152 let $checkbox = $('input.mwc-payments-tokenize-payment-method'); 153 let $parentRow = $checkbox.closest( 'p.form-row' ); 154 155 if (isAvailable) { 156 $parentRow.slideDown(); 157 $parentRow.next().show(); 158 } else { 159 $parentRow.hide(); 160 $parentRow.next().hide(); 161 162 $checkbox.prop('checked', false); 163 } 164 } 165 166 /** 167 * Gets the state of a Stripe Element form field depending on the potential for its value availability. 168 *
169 * - If the value is already available, we know we never need to collect it on the Stripe form. 170 * - If the value is not yet available but at least one of its form fields are present in the DOM, we know it will be collected by the Woo form and will be available when payment is submitted. 171 * 172 * @param {string} availableValue 173 * @param {string} sourceDomElements 174 * 175 * @returns {string} 176 */ 177 getFormFieldState(availableValue, ...sourceDomElements) { 178 179 return availableValue || sourceDomElements.some(function (sourceDomElement) { 180 return $(sourceDomElement).length > 0; 181 }) ? 'never' : 'auto' 182 } 183 184 /** 185 * Refreshes the payment form. 186 */ 187 refreshForm() { 188 this.initElements() 189 190 if (this.paymentElement) { 191 this.paymentElement.destroy() 192 } 193 194 this.initPaymentElement() 195 196 // ensure the saved method checkbox is toggled depending on the chosen payment method type 197 this.paymentElement.on('change', (event) => { 198 this.toggleSaveMethod(this.reusablePaymentMethodTypes.includes(event.value.type)); 199 }); 200 201 this.paymentElement.mount('#mwc-payments-stripe-form') 202 } 203 204 /** 205 * Handles the checkout page. 206 */ 207 handleCheckout() { 208 $( document.body ).on( 'updated_checkout', () => { 209 this.refreshForm() 210 this.handleSavedPaymentMethods() 211 } ); 212 213 $( this.form ).on( 'checkout_place_order_stripe', () => { 214 215 // submit the form and process server-side if a saved method is selected 216 if ( $( "input.mwc-payments-stripe-payment-method:checked" ).val() ) { 217 return true 218 } 219 220 $( this.form ).on('checkout_place_order_success.mwc-stripe', (event, data) => { 221 222 let returnUrl = data.redirect 223 224 if (data.billingDetails) { 225 this.billingDetails = data.billingDetails; 226 } 227 228 // prevent WC from redirecting before we can confirm the payment 229 data.redirect = '#!' 230 231 if (returnUrl !== '#!') { 232 this.handleConfirm(returnUrl); 233 } 234 }) 235 }); 236 } 237 238 /** 239 * Handles the order pay page 240 */ 241 handlePayPage() { 242 this.refreshForm(); 243 this.handleSavedPaymentMethods() 244 245 this.form.submit( (event) => { 246 if ( $( '#order_review input[name=payment_method]:checked' ).val() === 'stripe' ) { 247 248 // submit the form and process server-side if a saved method is selected 249 if ( $( "input.mwc-payments-stripe-payment-method:checked" ).val() ) { 250 return true 251 } 252 253 event.preventDefault() 254 255 if ($('#mwc-payments-stripe-tokenize-payment-method').is(':checked')) { 256 let url = new URL(this.redirectUrl); 257 url.searchParams.set("shouldTokenize", "true"); 258 this.redirectUrl = url.href; 259 } 260 261 this.handleConfirm(this.redirectUrl); 262 } 263 } ); 264 } 265 266 handleMyAccount() { 267 268 this.form.submit( (event) => { 269 270 if ( $( '#add_payment_method input[name=payment_method]:checked' ).val() === 'stripe' ) { 271 event.preventDefault(); 272 273 this.handleConfirm(this.redirectUrl); 274 } 275 } ); 276 } 277 278 /** 279 * Handles confirming a payment or setup intent. 280 * 281 * @param {String} returnUrl 282 */ 283 handleConfirm(returnUrl = null) { 284 285 // if the client secret is for a setup intent 286 if (this.clientSecret.toString().startsWith('seti_')) { 287 288 this.handleConfirmSetup(returnUrl); 289 290 return; 291 } 292 293 this.handleConfirmPayment(returnUrl); 294 } 295 296 /** 297 * Handles confirming a setup intent. 298 * 299 * @param {String} returnUrl 300 */ 301 handleConfirmSetup(returnUrl = null) { 302 303 this.stripe.confirmSetup({ 304 elements: this.elements, 305 confirmParams: { 306 payment_method_data: { 307 billing_details: this.billingDetails, 308 }, 309 return_url: returnUrl, 310 }, 311 }).then(result => { 312 if (result.error) { 313 this.handleError(result.error) 314 } 315 }); 316 } 317 318 /** 319 * Handles confirming a payment intent. 320 * 321 * @param {String} returnUrl 322 */ 323 handleConfirmPayment(returnUrl = null) { 324 325 this.stripe.confirmPayment({ 326 elements: this.elements, 327 confirmParams: { 328 payment_method_data: { 329 billing_details: this.billingDetails, 330 }, 331 return_url: returnUrl, 332 } 333 }).then(result => { 334 335 $(this.form).off('checkout_place_order_success.mwc-stripe'); 336 337 if (result.error) { 338 this.handleError(result.error) 339 } 340 }); 341 } 342 343 /** 344 * Handles the error event data. 345 * 346 * Logs errors to console. 347 * 348 * @param {Object} event after a form error 349 */ 350 handleError(event) { 351 $( '.woocommerce-error, .woocommerce-message' ).remove(); 352 353 if ((event.type === 'validation_error') || (event.type === 'card_error' && this.isDetailedDecline)) { 354 this.form.prepend( '<ul class="woocommerce-error"><li>' + event.message + '</li></ul>' ); 355 } else { 356 this.form.prepend( '<ul class="woocommerce-error"><li>' + this.genericError + '</li></ul>' ); 357 } 358 359 this.form.removeClass( 'processing' ).unblock(); 360 this.form.find( '.input-text, select' ).blur(); 361 362 $( 'html, body' ).animate( { scrollTop: this.form.offset().top - 100 }, 1000 ); 363 364 this.debugLog(event); 365 } 366 367 /** 368 * Logs an item to console if logging is enabled. 369 */ 370 debugLog() { 371 if (this.isLoggingEnabled) { 372 console.log.apply(null, arguments); 373 } 374 } 375 376 } 377 378 // dispatch loaded event 379 $(document.body).trigger('mwc_payments_stripe_payment_form_handler_loaded'); 380 381});
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.