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 onpaymentauthorizedHandler (e) { 174 postJson(action.onpaymentauthorized, filterEvent(e)).then(function (response) { 175 session.completePayment(ApplePaySession.STATUS_SUCCESS); 176 processServerResponse(response); 177 setSession(null); 178 submitOrder(response.redirect); 179 }, function (error) { 180 session.completePayment(mapStatus(error.message)); 181 processServerResponse(error.response); 182 doRedirect(); 183 }).catch(function (err) { 184 console.error(err); 185 }); 186 } 187 188 function onpaymentmethodselectedHandler (e) { 189 postJson(action.onpaymentmethodselected, filterEvent(e)).then(function (response) { 190 // update updatedRequest with new server response 191 updatedRequest = Object.assign(updatedRequest, response); 192 session.completePaymentMethodSelection(response.total, response.lineItems); 193 processServerResponse(response); 194 }, function (error) { 195 // No way to communicate error status on this event to Apple Pay 196 session.completePaymentMethodSelection( 197 updatedRequest.total, updatedRequest.lineItems); 198 processServerResponse(error.response); 199 }).catch(function (err) { 200 console.error(err); 201 }); 202 } 203 204 function onshippingcontactselectedHandler (e) { 205 postJson(action.onshippingcontactselected, filterEvent(e)).then(function (response) { 206 // update updatedRequest with new server response 207 updatedRequest = Object.assign(updatedRequest, response); 208 session.completeShippingContactSelection(ApplePaySession.STATUS_SUCCESS, 209 response.shippingMethods, response.total, response.lineItems); 210 processServerResponse(response); 211 }, function (error) { 212 session.completeShippingContactSelection(mapStatus(error.message), 213 [], updatedRequest.total, updatedRequest.lineItems); 214 processServerResponse(error.response); 215 }).catch(function (err) { 216 console.error(err); 217 }); 218 } 219 220 function onshippingmethodselectedHandler (e) { 221 postJson(action.onshippingmethodselected, filterEvent(e)).then(function (response) { 222 // update updatedRequest with new server response 223 updatedRequest = Object.assign(updatedRequest, response); 224 session.completeShippingMethodSelection(ApplePaySession.STATUS_SUCCESS, 225 response.total, response.lineItems); 226 processServerResponse(response); 227 }, function (error) { 228 session.completeShippingMethodSelection(mapStatus(error.message), 229 updatedRequest.total, updatedRequest.lineItems); 230 processServerResponse(error.response); 231 }).catch(function (err) { 232 console.error(err); 233 }); 234 } 235 236 function onvalidatemerchantHandler (e) { 237 postJson(action.onvalidatemerchant, Object.assign(filterEvent(e), { 238 hostname: window.location.hostname 239 })).then(function (response) { 240 session.completeMerchantValidation(response.session); 241 }, function (error) { 242 session.abort(); 243 }).catch(function (err) { 244 console.error(err); 245 }); 246 } 247 248 function setSession (s) { 249 if (session) { 250 session.oncancel = null; 251 session.onpaymentauthorized = null; 252 session.onpaymentmethodselected = null; 253 session.onshippingcontactselected = null; 254 session.onshippingmethodselected = null; 255 session.onvalidatemerchant = null; 256 } 257 258 session = s; 259 260 if (session) { 261 session.oncancel = oncancelHandler; 262 session.onpaymentauthorized = onpaymentauthorizedHandler; 263 session.onpaymentmethodselected = onpaymentmethodselectedHandler; 264 session.onshippingcontactselected = onshippingcontactselectedHandler; 265 session.onshippingmethodselected = onshippingmethodselectedHandler; 266 session.onvalidatemerchant = onvalidatemerchantHandler; 267 } 268 } 269 270 /** 271 * Begins the ApplePaySession. 272 */ 273 function createSession () { 274 // fake the total amount to make it a valid request 275 // if the basket is empty. 276 // @TODO some basic addition should be calculated instead 277 // to reflect a more reasonable amount 278 // this relies on the SKU price to be available on render 279 if (parseFloat(request.total.amount) === 0) { 280 request.total.amount = '0.01'; 281 } 282 setSession(new ApplePaySession(APPLE_PAY_VERSION, request)); 283 session.begin(); 284 285 // set updatedRequest to request 286 updatedRequest = Object.assign({}, request); 287 } 288 289 /** 290 * Prepares an Apple Pay basket, optionally 291 * containing exclusively the product with the given SKU to buy. 292 * @param {String} [sku] SKU of product to buy 293 */ 294 function prepareBasket (sku) { 295 postJson(action.prepareBasket, { 296 sku: sku 297 }).then(function (response) { 298 processServerResponse(response); 299 }, function (error) { 300 try { 301 session.abort(); 302 } catch (e) { 303 // Swallow an error if Apple Pay throws when aborting session 304 console.error(e); 305 } 306 processServerResponse(error.response); 307 doRedirect(); 308 }).catch(function (err) { 309 console.error(err); 310 }); 311 } 312 313 function validateInject (element, directive) { 314 if (element.hasAttribute(ATTR_SKU)) { 315 // No minimum total price required to inject for add to cart 316 return !!request; 317 } 318 319 // Inject only if request total price is positive 320 return request && request.total && request.total.amount && 321 parseFloat(request.total.amount) > 0; 322 } 323 324 function createButton (element, directive) { 325 // Create button element 326 var button = document.createElement('button'); 327 button.type = 'button'; 328 329 // Compose button class name 330 var className = ''; 331 if (directive.css) { 332 className += directive.css; 333 } 334 335 // Copy attributes to button 336 if (directive.copy) { 337 if (element.getAttribute(ATTR_SKU)) { 338 button.setAttribute(ATTR_SKU, element.getAttribute(ATTR_SKU)); 339 } 340 if (element.className) { 341 className += ' ' + element.className; 342 }
343 if (element.id) { 344 var id = element.id; 345 element.id = ''; 346 button.id = id; 347 } 348 } 349 350 if (className) { 351 button.className = className; 352 } 353 354 // Dynamically style the button 355 if (directive.style) { 356 var styleElement; 357 if (directive.style.ref === 'this') { 358 // Style reference is same element 359 styleElement = element; 360 } else if (directive.style.ref) { 361 // Query style reference element 362 styleElement = document.querySelector(directive.style.ref); 363 } 364 365 if (styleElement && directive.style.attr && 'getComputedStyle' in window) { 366 // Copy declared style attributes to button 367 var computedStyle = window.getComputedStyle(styleElement); 368 var style = ''; 369 for (var i = 0; i < directive.style.attr.length; i++) { 370 style += directive.style.attr[i] + ':' + computedStyle.getPropertyValue(directive.style.attr[i]) + ';'; 371 } 372 button.style = style; 373 } 374 } 375 376 // Add button click handler 377 button.onclick = function () { 378 // Create Apple Pay basket 379 prepareBasket(button.getAttribute(ATTR_SKU)); 380 381 // Create Apple Pay session synchronously 382 createSession(); 383 384 // Quit further event handling 385 return false; 386 }; 387 388 return button; 389 } 390 391 function process (element, directive) { 392 if (hasClass(element, CLASS_PROCESSED)) { 393 // Element has already been processed 394 return; 395 } 396 397 if (!validateInject(element, directive)) { 398 // Element is not valid for this directive 399 return; 400 } 401 402 // Create button for element 403 var button = createButton(element, directive); 404 405 // Execute action for button 406 switch (directive.action) { 407 case 'after': 408 element.parentNode.appendChild(button, element.nextSibling); 409 break; 410 case 'before': 411 element.parentNode.insertBefore(button, element); 412 break; 413 case 'replace': 414 element.parentNode.insertBefore(button, element); 415 element.parentNode.removeChild(element); 416 break; 417 case 'append': 418 element.appendChild(button); 419 break; 420 } 421 422 // Mark element as processed 423 element.className += ' ' + CLASS_PROCESSED; 424 } 425 426 function processDirectives () { 427 if (!request) { 428 // Quit processing without a valid request 429 return; 430 } 431 432 var directive, elements; 433 for (var i = 0; i < inject.directives.length; i++) { 434 directive = inject.directives[i]; 435 elements = document.querySelectorAll(directive.query); 436 if (elements && elements.length > 0) { 437 for (var j = 0; j < elements.length; j++) { 438 process(elements[j], directive); 439 } 440 } 441 } 442 443 if (inject.directives.length) { 444 // Repeat processing in one second 445 setTimeout(processDirectives, 1000); 446 } 447 } 448 449 /** 450 * Retrieves Apple Pay request info from the server. 451 */ 452 function getRequest () { 453 getJson(action.getRequest).then(function (response) { 454 request = Object.assign({}, response.request); 455 processDirectives(); 456 processServerResponse(response); 457 }).catch(function (err) { 458 console.error(err); 459 processServerResponse(err.response); 460 }); 461 } 462 463 function getUpdateRequestCookie () { 464 var cookie = '; ' + (document.cookie || '') + '; '; 465 var start = cookie.indexOf(UPDATE_REQUEST_COOKIE_LHS); 466 if (start < 0) { 467 return ''; 468 } 469 470 start += UPDATE_REQUEST_COOKIE_LHS.length; 471 return cookie.substring(start, cookie.indexOf('; ', start)); 472 } 473 474 function pollCookies () { 475 var value = getUpdateRequestCookie(); 476 if (value && value !== lastUpdateRequestCookie) { 477 lastUpdateRequestCookie = value; 478 getRequest(); 479 } 480 } 481 482 // Kick off XHR to get initial Apple Pay request 483 getRequest(); 484 485 // Poll for cookie to update Apple Pay request 486 lastUpdateRequestCookie = getUpdateRequestCookie(); 487 setInterval(pollCookies, 200); 488 })(window.dw.applepay, window.location, window.ApplePaySession); 489}
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.