1/* global fetch, CustomEvent */ 2if (window.dw && window.dw.applepay && window.ApplePaySession && window.ApplePaySession.canMakePayments() && typeof fetch === 'function') { 3 (function (config, location, ApplePaySession) { 4 /** Used version of ApplePaySession */ 5 var APPLE_PAY_VERSION = 1; 6 7 /** CSS class name indicating an element was processed by a directive */ 8 var CLASS_PROCESSED = 'dw-apple-pay-processed'; 9 10 /** Name of the SKU element attribute indicating add to basket */ 11 var ATTR_SKU = 'sku'; 12 13 /** Left hand side of cookie indicating to update the Apple Pay request. */ 14 var UPDATE_REQUEST_COOKIE_LHS = '; dwapupreq='; 15 16 /** Map of Demandware status names to Apple Pay status codes */ 17 var STATUSES = { 18 Failure: ApplePaySession.STATUS_FAILURE, 19 InvalidBillingPostalAddress: ApplePaySession.STATUS_INVALID_BILLING_POSTAL_ADDRESS, 20 InvalidShippingPostalAddress: ApplePaySession.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS, 21 InvalidShippingContact: ApplePaySession.STATUS_INVALID_SHIPPING_CONTACT, 22 PINRequired: ApplePaySession.STATUS_PIN_REQUIRED, 23 PINIncorrect: ApplePaySession.STATUS_PIN_INCORRECT, 24 PINLockout: ApplePaySession.STATUS_PIN_LOCKOUT 25 }; 26 27 if (location.protocol !== 'https:' || !ApplePaySession.supportsVersion(APPLE_PAY_VERSION)) { 28 return; 29 } 30 var action = config.action; 31 var inject = config.inject; 32 33 /** Last value of the cookie indicating to update the Apple Pay request. */ 34 var lastUpdateRequestCookie; 35 36 /** URL to which to redirect if the Apple Pay session is aborted. */ 37 var redirect; 38 39 /** Latest Apple Pay request data to use to create a session. */ 40 var request; 41 42 /** Request object that gets updated with new data from server responses. */ 43 var updatedRequest; 44 45 /** Current Apple Pay session. */ 46 var session; 47 48 function hasClass (element, className) { 49 return (' ' + element.className + ' ').replace(/[\t\r\n\f]/g, ' ').indexOf(' ' + className + ' ') > -1; 50 } 51 52 function mapStatus (status) { 53 if (status && STATUSES[status]) { 54 return STATUSES[status]; 55 } 56 return ApplePaySession.STATUS_FAILURE; 57 } 58 59 function handleResponse (response) { 60 return response.json() 61 .then(function (json) { 62 if (response.status >= 200 && response.status < 300) { 63 // Return success JSON response 64 return json; 65 } 66 67 // Throw error with response status 68 var err = new Error(json ? json.status : 'Request error'); 69 err.response = json; 70 throw err; 71 }); 72 } 73 74 function postJson (url, data) { 75 var json = data; 76 if (typeof data === 'object') { 77 json = JSON.stringify(data); 78 } else if (typeof data !== 'string') { 79 throw new Error('Data must be an object or a JSON string.'); 80 } 81 return fetch(url, { 82 method: 'POST', 83 credentials: 'include', 84 headers: { 85 'Content-Type': 'application/json', 86 'Accept': 'application/json' 87 }, 88 body: json 89 }).then(handleResponse); 90 } 91 92 function getJson (url) { 93 return fetch(url, { 94 credentials: 'include', 95 headers: { 96 'Accept': 'application/json' 97 } 98 }).then(handleResponse); 99 } 100 101 /* Filter out properties that are not from Event prototype, 102 * and include all others as ownProperty to a new object. 103 * @param {Event} e 104 * @return {Object} object with relevant properties as ownProperty 105 */ 106 function filterEvent (e) { 107 var filteredEvent = {}; 108 for (var prop in e) { 109 /* global Event */ 110 if (!Event.prototype.hasOwnProperty(prop)) { 111 filteredEvent[prop] = e[prop]; 112 } 113 } 114 return filteredEvent; 115 } 116 117 /** 118 * On successful creation of an order, submit it through a form 119 * @param {String} formAction URL to which to POST form to 120 */ 121 function submitOrder (formAction) { 122 var form = document.createElement('form'); 123 form.action = formAction; 124 form.method = 'post'; 125 126 document.body.appendChild(form); 127 form.submit(); 128 } 129 130 function doRedirect () { 131 if (redirect) { 132 // Redirect to last responded redirect 133 location.href = redirect; 134 } 135 } 136 137 function dispatchEvent (event) { 138 if (!event || !event.name) { 139 return; 140 } 141 document.body.dispatchEvent(new CustomEvent(event.name, { 142 bubbles: true, 143 detail: event.detail 144 })); 145 } 146 147 function processServerResponse (response) { 148 if (!response) { 149 return; 150 } 151 // redirect could be an empty string 152 if (typeof response.redirect !== 'undefined') { 153 // set redirect URL 154 redirect = response.redirect; 155 } 156 dispatchEvent(response.event); 157 } 158 159 function oncancelHandler (e) { 160 setSession(null); 161 162 postJson(action.cancel, {}).then(function (response) { 163 processServerResponse(response); 164 doRedirect(); 165 }, function (error) { 166 processServerResponse(error.response); 167 doRedirect(); 168 }).catch(function (err) { 169 console.error(err); 170 }); 171 } 172 173 function mapPaymentAuthorizedResult(response) { 174 if (response.result) { 175 if (response.result.errors) { 176 response.result.errors = response.result.errors.map(function (err) { 177 return new ApplePayError(err.code, err.contactField, err.message); 178 }); 179 } 180 return response.result; 181 } 182 183 // Fall back to legacy result 184 return ApplePaySession.STATUS_SUCCESS; 185 } 186 187 function onpaymentauthorizedHandler (e) { 188 postJson(action.onpaymentauthorized, filterEvent(e)).then(function (response) { 189 session.completePayment(mapPaymentAuthorizedResult(response)); 190 processServerResponse(response); 191 setSession(null); 192 submitOrder(response.redirect); 193 }, function (error) { 194 session.completePayment(mapStatus(error.message)); 195 processServerResponse(error.response); 196 doRedirect(); 197 }).catch(function (err) { 198 console.error(err); 199 }); 200 } 201 202 function onpaymentmethodselectedHandler (e) { 203 postJson(action.onpaymentmethodselected, filterEvent(e)).then(function (response) { 204 // update updatedRequest with new server response 205 updatedRequest = Object.assign(updatedRequest, response); 206 session.completePaymentMethodSelection(response.total, response.lineItems); 207 processServerResponse(response); 208 }, function (error) { 209 // No way to communicate error status on this event to Apple Pay 210 session.completePaymentMethodSelection( 211 updatedRequest.total, updatedRequest.lineItems); 212 processServerResponse(error.response); 213 }).catch(function (err) { 214 console.error(err); 215 }); 216 } 217 218 function onshippingcontactselectedHandler (e) { 219 postJson(action.onshippingcontactselected, filterEvent(e)).then(function (response) { 220 // update updatedRequest with new server response 221 updatedRequest = Object.assign(updatedRequest, response); 222 session.completeShippingContactSelection(ApplePaySession.STATUS_SUCCESS, 223 response.shippingMethods, response.total, response.lineItems); 224 processServerResponse(response); 225 }, function (error) { 226 session.completeShippingContactSelection(mapStatus(error.message), 227 [], updatedRequest.total, updatedRequest.lineItems); 228 processServerResponse(error.response); 229 }).catch(function (err) { 230 console.error(err); 231 }); 232 } 233 234 function onshippingmethodselectedHandler (e) { 235 postJson(action.onshippingmethodselected, filterEvent(e)).then(function (response) { 236 // update updatedRequest with new server response 237 updatedRequest = Object.assign(updatedRequest, response); 238 session.completeShippingMethodSelection(ApplePaySession.STATUS_SUCCESS, 239 response.total, response.lineItems); 240 processServerResponse(response); 241 }, function (error) { 242 session.completeShippingMethodSelection(mapStatus(error.message), 243 updatedRequest.total, updatedRequest.lineItems); 244 processServerResponse(error.response); 245 }).catch(function (err) { 246 console.error(err); 247 }); 248 } 249 250 function onvalidatemerchantHandler (e) { 251 postJson(action.onvalidatemerchant, Object.assign(filterEvent(e), { 252 hostname: window.location.hostname 253 })).then(function (response) { 254 session.completeMerchantValidation(response.session); 255 }, function (error) { 256 session.abort(); 257 }).catch(function (err) { 258 console.error(err); 259 }); 260 } 261 262 function setSession (s) { 263 if (session) { 264 session.oncancel = null; 265 session.onpaymentauthorized = null; 266 session.onpaymentmethodselected = null; 267 session.onshippingcontactselected = null; 268 session.onshippingmethodselected = null; 269 session.onvalidatemerchant = null; 270 } 271 272 session = s; 273 274 if (session) { 275 session.oncancel = oncancelHandler; 276 session.onpaymentauthorized = onpaymentauthorizedHandler; 277 session.onpaymentmethodselected = onpaymentmethodselectedHandler; 278 session.onshippingcontactselected = onshippingcontactselectedHandler; 279 session.onshippingmethodselected = onshippingmethodselectedHandler; 280 session.onvalidatemerchant = onvalidatemerchantHandler; 281 } 282 } 283 284 /** 285 * Begins the ApplePaySession. 286 */ 287 function createSession () { 288 // fake the total amount to make it a valid request 289 // if the basket is empty. 290 // @TODO some basic addition should be calculated instead 291 // to reflect a more reasonable amount 292 // this relies on the SKU price to be available on render 293 if (parseFloat(request.total.amount) === 0) { 294 request.total.amount = '0.01'; 295 } 296 setSession(new ApplePaySession(APPLE_PAY_VERSION, request)); 297 session.begin(); 298 299 // set updatedRequest to request 300 updatedRequest = Object.assign({}, request); 301 } 302 303 /** 304 * Prepares an Apple Pay basket, optionally 305 * containing exclusively the product with the given SKU to buy. 306 * @param {String} [sku] SKU of product to buy 307 */ 308 function prepareBasket (sku) { 309 postJson(action.prepareBasket, { 310 sku: sku 311 }).then(function (response) { 312 processServerResponse(response); 313 }, function (error) { 314 try { 315 session.abort(); 316 } catch (e) { 317 // Swallow an error if Apple Pay throws when aborting session 318 console.error(e); 319 } 320 processServerResponse(error.response); 321 doRedirect(); 322 }).catch(function (err) { 323 console.error(err); 324 }); 325 } 326 327 function validateInject (element, directive) { 328 if (element.hasAttribute(ATTR_SKU)) { 329 // No minimum total price required to inject for add to cart 330 return !!request; 331 } 332 333 // Inject only if request total price is positive 334 return request && request.total && request.total.amount && 335 parseFloat(request.total.amount) > 0; 336 } 337 338 function createButton (element, directive) { 339 // Create button element 340 var button = document.createElement('button'); 341 button.type = 'button'; 342 343 // Compose button class name 344 var className = ''; 345 if (directive.css) { 346 className += directive.css; 347 } 348 349 // Copy attributes to button 350 if (directive.copy) { 351 if (element.getAttribute(ATTR_SKU)) { 352 button.setAttribute(ATTR_SKU, element.getAttribute(ATTR_SKU)); 353 } 354 if (element.className) { 355 className += ' ' + element.className; 356 }
357 if (element.id) { 358 var id = element.id; 359 element.id = ''; 360 button.id = id; 361 } 362 } 363 364 if (className) { 365 button.className = className; 366 } 367 368 // Dynamically style the button 369 if (directive.style) { 370 var styleElement; 371 if (directive.style.ref === 'this') { 372 // Style reference is same element 373 styleElement = element; 374 } else if (directive.style.ref) { 375 // Query style reference element 376 styleElement = document.querySelector(directive.style.ref); 377 } 378 379 if (styleElement && directive.style.attr && 'getComputedStyle' in window) { 380 // Copy declared style attributes to button 381 var computedStyle = window.getComputedStyle(styleElement); 382 var style = ''; 383 for (var i = 0; i < directive.style.attr.length; i++) { 384 style += directive.style.attr[i] + ':' + computedStyle.getPropertyValue(directive.style.attr[i]) + ';'; 385 } 386 button.style = style; 387 } 388 } 389 390 // Add button click handler 391 button.onclick = function () { 392 // Create Apple Pay basket 393 prepareBasket(button.getAttribute(ATTR_SKU)); 394 395 // Create Apple Pay session synchronously 396 createSession(); 397 398 // Quit further event handling 399 return false; 400 }; 401 402 return button; 403 } 404 405 function process (element, directive) { 406 if (hasClass(element, CLASS_PROCESSED)) { 407 // Element has already been processed 408 return; 409 } 410 411 if (!validateInject(element, directive)) { 412 // Element is not valid for this directive 413 return; 414 } 415 416 // Create button for element 417 var button = createButton(element, directive); 418 419 // Execute action for button 420 switch (directive.action) { 421 case 'after': 422 element.parentNode.appendChild(button, element.nextSibling); 423 break; 424 case 'before': 425 element.parentNode.insertBefore(button, element); 426 break; 427 case 'replace': 428 element.parentNode.insertBefore(button, element); 429 element.parentNode.removeChild(element); 430 break; 431 case 'append': 432 element.appendChild(button); 433 break; 434 } 435 436 // Mark element as processed 437 element.className += ' ' + CLASS_PROCESSED; 438 } 439 440 function processDirectives () { 441 if (!request) { 442 // Quit processing without a valid request 443 return; 444 } 445 446 var directive, elements; 447 for (var i = 0; i < inject.directives.length; i++) { 448 directive = inject.directives[i]; 449 elements = document.querySelectorAll(directive.query); 450 if (elements && elements.length > 0) { 451 for (var j = 0; j < elements.length; j++) { 452 process(elements[j], directive); 453 } 454 } 455 } 456 457 if (inject.directives.length) { 458 // Repeat processing in one second 459 setTimeout(processDirectives, 1000); 460 } 461 } 462 463 /** 464 * Retrieves Apple Pay request info from the server. 465 */ 466 function getRequest () { 467 getJson(action.getRequest).then(function (response) { 468 request = Object.assign({}, response.request); 469 processDirectives(); 470 processServerResponse(response); 471 }).catch(function (err) { 472 console.error(err); 473 processServerResponse(err.response); 474 }); 475 } 476 477 function getUpdateRequestCookie () { 478 var cookie = '; ' + (document.cookie || '') + '; '; 479 var start = cookie.indexOf(UPDATE_REQUEST_COOKIE_LHS); 480 if (start < 0) { 481 return ''; 482 } 483 484 start += UPDATE_REQUEST_COOKIE_LHS.length; 485 return cookie.substring(start, cookie.indexOf('; ', start)); 486 } 487 488 function pollCookies () { 489 var value = getUpdateRequestCookie(); 490 if (value && value !== lastUpdateRequestCookie) { 491 lastUpdateRequestCookie = value; 492 getRequest(); 493 } 494 } 495 496 // Kick off XHR to get initial Apple Pay request 497 getRequest(); 498 499 // Poll for cookie to update Apple Pay request 500 lastUpdateRequestCookie = getUpdateRequestCookie(); 501 setInterval(pollCookies, 200); 502 })(window.dw.applepay, window.location, window.ApplePaySession); 503}
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.