1/** 2 * Poynt Collect payment form handler. 3 * 4 * @since 1.0.0 5 */ 6jQuery( ( $ ) => { 7 8 'use strict'; 9 10 /** 11 * Payment form handler. 12 * 13 * Interacts with the Poynt Collect API to process a checkout payment form. 14 * 15 * @link https://docs.poynt.com/app-integration/poynt-collect/#poynt-collect 16 * 17 * @since 1.0.0 18 */ 19 window.MWCPaymentsPoyntPaymentFormHandler = class MWCPaymentsPoyntPaymentFormHandler { 20 21 /** 22 * Instantiates the payment form handler. 23 * 24 * Loads the payment handler and intercepts form submissions to inject the token returned by Poynt Collect API. 25 * 26 * @since 1.0.0 27 * 28 * @param {Object} args form handler arguments 29 */ 30 constructor( args ) { 31 32 this.appId = args.appId; 33 this.businessId = args.businessId; 34 this.customerAddress = args.customerAddress; 35 this.shippingAddress = args.shippingAddress; 36 this.isLoggingEnabled = args.isLoggingEnabled; 37 this.options = args.options; 38 this.formInitialized = false; 39 40 // bail if no Poynt wrappers exist on the page 41 if (! $('#mwc-payments-poynt-hosted-form').length) { 42 return; 43 } 44 45 if ( $( 'form.checkout' ).length ) { 46 this.form = $( 'form.checkout' ); 47 this.handleCheckout(); 48 } else if ( $( 'form#order_review' ).length ) { 49 this.form = $( 'form#order_review' ); 50 this.handlePayPage(); 51 } else if ( $( 'form#add_payment_method' ).length ) { 52 this.form = $( 'form#add_payment_method' ); 53 this.handleMyAccount(); 54 } else { 55 this.debugLog('No payment form available'); 56 return; 57 } 58 59 // clear the payment nonce on errors 60 $( document.body ).on( 'checkout_error', () => { 61 this.clearNonce(); 62 } ); 63 } 64 65 /** 66 * Gets the nonce field. 67 * 68 * Returns a jQuery object with the hidden input that holds a nonce value. 69 * 70 * @since 1.0.0 71 * 72 * @returns {Object} jQuery object 73 */ 74 getNonceField() { 75 return $( '#mwc-payments-poynt-payment-nonce' ); 76 } 77 78 /** 79 * Clears the payment nonce. 80 * 81 * Resets the nonce value in the hidden input. 82 * 83 * @since 1.0.0 84 */ 85 clearNonce() { 86 this.getNonceField().val( '' ); 87 } 88 89 /** 90 * Creates a nonce using Poynt Collect. 91 * 92 * Saves the nonce to a hidden input and resubmits the form. 93 * 94 * @link https://docs.poynt.com/app-integration/poynt-collect/#creating-a-nonce 95 * 96 * @since 1.0.0 97 */ 98 createNonce() { 99 100 let nonceData = { 101 businessId: this.businessId 102 }; 103 104 if ( this.customerAddress.firstName ) { 105 nonceData.firstName = this.customerAddress.firstName; 106 } 107 108 if ( this.customerAddress.lastName ) { 109 nonceData.lastName = this.customerAddress.lastName; 110 } 111 112 if ( this.customerAddress.line1 ) { 113 nonceData.line1 = this.customerAddress.line1; 114 } 115 116 if ( this.customerAddress.line2 ) { 117 nonceData.line2 = this.customerAddress.line2; 118 } 119 120 if ( this.customerAddress.city ) { 121 nonceData.city = this.customerAddress.city; 122 } 123 124 if ( this.customerAddress.state ) { 125 nonceData.territory = this.customerAddress.state; 126 } 127 128 if ( this.customerAddress.country ) { 129 nonceData.countryCode = this.customerAddress.country; 130 } 131 132 if ( this.customerAddress.postcode ) { 133 nonceData.zip = this.customerAddress.postcode; 134 } 135 136 if ( this.customerAddress.phone ) { 137 nonceData.phone = this.customerAddress.phone; 138 } 139 140 if ( this.customerAddress.email ) { 141 nonceData.emailAddress = this.customerAddress.email; 142 } 143 144 if ( this.shippingAddress.line1 ) { 145 nonceData.shippingLine1 = this.shippingAddress.line1; 146 } 147 148 if ( this.shippingAddress.line2 ) { 149 nonceData.shippingLine2 = this.shippingAddress.line2; 150 } 151 152 if ( this.shippingAddress.city ) { 153 nonceData.shippingCity = this.shippingAddress.city; 154 } 155 156 if ( this.shippingAddress.state ) { 157 nonceData.shippingTerritory = this.shippingAddress.state; 158 } 159 160 if ( this.shippingAddress.postcode ) { 161 nonceData.shippingZip = this.shippingAddress.postcode; 162 } 163 164 this.debugLog( nonceData ); 165 166 /** 167 * @link https://docs.poynt.com/app-integration/poynt-collect/#collect-getnonce 168 */ 169 this.collect.getNonce( nonceData ); 170 } 171 172 handleCheckout() { 173 $( document.body ).on( 'updated_checkout', () => this.setFields() ); 174 175 $( document.body ).on( 'updated_checkout', () => this.handleSavedPaymentMethods() ); 176 177 this.form.on( 'checkout_place_order_poynt', () => this.validatePaymentData() ); 178 } 179 180 /** 181 * Determines whether a nonce exists. 182 * 183 * Checks the hidden input for a value. 184 * 185 * @since 1.0.0 186 * 187 * @returns {boolean} whether a nonce exists 188 */ 189 hasNonce() { 190 return this.getNonceField().val().length > 0; 191 } 192 193 handleMyAccount() { 194 195 this.setFields(); 196 197 this.form.submit( () => { 198 199 if ( $( '#add_payment_method input[name=payment_method]:checked' ).val() === 'poynt' ) { 200 return this.validatePaymentData(); 201 } 202 } ); 203 } 204 205 /** 206 * Handles the error event data. 207 * 208 * Logs errors to console and maybe renders them in a user-facing notice. 209 * 210 * @link https://docs.poynt.com/app-integration/poynt-collect/getting-started/event-listeners.html#error 211 * 212 * We render their provided message if the error is: 213 * - a result of the form submit (inline field errors are not handled by our JS) 214 * - of type "invalid_details" or "missing_fields". These are the only two known types right now, 215 * but we check for them anyway in case more are added later that we might not want displayed 216 * 217 * Note: the event will have a different shape than what's documented above if it comes after form validation, directly from their 218 * server-side API. They just pass the raw error in that case, so there is no event.data.error object. For that 219 * we just render the generic error message. 220 * 221 * @param {Object} event after a form error 222 */ 223 handleError( event ) { 224 225 // always console log the event 226 this.debugLog( event ); 227 228 // only handle error events 229 if ('error' !== event?.type) { 230 return; 231 } 232 233 // default to a generic error message 234 let errorMessage = poyntPaymentFormI18n.errorMessages.genericError; 235 236 // special handling for errors from the form vs. the API 237 if (event.data?.error) { 238 239 // if this is not a submit event (such as a field change), don't render anything 240 if ('submit' !== event.data.error?.source) { 241 return; 242 } 243 244 // use the provided error message if available and is fixable by C2
245 if (event.data.error.message && ['invalid_details', 'missing_fields'].includes(event.data.error.type)) { 246 errorMessage = event.data.error.message; 247 } 248 } 249 250 // render the error at the top of the page 251 this.renderErrors( [ errorMessage ] ); 252 } 253 254 handlePayPage() { 255 256 this.setFields(); 257 258 this.handleSavedPaymentMethods(); 259 260 this.form.submit( () => { 261 262 if ( $( '#order_review input[name=payment_method]:checked' ).val() === 'poynt' ) { 263 return this.validatePaymentData(); 264 } 265 } ); 266 } 267 268 /** 269 * Handles a payment form ready event. 270 * 271 * Unblocks the payment form after initialization. 272 * 273 * @since 1.0.0 274 * 275 * @param {Object} event after the form is ready 276 */ 277 handlePaymentFormReady( event ) { 278 279 if ( ! event.type || 'ready' !== event.type ) { 280 this.debugLog( event ); 281 } else { 282 this.debugLog( 'Payment form ready' ); 283 } 284 285 this.form.unblock(); 286 } 287 288 /** 289 * Handles a nonce ready event. 290 * 291 * Sets the nonce to hidden field and submits the form. 292 * 293 * @since 1.0.0 294 * 295 * @param {Object} payload containing the nonce 296 */ 297 handleNonceReady( payload ) { 298 299 if ( payload.data && payload.data.nonce ) { 300 this.getNonceField().val( payload.data.nonce ); 301 this.debugLog( 'Nonce set' ); 302 } else { 303 this.clearNonce(); 304 this.debugLog( 'Nonce value is empty' ); 305 } 306 307 this.form.submit(); 308 } 309 310 handleSavedPaymentMethods() { 311 312 let $newMethodForm = $('.mwc-payments-poynt-new-payment-method-form'); 313 314 $('input.mwc-payments-poynt-payment-method').change( () => { 315 316 if ( $( "input.mwc-payments-poynt-payment-method:checked" ).val() ) { 317 $newMethodForm.slideUp( 200 ); 318 } else { 319 $newMethodForm.slideDown( 200 ); 320 } 321 322 } ).change(); 323 324 $( 'input#createaccount' ).change(function () { 325 326 let $parentRow = $('input.mwc-payments-tokenize-payment-method').closest( 'p.form-row' ); 327 328 if ( $( this ).is( ':checked' ) ) { 329 $parentRow.slideDown(); 330 $parentRow.next().show(); 331 } else { 332 $parentRow.hide(); 333 $parentRow.next().hide(); 334 } 335 }); 336 337 if (! $( 'input#createaccount' ).is( ':checked' ) ) { 338 $( 'input#createaccount' ).change(); 339 } 340 } 341 342 /** 343 * Initializes the form. 344 * 345 * Adds listeners for the ready and error events. 346 * 347 * @link https://docs.poynt.com/app-integration/poynt-collect/#collect-mount 348 * 349 * @since 1.0.0 350 */ 351 initForm() { 352 353 // run only once 354 if ( this.initializingForm ) { 355 return; 356 } 357 358 this.initializingForm = true; 359 360 this.collect = new TokenizeJs( this.businessId, this.appId ); 361 362 /** 363 * Initialize the Payment Form with Poynt Collect API. 364 */ 365 this.collect.mount( 'mwc-payments-poynt-hosted-form', document, this.options ); 366 367 // triggers when a nonce is ready 368 this.collect.on( 'nonce', payload => { 369 this.handleNonceReady( payload ); 370 } ); 371 372 // triggers when the payment form is ready 373 this.collect.on( 'ready', event => { 374 375 this.initializingForm = false; 376 this.formInitialized = true; 377 378 this.handlePaymentFormReady( event ); 379 } ); 380 381 // triggers when there is a payment form error 382 this.collect.on( 'error', error => { 383 this.handleError( error ); 384 } ); 385 } 386 387 /** 388 * Sets up the payment fields. 389 * 390 * Calls parent method and initializes the payment form. 391 * 392 * @since 1.0.0 393 */ 394 setFields() { 395 396 this.fields = $('.payment_method_poynt'); 397 398 if ( this.formInitialized ) { 399 this.collect.unmount( 'mwc-payments-poynt-hosted-form', document ); 400 this.formInitialized = false; 401 } 402 403 if ( this.businessId && this.appId && ! this.initializingForm ) { 404 this.initForm(); 405 } 406 } 407 408 validatePaymentData() { 409 410 if ( this.form.is( '.processing' ) ) { 411 return false; 412 } 413 414 if ( this.fields.find( 'input.mwc-payments-poynt-payment-method:checked' ).val() || this.hasNonce() ) { 415 return true; 416 } 417 418 // override the loaded address data if available via form fields 419 if ( $( '#billing_first_name' ).val() ) { 420 this.customerAddress.firstName = $( '#billing_first_name' ).val(); 421 } 422 423 if ( $( '#billing_last_name' ).val() ) { 424 this.customerAddress.lastName = $( '#billing_last_name' ).val(); 425 } 426 427 if ( $( '#billing_phone' ).val() ) { 428 this.customerAddress.phone = $( '#billing_phone' ).val(); 429 } 430 431 if ( $( '#billing_email' ).val() ) { 432 this.customerAddress.email = $( '#billing_email' ).val(); 433 } 434 435 if ( $( '#billing_address_1' ).val() ) { 436 this.customerAddress.line1 = $( '#billing_address_1' ).val(); 437 } 438 439 if ( $( '#billing_address_2' ).val() ) { 440 this.customerAddress.line2 = $( '#billing_address_2' ).val(); 441 } 442 443 if ( $( '#billing_city' ).val() ) { 444 this.customerAddress.city = $( '#billing_city' ).val(); 445 } 446 447 if ( $( '#billing_state' ).val() ) { 448 this.customerAddress.state = $( '#billing_state' ).val(); 449 } 450 451 if ( $( '#billing_country' ).val() ) { 452 this.customerAddress.country = $( '#billing_country' ).val(); 453 } 454 455 if ( $( '#billing_postcode' ).val() ) { 456 this.customerAddress.postcode = $( '#billing_postcode' ).val(); 457 } 458 459 let shipToDifferentAddress = $( '#ship-to-different-address-checkbox' ).is( ':checked' ); 460 461 let shippingLine1 = this.shippingAddress.needsShipping ? (shipToDifferentAddress ? $( '#shipping_address_1' ).val() : this.customerAddress.line1) : ''; 462 let shippingLine2 = this.shippingAddress.needsShipping ? (shipToDifferentAddress ? $( '#shipping_address_2' ).val() : this.customerAddress.line2) : ''; 463 let shippingCity = this.shippingAddress.needsShipping ? (shipToDifferentAddress ? $( '#shipping_city' ).val() : this.customerAddress.city) : ''; 464 let shippingState = this.shippingAddress.needsShipping ? (shipToDifferentAddress ? $( '#shipping_state' ).val() : this.customerAddress.state) : ''; 465 let shippingPostcode = this.shippingAddress.needsShipping ? (shipToDifferentAddress ? $( '#shipping_postcode' ).val() : this.customerAddress.postcode) : ''; 466 467 if ( shippingLine1 && shippingLine1.length > 0 ) { 468 this.shippingAddress.line1 = shippingLine1; 469 } 470 if ( shippingLine2 && shippingLine2.length > 0 ) { 471 this.shippingAddress.line2 = shippingLine2; 472 } 473 if ( shippingCity && shippingCity.length > 0 ) { 474 this.shippingAddress.city = shippingCity; 475 } 476 if ( shippingState && shippingState.length > 0 ) { 477 this.shippingAddress.state = shippingState; 478 } 479 if ( shippingPostcode && shippingPostcode.length > 0 ) { 480 this.shippingAddress.postcode = shippingPostcode; 481 } 482 483 // block the UI 484 this.form.block( { message: null, overlayCSS: { background: '#fff', opacity: 0.6 } } ); 485 486 // create the nonce 487 this.createNonce(); 488 489 // always return false to resubmit the form 490 return false;
491 } 492 493 /** 494 * Logs an item to console if logging is enabled. 495 * 496 * @since 1.0.0 497 * 498 * @param {String|Object} logData 499 */ 500 debugLog( logData ) { 501 if ( this.isLoggingEnabled ) { 502 console.log( logData ); 503 } 504 } 505 506 renderErrors(errors) { 507 $( '.woocommerce-error, .woocommerce-message' ).remove(); 508 509 this.form.prepend( '<ul class="woocommerce-error"><li>' + errors.join( '</li><li>' ) + '</li></ul>' ); 510 511 this.form.removeClass( 'processing' ).unblock(); 512 this.form.find( '.input-text, select' ).blur(); 513 514 $( 'html, body' ).animate( { scrollTop: this.form.offset().top - 100 }, 1000 ); 515 } 516 517 } 518 519 // dispatch loaded event 520 $( document.body ).trigger( 'mwc_payments_poynt_payment_form_handler_loaded' ); 521 522} );
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.