1/* 2 * async cart processing code 3 */ 4 5// fix older IE barfing on console.log code 6if (typeof console == "undefined" || typeof console.log == "undefined") var console = { log: function() {} }; 7 8function Cart(use_stripe) { 9 // properties 10 this.cart = {}; 11 this.cart_message = ''; 12 this.stripe_finished = false; 13 14 // detect coming from bfcache and re-get cart if on iOS 15 var mios = /(ipad|ipod|iphone)/i.test( navigator.userAgent ); 16 var mandroid = /android/i.test( navigator.userAgent ); 17 if( mios ){ 18 window.onpageshow = function( event ) { 19 if( event.persisted ){ 20 cart.get_cart(); 21 } 22 }; 23 } 24 25 // methods 26 27 28 this.show_notification = function(text){ 29 this.cart_message = text; 30 this.show_cart(); 31 $('#show_cart_full').show(); 32 33 // commenting out cart notification message to only show cart display 34 //$("#cart_notification").show(); 35 //$('#cart_notification').html('' + text + '<div style="text-align:center"><button id="cart_notification_button">OK</button></div>'); 36 //$('#cart_notification_button').click(function(){$("#cart_notification").fadeOut('slow');}); 37 38 // commenting out automatic fade 39 //setTimeout(function(){$("#cart_notification").fadeOut('slow');}, 1500); 40 }; 41 42 this.get_cart = function() { 43 var verify = ''; 44 var location = document.location.href; 45 if (location.match(/SecureCartCheckout/)) { 46 verify = '1'; 47 } 48 var params = new URLSearchParams({ 49 verify: verify, 50 time_stamp: new Date().getTime() 51 }); 52 AwsWafIntegration.fetch('/SecureCartList.json?' + params.toString(), { 53 credentials: 'same-origin' 54 }) 55 .then(function(response) { return response.json(); }) 56 .then(function(data) { 57 cart.cart = data; 58 cart.show_cart(); 59 var location = document.location.href;
60 if (!location.match(/SecureCartCheckoutResults/) && location.match(/SecureCartCheckout/) ) { 61 // Stripe enforces a minimum charge, so a cart landing between 62 // $0.01 and $0.50 cannot be paid for and is held at the cart. 63 // A cart a coupon discounted all the way to zero is a different 64 // case: there is nothing to charge at all, and checkout settles 65 // it without contacting Stripe (see Checkout::Submit's 66 // dispatch_payment). Note prices.total arrives as null rather 67 // than "0.00" when it is zero â the shared price formatter 68 // drops zeros â so compare the parsed number, not the raw value. 69 var cart_total = parseFloat(data.prices.total) || 0; 70 var comped = (parseFloat(data.prices.discount) || 0) > 0 71 && cart_total === 0; 72 if(cart_total > 0.5 || comped){ 73 cart.show_checkout(); 74 }else if(cart_total > 0){ 75 // A cart with real value but under the payment processor's 76 // minimum cannot be charged. Explain it here, in the 77 // checkout page's own form slot, rather than bouncing to 78 // the cart view â that redirect is silent, and the cart 79 // view's message area lives inside #cartmodal, which stays 80 // hidden on that page, so the shopper would learn nothing. 81 $("#checkout_form_target").html( 82 '<div class="alert alert-warning" id="cart_minimum_notice">' 83 + '<strong>This order is under the $0.50 minimum we can charge.</strong> ' 84 + 'Your total comes to $' + cart_total.toFixed(2) + '. ' 85 + 'Please <a href="/SecureCartView">add another item to your cart</a>, ' 86 + 'or contact the store to complete this order.' 87 + '</div>' 88 ); 89 }else{ 90 // Nothing of value in the cart at all â back to the cart. 91 window.location.replace("/SecureCartView"); 92 } 93 } 94 }) 95 .catch(function(error) { 96 cart.show_notification("<p>Could not retreive cart, please try again!</p>"); 97 console.log("cart dataload error - " + error); 98 }); 99 }; 100 101 this.update_cart = function(type, id, qty) { 102 // If iPad|iPhone|iPod 103 var base_url = '/'; 104 if( mios || mandroid ){ 105 window.location.assign(base_url+"SecureCartAdd2?type="+type+"&id="+id 106 +"&qty="+qty); 107 }else{ 108 $('html, body').animate({scrollTop:0}, 'slow'); 109 $.ajax({ 110 url: base_url+'SecureCartAdd.json', 111 type: 'POST', 112 //dataType: 'jsonp', 113 data: { 114 'type': type, 115 'id' : id, 116 'qty' : qty 117 }, 118 success: function(data) { 119 var results = data; 120 if (results.errors) { 121 var error_msg = "<p>Errors in updating cart:</p><ul><li>"; 122 error_msg += results.errors.join("</li>\n<li>"); 123 error_msg += "</li></ul>"; 124 cart.show_notification(error_msg); 125 } else { 126 cart.show_notification("<p>"+results.message+"</p>"); 127 cart.get_cart(); 128 } 129 130 var cart_modal = $("#cartmodal"); 131 if(cart_modal){ 132 var fancybox = $(".fancybox-close"); 133 if(fancybox){ 134 fancybox.click() 135 } 136 $("#cartlink").click(); 137 setTimeout(function(){$("#cartviewCheckoutButton").focus(); console.log('focused')}, 0); 138 } 139 }, 140 error: function(jqXHR, textStatus, errorThrown) { 141 console.log({jqXHR: jqXHR, ts: textStatus, et: errorThrown}); 142 cart.show_notification("<p>Could not add to cart, please try again!</p>"); 143 } 144 }); 145 } 146 }; 147 148 this.delete_from_cart = function(type, id) { 149 $.ajax({ 150 url: '/SecureCartAdd.json', 151 type: 'POST', 152 //dataType: 'jsonp', 153 data: { 154 'type': type, 155 'id' : id, 156 'delete' : 1 157 }, 158 success: function(data) { 159 var results = data; 160 if (results.errors) { 161 var error_msg = "<p>Errors in removing item from cart:</p><ul><li>"; 162 error_msg += results.errors.join("</li>\n<li>"); 163 error_msg += "</li></ul>"; 164 cart.show_notification(error_msg); 165 } else { 166 cart.show_notification("<p>"+results.message+"</p>"); 167 cart.get_cart(); 168 } 169 }, 170 error: function(jqXHR, textStatus, errorThrown) { 171 cart.show_notification("<p>Could not remove from cart, please try again!</p>"); 172 } 173 }); 174 }; 175 176 this.clear_cart = function() { 177 $.ajax({ 178 url: '/SecureCartAdd.json', 179 type: 'POST', 180 //dataType: 'jsonp', 181 data: { 182 'clear': 1 183 }, 184 success: function(data) { 185 var results = data; 186 if (results.errors) { 187 var error_msg = "<p>Errors in adding to cart:</p><ul><li>"; 188 error_msg += results.errors.join("</li>\n<li>"); 189 error_msg += "</li></ul>"; 190 cart.show_notification(error_msg); 191 } else { 192 cart.show_notification("<p>"+results.message+"</p>"); 193 cart.get_cart(); 194 } 195 }, 196 error: function(jqXHR, textStatus, errorThrown) { 197 cart.show_notification("<p>Could not clear cart, please try again!</p>"); 198 } 199 }); 200 }; 201 202 this.show_cart_summary = function() { 203 var tmpl = "<span id='cart_summary'> <i class='bi bi-cart2'></i></span><span id='cart_summary_count'>{{count}}</span>"; 204 var template = Handlebars.compile(tmpl); 205 var price = 0; 206 var count = 0; 207 if (this.cart) { 208 if (this.cart.prices && this.cart.prices.total) { 209 price = this.cart.prices.total; 210 } 211 if (this.cart.cart_data) { 212 count = this.cart.cart_data.map(item => Number(item.cart_qty)).reduce( (prev, next) => prev + next, 0); 213 } 214 } 215 var data = { 216 count: count, 217 price: Number(price).toFixed(2) 218 }; 219 $("#cartplace").html(template(data)); 220 }; 221 222 this.show_cart_full = function() { 223 //console.log(this.cart_message); 224 var tmpl = $("#cartview_template").html(); 225 var template = Handlebars.compile(tmpl); 226 var data = { 227 cart_data: this.cart.cart_data, 228 taxes: (this.cart.prices && this.cart.prices.taxes) ? Number(this.cart.prices.taxes).toFixed(2) : 0, 229 fees: (this.cart.prices && this.cart.prices.fees) ? Number(this.cart.prices.fees).toFixed(2) : 0, 230 shipping: (this.cart.prices && this.cart.prices.shipping) ? Number(this.cart.prices.shipping).toFixed(2) : 0, 231 total: (this.cart.prices && this.cart.prices.total) ? Number(this.cart.prices.total).toFixed(2) : 0, 232 message: this.cart_message ? this.cart_message : '' 233 } 234 var text = template(data); 235 //console.log(text); 236 $("#show_cart_full").html(text); 237 $("#show_cart_full").css('position', 'relative').css('z-index', '10000'); 238 }; 239 240 this.show_cart = function() { 241 this.show_cart_full(); 242 this.show_cart_summary(); 243 $("#cartviewCheckoutButton").focus(); 244 }; 245 246 this.toggle_ccform = function() { 247 var payment_form = $('input:radio[name=payment_option]:checked').val(); 248 if (payment_form == "creditcard") { 249 var cctmpl = $("#cc_form_template").html(); 250 $("#paymentplace").html(cctmpl); 251 } else { 252 $("#paymentplace").html(""); 253 } 254 }; 255 256 this.toggle_checking_out = function(checking_out){ 257 cart.submitting_checkout = checking_out; 258 $("#checkout_submit").attr("disabled", checking_out); 259 //console.log("Checking out? "+checking_out); 260 //console.log("Button disabled? "+$("#checkout_submit").attr("disabled")); 261 }; 262 263 this.init_stripe_widget = function() { 264 $('#error_display').attr('style','display:none;'); 265 $('#error_display').html(''); 266 var form = document.getElementById('stripe_payment_form'); 267 if (!form) return; 268 $(form).show(); 269 270 // Build our Stripe instance from the per-site publishable key. 271 // Sourced from the #tp-stripe-pk hidden input inside the Stripe 272 // payment form (which is itself inside the payment_options_template 273 // script-clone block â a hidden input is safe there; a nested 274 // <script> would close the outer template prematurely, leaking 275 // the form to live page-render). 276 var pk = $('#tp-stripe-pk').val(); 277 if (!pk || typeof Stripe === 'undefined') { 278 $('#checkout_message_display').attr('style','display:block;'); 279 $('#checkout_message_display').html(
280 "Payment configuration error â please refresh or contact support." 281 ); 282 return; 283 } 284 if (!cart.stripe) { 285 // Connect-aware: pass stripeAccount so client-side Stripe.js 286 // routes through the connected account the server creates PIs 287 // on. Without this, confirmCardPayment errors with 288 // "No such payment_intent" because Stripe.js looks under the 289 // platform account by default but the PI lives on acct_X. 290 var acct = $('#tp-stripe-acct').val(); 291 cart.stripe = acct ? Stripe(pk, { stripeAccount: acct }) : Stripe(pk); 292 } 293 294 // Rollout-gated path: async_pi (server creates PI, client confirms) 295 // vs legacy (client createToken, server creates Charge). Branch sets 296 // both the mount target and the do_checkout submit choreography. 297 cart.payment_mode = ($('#tp-payment-mode').val() === 'legacy') ? 'legacy' : 'async_pi'; 298 var mount_target = (cart.payment_mode === 'legacy') ? '#card-element' : '#payment-element'; 299 300 try { 301 // CardElement either way. CardElement doesn't need to know 302 // about the PI to mount â clientSecret (PI path) or a tok_xxx 303 // (legacy path) is only produced at submit-time. Single-row 304 // Stripe-rendered card UI, zero extra Stripe.js calls before 305 // submit, in both modes. 306 cart.payment_elements = cart.stripe.elements(); 307 cart.payment_element = cart.payment_elements.create('card', { 308 hidePostalCode: true, // we collect billing zip from our own form 309 }); 310 cart.payment_element.mount(mount_target); 311 } catch (e) { 312 console.error('CardElement mount failed:', e); 313 $('#checkout_message_display').attr('style','display:block;'); 314 $('#checkout_message_display').html( 315 'Payment widget failed to load: ' + (e && e.message) + ' â please refresh.' 316 ); 317 return; 318 } 319 320 // Single-click submit: validate -> create PI -> confirm. 321 form.addEventListener('submit', function(event){ 322 event.preventDefault(); 323 if (cart.submitting_checkout) { 324 console.log('returning early from submit, preventing duplicate clicks'); 325 return; 326 } 327 cart.do_checkout(); 328 }); 329 }; 330 331 this.get_updated_cart_data = function(has_shipping_selected) { 332 var shipping_option_id = $('input[name="shipping_option_id"]:checked').val(); 333 var shipping_is_billing = $('input[name="billing_is_shipping"]:checked').val(); 334 var state = shipping_is_billing? $('select[name="billing_state"]').val(): 335 $('select[name="shipping_state"]').val(); 336 var city = shipping_is_billing? $('input[name="billing_city"]').val(): 337 $('input[name="shipping_city"]').val(); 338 // zip + country let live weight-based options quote a real rate; 339 // ignored entirely by flat-rate sites. 340 var zip = shipping_is_billing? $('input[name="billing_zip"]').val(): 341 $('input[name="shipping_zip"]').val(); 342 var country = shipping_is_billing? $('select[name="billing_country"] option:selected').val(): 343 $('select[name="shipping_country"] option:selected').val(); 344 var shipping_address_id = $('input[name="shipping_address_id"]:checked').val(); 345 var d = { 346 'ig': 1, 347 'state': state, 348 'city': city, 349 'zip': zip, 350 'country': country, 351 'address_id': shipping_address_id, 352 'shipping_option_id': shipping_option_id 353 }; 354 355 if(cart.cart.giftcard_amount && cart.cart.giftcard_code){ 356 d["giftcard_amount"] = cart.cart.giftcard_amount; 357 d["giftcard_code"] = cart.cart.giftcard_code; 358 } 359 360 $.ajax({ 361 url: '/SecureCartTotals.json', 362 dataType: 'jsonp', 363 data: d, 364 success: function(data) { 365 cart.cart.prices = data.prices; 366 cart.cart.giftcard_covers_all = typeof(data.final_total) !== 'undefined' && data.final_total == "$0.00"; 367 // A coupon that discounts the cart to exactly zero leaves 368 // nothing to charge either â same shape as a gift card 369 // covering the whole total. prices.total arrives as null 370 // rather than "0.00" when it is zero (the shared price 371 // formatter drops zeros), so compare the parsed number. 372 cart.cart.discounted_to_zero = (parseFloat(data.prices.discount) || 0) > 0 373 && (parseFloat(data.prices.total) || 0) === 0; 374 if(has_shipping_selected){ 375 cart.init_totals_html(); 376 $("#payment_options").show(); 377 $(".subtotal").text(data.subtotal); 378 $(".adjusted_subtotal").text(data.adjusted_subtotal); 379 $(".shipping").text(data.shipping || "$0"); 380 $(".taxes").text(data.taxes || "$0"); 381 $(".total").text(data.total || "$0"); 382 if(data.prices.fees && data.prices.fees > 0){ 383 $(".fees_display").show(); 384 $(".fees").text(data.fees || "$0"); 385 } 386 if(data.final_total && data.giftcard_amount){ 387 $(".giftcard_totals").show(); 388 $(".giftcard_value").text(data.giftcard_amount); 389 $(".final_total").text(data.final_total); 390 } 391 cart.set_payment_html(cart.cart.giftcard_covers_all, cart.cart.discounted_to_zero); 392 if(!use_stripe){ 393 cart.toggle_ccform(); 394 } 395 } 396 397 }, 398 error: function(jqXHR, textStatus, errorThrown) { 399 console.log("Error in getting updated pricing: " + errorThrown); 400 } 401 }); 402 //if(has_shipping_selected){ 403 //this.toggle_ccform(); 404 //} 405 } 406 407 this.toggle_shipping = function() { 408 var target_checked = $('input[name="billing_is_shipping"]:checked').val(); 409 if (target_checked) { 410 $('.shipping_data_item').attr('disabled', 'disabled'); 411 $('.shipping_legend').css('color', '#cccccc'); 412 } else { 413 $('.shipping_data_item').removeAttr('disabled'); 414 $('.shipping_legend').css('color', '#000000'); 415 } 416 } 417 418 this.toggle_address_form = function() { 419 var address_checked = $('input[name="billing_address_id"]:checked').val(); 420 if (cart.cart && cart.cart.username && cart.cart.user_data && cart.cart.user_data.good_addresses) { 421 if (address_checked) { 422 $('input:radio[name=billing_address_id][disabled=false]').removeAttr('checked'); 423 $('input:radio[name=shipping_address_id][disabled=false]').removeAttr('checked'); 424 } else { 425 $('input:radio[name=billing_address_id]:first').attr('checked', true); 426 $('input:radio[name=shipping_address_id]:first').attr('checked', true); 427 } 428 } 429 } 430 431 this.render_cart_address_form = function() { 432 var tmpl = $("#checkout_address_template").html(); 433 if (tmpl) { 434 var template = Handlebars.compile(tmpl); 435 var data = { 436 username: cart.cart.username, 437 user_data: cart.cart.user_data, 438 has_physical: cart.cart.has_physical, 439 addresses: user.addresses 440 }; 441 $("#checkout_address_target").html(template(data)); 442 this.toggle_address_form(); 443 $("select[name$=_country]").each(function(){ 444 var name = $(this).attr("name").replace("country","state"); 445 var target = $("[name="+name+"]").parent(); 446 var us_vals = target.html(); 447 var other_vals = "<input name='"+name+"' type='text' value=''/>"; 448 $(this).change(function(){ 449 if($(this).val() == 'US') { 450 target.html(us_vals); 451 target.parent().find(".legend").html("State"); 452 }else{ 453 target.html(other_vals); 454 target.parent().find(".legend").html("State/Province/..."); 455 } 456 })}); 457 458 } 459 460 if(cart.cart.shipping_exempt){ 461 cart.get_updated_cart_data(1); 462 /* 463 var data = { 464 username: cart.cart.username, 465 show_international_digital_warning: cart.cart.show_international_digital_warning, 466 cart_data: cart.cart.cart_data, 467 prices: cart.cart.prices, 468 user_data: cart.cart.user_data, 469 has_digital: cart.cart.has_digital, 470 has_physical: cart.cart.has_physical, 471 google: cart.cart.google, 472 paypal: cart.cart.paypal, 473 has_giftcards: cart.cart.has_giftcards, 474 475 }; 476 */ 477 //var totals_template = Handlebars.compile($("#totals_template").html()); 478 //var totals_html = totals_template(data); 479 //$("#totals_target").html(totals_html); 480 //cart.set_payment_html(false); 481 //$("#totals").show(); 482 //$("#payment_options").show();
483 // TODO or validate? 484 //cart.do_checkout(); 485 } 486 }; 487 488 this.init_totals_html = function(){ 489 if(!this.totals_initialized){ 490 var totals_template = Handlebars.compile($("#totals_template").html()); 491 $("#totals_target").html(totals_template(cart.cart)); 492 $("#totals").show(); 493 this.totals_initialized = true; 494 } 495 }; 496 497 this.set_payment_html = function(giftcard_covers_all, discounted_to_zero){ 498 var payment_html; 499 // Nothing left to charge â either a gift card covers the whole total 500 // or a coupon discounted it to zero. Render the no-card panel instead 501 // of a card form the customer has nothing to pay with. 502 if(giftcard_covers_all || discounted_to_zero){ 503 var payment_template = Handlebars.compile($("#paying_all_with_giftcard").html()); 504 payment_html = payment_template({ giftcard_covers_all: giftcard_covers_all }); 505 $("#payment_target").html(payment_html); 506 }else{ 507 var payment_template = Handlebars.compile($("#payment_options_template").html()); 508 payment_html = payment_template({ 509 username: cart.cart.username, 510 show_international_digital_warning: cart.cart.show_international_digital_warning, 511 cart_data: cart.cart.cart_data, 512 prices: cart.cart.prices, 513 user_data: cart.cart.user_data, 514 has_digital: cart.cart.has_digital, 515 has_physical: cart.cart.has_physical, 516 google: cart.cart.google, 517 paypal: cart.cart.paypal, 518 has_giftcards: cart.cart.has_giftcards, 519 }); 520 $("#payment_target").html(payment_html); 521 if(use_stripe){ 522 cart.init_stripe_widget(); 523 } 524 } 525 $("#payment_options").show(); 526 }; 527 528 this.check_before_checkout = function(){ 529 var validator = $("#cart_form").validate(); 530 if(!validator.form()){ 531 var err0 = $(validator.errorList[0].element); 532 $('html, body').animate({scrollTop: err0.offset().top}, 533 300, function(){err0.focus();} ); 534 return; 535 }else{ 536 cart.do_checkout(); 537 } 538 }; 539 540 this.set_coupon_message = function(msg){ 541 $("#coupon_message").html(msg); 542 }; 543 544 this.try_coupon_code = function(evt) { 545 var code = $("#coupon_code").val(); 546 if(!code){ 547 cart.set_coupon_message("Please Enter A Coupon Code"); 548 return; 549 } 550 $.ajax({ 551 url: '/SecureCartAdd.json', 552 type: 'POST', 553 //dataType: 'jsonp', 554 data: { 555 'type': 'coupon', 556 'id' : code, 557 }, 558 success: function(data) { 559 var results = data; 560 if (results.errors) { 561 var error_msg = ""; 562 error_msg += results.errors.join("<br/>"); 563 cart.set_coupon_message(error_msg); 564 } else { 565 cart.set_coupon_message(results.message); 566 cart.get_cart(); 567 } 568 }, 569 error: function(jqXHR, textStatus, errorThrown) { 570 cart.set_coupon_message("Could not add coupon, please try again later!"); 571 } 572 }); 573 evt.preventDefault(); 574 evt.stopPropagation(); 575 }; 576 577 this.show_checkout = function() { 578 var tmpl = $("#checkout_form_template").html(); 579 if(!tmpl){ 580 return; 581 } 582 var template = Handlebars.compile(tmpl); 583 var data = { 584 username: cart.cart.username, 585 show_international_digital_warning: cart.cart.show_international_digital_warning, 586 cart_data: cart.cart.cart_data, 587 prices: cart.cart.prices, 588 user_data: cart.cart.user_data, 589 has_digital: cart.cart.has_digital?true:false, 590 has_physical: cart.cart.has_physical?true:false, 591 shipping_exempt: cart.cart.shipping_exempt?true:false, 592 google: cart.cart.google, 593 paypal: cart.cart.paypal 594 595 }; 596 //console.log(cart.cart.cart_data); 597 $("#checkout_form_target").html(template(data)); 598 $('input:radio[name=shipping_option_id][disabled=false]:first').attr('checked', true); 599 $("#cart_form").validate({ 600 submitHandler: function(form,event) { 601 if(event){ 602 event.preventDefault(); 603 } 604 cart.do_checkout(); 605 return false;
606 } 607 }); 608 user.loadAddressesForCheckout(); 609 this.render_cart_address_form(); 610 this.toggle_shipping(); 611 this.toggle_ccform(); 612 this.prepopulate_cc_name(); 613 $('input[name=email]').change(function(){cart.check_email()}); 614 $('input:radio[name=billing_address_id]').change(function(){cart.prepopulate_cc_name()}); 615 setTimeout(cart.get_updated_cart_data, 0); 616 } 617 618 this.prepopulate_cc_name = function() { 619 var billing_address_id = $('input:radio[name=billing_address_id]:checked').val(); 620 if (billing_address_id && cart.cart.user_data && cart.cart.user_data.address_list) { 621 var address; 622 for (count in cart.cart.user_data.address_list) { 623 var test_address = cart.cart.user_data.address_list[count]; 624 //console.log("testing billing address " + count + " against " + billing_address_id + " == " + test_address.id); 625 if (test_address.id == billing_address_id) { 626 //console.log("yay, found it"); 627 address = test_address; 628 //break; 629 } 630 } 631 if (address) { 632 $('input[name=cc_firstname]').val(address.first_name); 633 $('input[name=cc_lastname]').val(address.last_name); 634 635 } 636 } 637 } 638 639 this.check_email = function() { 640 var email = $('input[name=email]').val(); 641 if (user.user && user.user.fully_logged_in) { 642 // existing customer, let them pass 643 } else { 644 if (email) { 645 $.ajax({ 646 url: '/SecureCartUserCheck.json', 647 dataType: 'jsonp', 648 data: { 649 'email': email 650 }, 651 success: function(data) { 652 if (data.force_login) { 653 console.log("existing user, not logged in"); 654 var prepopulated_data = { 655 'username': email 656 }; 657 user.cartSplash(prepopulated_data); 658 659 } else if (data.incomplete_user) { 660 console.log("mailing list user"); 661 user.showPasswordRecovery(email); 662 663 } else { 664 // new customer, let them pass 665 } 666 667 }, 668 error: function(jqXHR, textStatus, errorThrown) { 669 console.log("Error in doing email check: " + errorThrown); 670 } 671 }); 672 } 673 } 674 return false; 675 } 676 677 this.get_addresses = function(){ 678 var billing_is_shipping = $('input[name=billing_is_shipping]:checked').val(); 679 var shipping_id = $('input:radio[name=shipping_address_id]:checked').val(); 680 var shipping_address_1 = $('input[name=shipping_address_1]').val(); 681 var shipping_address_2 = $('input[name=shipping_address_2]').val(); 682 var shipping_first_name = $('input[name=shipping_first_name]').val(); 683 var shipping_last_name = $('input[name=shipping_last_name]').val(); 684 var shipping_city = $('input[name=shipping_city]').val(); 685 var shipping_state = $('select[name=shipping_state] option:selected').val() 686 || $('input[name=shipping_state]').val(); 687 var shipping_zip = $('input[name=shipping_zip]').val(); 688 var shipping_phone = $('input[name=shipping_phone]').val(); 689 var shipping_country = $('select[name=shipping_country] option:selected').val(); 690 var billing_id = $('input:radio[name=billing_address_id]:checked').val(); 691 var billing_address_1 = $('input[name=billing_address_1]').val(); 692 var billing_address_2 = $('input[name=billing_address_2]').val(); 693 var billing_first_name = $('input[name=first_name]').val(); 694 var billing_last_name = $('input[name=last_name]').val(); 695 var billing_city = $('input[name=billing_city]').val(); 696 var billing_state = $('select[name=billing_state] option:selected').val() 697 || $('input[name=billing_state]').val(); 698 var billing_zip = $('input[name=billing_zip]').val(); 699 var billing_phone = $('input[name=billing_phone]').val(); 700 var billing_country = $('select[name=billing_country] option:selected').val(); 701 var errors = []; 702 var a = { 703 shipping: { 704 id: billing_is_shipping? billing_id : shipping_id, 705 address_1: billing_is_shipping? billing_address_1 : shipping_address_1, 706 address_2: billing_is_shipping? billing_address_2 : shipping_address_2, 707 first_name: billing_is_shipping? billing_first_name : shipping_first_name, 708 last_name: billing_is_shipping? billing_last_name : shipping_last_name, 709 city: billing_is_shipping? billing_city : shipping_city, 710 state: billing_is_shipping? billing_state : shipping_state, 711 zip: billing_is_shipping? billing_zip : shipping_zip, 712 phone: billing_is_shipping? billing_phone : shipping_phone, 713 country: billing_is_shipping? billing_country : shipping_country, 714 }, 715 billing: { 716 is_shipping: billing_is_shipping, 717 id: billing_id, 718 address_1: billing_address_1, 719 address_2: billing_address_2, 720 first_name: billing_first_name, 721 last_name: billing_last_name, 722 city: billing_city, 723 state: billing_state, 724 zip: billing_zip, 725 phone: billing_phone, 726 country: billing_country, 727 } 728 }; 729 730 731 if (! ( a.billing.id || (a.billing.address_1 && a.billing.first_name && a.billing.last_name && a.billing.city && a.billing.country && (a.billing.state || a.billing.country != 'US' ) && a.billing.phone && a.billing.zip) ) ) { 732 if ( a.billing.address_1 || a.billing.first_name || a.billing.last_name || a.billing.city || a.billing.state || a.billing.country || a.billing.phone || a.billing.zip) { 733 if (! a.billing.address_1) { 734 errors.push("Need to provide a billing address"); 735 } 736 if (! a.billing.first_name) { 737 errors.push("Need to provide a first name for billing"); 738 } 739 if (! a.billing.last_name) { 740 errors.push("Need to provide a last name for billing"); 741 } 742 if (! a.billing.city) { 743 errors.push("Need to provide a billing city"); 744 } 745 if (! a.billing.country) { 746 errors.push("Need to provide a billing country"); 747 } 748 if ((!a.billing.state || !a.billing.state.length) && a.billing.country == 'US') { 749 errors.push("Need to provide a billing state"); 750 } 751 if (! a.billing.phone) { 752 errors.push("Need to provide a billing phone number"); 753 } 754 if (! a.billing.zip) { 755 errors.push("Need to provide a billing zip"); 756 } 757 } else { 758 errors.push("Need to specify a billing address"); 759 } 760 } 761 762 var block_digital = true; 763 if( a.billing.country != "US" && cart.cart.has_digital){ 764 if(a.billing.id){ 765 var adds = cart.cart.user_data.address_list.concat(user.addresses); 766 if(adds){ 767 adds.forEach(function(el){ 768 if(el.id == a.billing.id && el.country == "US"){ 769 block_digital = false;
770 } 771 }); 772 } 773 } 774 }else{ 775 block_digital = false; 776 } 777 778 if(block_digital){ 779 errors.push("We’re sorry, we can only sell MP3 downloads to customers in the United States. We are not able to provide you with your downloads."); 780 } 781 782 if (! ( a.shipping.id || a.billing.is_shipping || (a.shipping.address_1 && a.shipping.first_name && a.shipping.last_name && a.shipping.city && a.shipping.country && (a.shipping.state || a.shipping.country != 'US') && a.shipping.phone && a.shipping.zip) ) ) { 783 if ( a.shipping.address_1 || a.shipping.first_name || a.shipping.last_name || a.shipping.city || a.shipping.state || a.shipping.country || a.shipping.phone || a.shipping.zip) { 784 if (! a.shipping.address_1) { 785 errors.push("Need to provide a shipping address"); 786 } 787 if (! a.shipping.first_name) { 788 errors.push("Need to provide a first name for shipping"); 789 } 790 if (! a.shipping.last_name) { 791 errors.push("Need to provide a last name for shipping"); 792 } 793 if (! a.shipping.city) { 794 errors.push("Need to provide a shipping city"); 795 } 796 if (! a.shipping.country) { 797 errors.push("Need to provide a shipping country"); 798 } 799 if (! a.shipping.state && a.shipping.country == 'US') { 800 errors.push("Need to provide a shipping state"); 801 } 802 if (! a.shipping.phone) { 803 errors.push("Need to provide a shipping phone number"); 804 } 805 if (! a.shipping.zip) { 806 errors.push("Need to provide a shipping zip"); 807 } 808 } else { 809 errors.push("Need to specify a shipping address"); 810 } 811 } 812 813 a.errors = errors; 814 return a; 815 } 816 817 this.get_shipping_options = function(){ 818 var validator = $("#cart_form").validate(); 819 if(!validator.form()){ 820 var err0 = $(validator.errorList[0].element); 821 $('html, body').animate({scrollTop: err0.offset().top}, 822 300, function(){err0.focus();} ); 823 return; 824 } 825 var addresses = this.get_addresses(); 826 var shipping_error = function(msg){ 827 $("#pre_shipping_messages").html(msg); 828 }; 829 if(addresses.errors.length){ 830 shipping_error("<br/>Errors: <br />" + addresses.errors.join("<br /></br>")); 831 }else{ 832 $.ajax({ 833 url: '/SecureCartShippingOptions.json', 834 type: 'GET', 835 dataType: 'json', 836 data: { 837 total: this.cart.prices.total, 838 shipping_address_id: addresses.shipping.id, 839 shipping_country: addresses.shipping.country, 840 // destination detail for live weight-based rating; 841 // flat-rate sites ignore these. 842 shipping_zip: addresses.shipping.zip, 843 shipping_state: addresses.shipping.state, 844 shipping_city: addresses.shipping.city, 845 }, 846 success: function(shipping_data, textStatus, jqXHR) { 847 if(shipping_data.errors.length){ 848 shipping_error("Errors: <br />" + shipping_data.errors.join("<br />")); 849 }else{ 850 /* 851 var payment_data = { 852 username: cart.cart.username, 853 show_international_digital_warning: cart.cart.show_international_digital_warning, 854 cart_data: cart.cart.cart_data, 855 prices: cart.cart.prices, 856 user_data: cart.cart.user_data, 857 has_digital: cart.cart.has_digital, 858 has_physical: cart.cart.has_physical, 859 has_giftcards: cart.cart.has_giftcards, 860 google: cart.cart.google, 861 paypal: cart.cart.paypal 862 863 };*/ 864 //var data = $.extend(shipping_data, payment_data); 865 //var shipping_html = shipping_template(data); 866 //var totals_template = Handlebars.compile($("#totals_template").html()); 867 //var totals_html = totals_template(data); 868 //var payment_template = Handlebars.compile($("#payment_options_template").html()); 869 //var payment_html = payment_template(data); 870 var shipping_template = Handlebars.compile($("#shipping_options_template").html()); 871 var shipping_html = shipping_template(shipping_data); 872 $("#shipping_target").html(shipping_html); 873 874 $("[name*=_country]").attr("disabled", true); 875 $("#billcheck").attr("disabled", true); 876 } 877 }, 878 error: function(jqXHR, textStatus, errorThrown) { 879 shipping_error("It seems we had problems connecting to the server to retrieve the available shipping options. Please try again."); 880 } 881 }); 882 } 883 }; 884 885 886 this.apply_giftcard = function(){ 887 cart.cart.giftcard_amount = $('#giftcard_redeem_amount').val(); 888 cart.cart.giftcard_code = $.trim($('#giftcard_code').val()); 889 cart.get_updated_cart_data(1); 890 cart.set_payment_html(cart.cart.giftcard_covers_all); 891 }; 892 893 this.check_giftcard = function(){ 894 var code = $.trim($('#giftcard_code').val()); 895 var status_message = function(giftcard_data){ 896 var color, message; 897 if(giftcard_data.success){ 898 color = "green"; 899 message = "You have $"+giftcard_data.balance+" left on your card."; 900 }else{ 901 color = "red"; 902 message = giftcard_data.message; 903 } 904 $('#giftcard_status_message').text(message); 905 $('#giftcard_status_message').attr('style', 'display:block; color:'+color); 906 }; 907 $.ajax({ 908 url: '/SecureCartCheckGiftcard.json', 909 type: 'GET', 910 dataType: 'json', 911 data: { 912 code: code 913 }, 914 success: function(giftcard_data, textStatus, jqXHR) { 915 status_message(giftcard_data); 916 // console.log(giftcard_data.debug); 917 if(giftcard_data.success){ 918 var max_redeem = Math.min(giftcard_data.balance, cart.cart.prices.total).toFixed(2); 919 var min_charge = 1; 920 var secondary_max = (cart.cart.prices.total - min_charge).toFixed(2); 921 922 var update_amount = function(){ 923 var num_val = Number($(this).val()); 924 var diff = cart.cart.prices.total - num_val; 925 if(num_val > Number(max_redeem)){ 926 $(this).val(max_redeem); 927 }else if( diff > 0 && diff < min_charge ){ 928 $(this).val(secondary_max); 929 $('#giftcard_status_message').html("Giftcard amount $"+num_val.toFixed(2)+" does not cover full order amount:<br/>
929 Need to charge at least $1 due to processing minimum.<br/>Changed giftcard amount to $"+secondary_max); 930 cart.apply_giftcard(); 931 }else if(num_val < 0.01){ 932 $(this).val(0.01); 933 } 934 }; 935 $('#giftcard_redeem_amount').val(max_redeem); 936 $('#giftcard_redeem_amount').change(update_amount).change(); 937 $('#giftcard_redeem_amount').attr('max', max_redeem); 938 $('#giftcard_redeem_input').show(); 939 940 } 941 }, 942 error: function(jqXHR, textStatus, errorThrown) { 943 console.log(textStatus); 944 console.log(errorThrown); 945 } 946 }); 947 }; 948 949 this.do_checkout = function() { 950 if(cart.submitting_checkout){ 951 console.log("returning early from checkout, preventing duplicate clicks"); 952 return; 953 } 954 cart.toggle_checking_out(true); 955 956 var email = $('form#cart_form input[name=email]').val(); 957 var password = $('input[name=password]').val(); 958 var password_confirm = $('input[name=password_confirm]').val(); 959 var subscribe = $('input:checkbox[name=subscribe]:checked').val(); 960 var po_number = $('input[name=po_number]').val(); 961 var comments = $('textarea[name=comments]').val(); 962 var mobile_phone = $('input[name=mobile_phone]').val(); 963 var shipping_option_id = $('input[name=shipping_option_id]:checked').val(); 964 var payment_option = $('input:hidden[name=payment_option]').val() || $('input:radio[name=payment_option]:checked').val(); 965 var cc_firstname = $('input[name=cc_firstname]').val(); 966 var cc_lastname = $('input[name=cc_lastname]').val(); 967 var cc_num = $('input[name=cc_num]').val(); 968 var cc_type = $('input:radio[name=cc_type]:checked').val(); 969 var cc_expmonth = $('select[name=cc_expmonth] option:selected').val(); 970 var cc_expyear = $('select[name=cc_expyear] option:selected').val(); 971 var cc_cvc = $('input[name=cc_cvc]').val(); 972 973 974 var errors = []; 975 var addresses = this.get_addresses(); 976 if(addresses.errors.length){ 977 errors.push(addresses.errors); 978 } 979 980 981 if (! shipping_option_id && cart.cart && cart.cart.has_physical) { 982 errors.push("Need to specify a shipping method"); 983 } 984 985 if (payment_option == "creditcard" && ! (cc_firstname && cc_lastname && cc_num && cc_type && cc_expmonth && cc_expyear && cc_cvc)) { 986 errors.push("Need to fill out all fields for credit card"); 987 } 988 989 // sorry, no logical XOR in Javascript. This kludge makes do. 990 if ((password && ! password_confirm) || (!password && password_confirm)) { 991 errors.push("You need to enter both a password and a password confirmation"); 992 } 993 994 if (password && password_confirm && password != password_confirm) { 995 errors.push("Passwords did not match"); 996 } 997 998 if (errors.length > 0) { 999 cart.toggle_checking_out(false); 1000 var errorstring = "Errors: <br />" + errors.join("<br />"); 1001 console.log("Showing errors: " + errorstring); 1002 $('#checkout_message_display').attr('style','display:block;'); 1003 $("#checkout_message_display").html(errorstring); 1004 $('#error_display').attr('style','display:block;'); 1005 $("#error_display").html(errorstring); 1006 } else if (!use_stripe && !cart.cart.has_physical && !cart.totals_initialized){ 1007 cart.toggle_checking_out(false); 1008 cart.get_updated_cart_data(true); 1009 //cart.init_totals_html(); 1010 //cart.set_payment_html(false); 1011 //return false; 1012 } else { 1013 $('#checkout_message_display').attr('style','display:block;'); 1014 $("#checkout_message_display").html("Submitting order - please wait…"); 1015 var d = { 1016 shipping_address_id: addresses.shipping.id, 1017 billing_address_id: addresses.billing.id, 1018 email: email, 1019 password: password, 1020 subscribe: subscribe, 1021 billing_is_shipping: addresses.billing.is_shipping, 1022 shipping_address_1: addresses.shipping.address_1, 1023 shipping_address_2: addresses.shipping.address_2, 1024 shipping_first_name: addresses.shipping.first_name, 1025 shipping_last_name: addresses.shipping.last_name, 1026 shipping_city: addresses.shipping.city, 1027 shipping_state: addresses.shipping.state, 1028 shipping_country: addresses.shipping.country, 1029 shipping_zip: addresses.shipping.zip, 1030 shipping_phone: addresses.shipping.phone, 1031 billing_address_1: addresses.billing.address_1, 1032 billing_address_2: addresses.billing.address_2, 1033 billing_first_name: addresses.billing.first_name, 1034 billing_last_name: addresses.billing.last_name, 1035 billing_city: addresses.billing.city, 1036 billing_state: addresses.billing.state, 1037 billing_country: addresses.billing.country, 1038 billing_zip: addresses.billing.zip, 1039 billing_phone: addresses.billing.phone, 1040 po_number: po_number, 1041 comments: comments, 1042 mobile_phone: mobile_phone, 1043 shipping_option_id: shipping_option_id, 1044 payment_option: payment_option, 1045 cc_firstname: cc_firstname, 1046 cc_lastname: cc_lastname, 1047 cc_num: cc_num, 1048 cc_type: cc_type, 1049 cc_expmonth: cc_expmonth, 1050 cc_expyear: cc_expyear, 1051 cc_cvc: cc_cvc 1052 }; 1053 1054 if(cart.cart.giftcard_amount && cart.cart.giftcard_code){ 1055 d["giftcard_amount"] = cart.cart.giftcard_amount; 1056 d["giftcard_code"] = cart.cart.giftcard_code; 1057 } 1058 1059 var postOrder = function() { 1060 $.ajax({ 1061 url: '/SecureCartCheckoutSubmit.json', 1062 // important to be POST because nginx doesn't retry requests w/ non-idempotent methods 1063 // want to avoid duplicate orders, even if request times out or errors initially 1064 type: 'POST', 1065 //dataType: 'jsonp', 1066 data: d, 1067 success: function(data, textStatus, jqXHR) {
1068 console.log("cart submit complete"); 1069 if (data.force_login) { 1070 cart.toggle_checking_out(false); 1071 console.log("user not logged in"); 1072 var prepopulated_data = { 1073 'half_logged_in': 1, 1074 'username': email 1075 }; 1076 user.cartSplash(prepopulated_data); 1077 1078 } else if (data.incomplete_user) { 1079 cart.toggle_checking_out(false); 1080 console.log("mailing list user"); 1081 user.showPasswordRecovery(email); 1082 1083 } else if (data.errors.length > 0) { 1084 cart.toggle_checking_out(false); 1085 var errorstring = "Errors from server: <br />" + data.errors.join("<br />"); 1086 if (data.force_cart_refresh){ 1087 // TODO reload cart, dropping unavailable items 1088 setTimeout(function(){location.reload();}, 5000); 1089 errorstring += "<span class='fa fa-spinner fa-spin'></span>"; 1090 } 1091 console.log("errors from server - " + errorstring); 1092 $('#checkout_message_display').attr('style','display:block;'); 1093 $("#checkout_message_display").html(errorstring); 1094 1095 } else if (data.status === 'pending_confirmation' 1096 && data.client_action 1097 && data.client_action.type === 'stripe_payment_intent') { 1098 // Backend created the PI; confirm it client-side with 1099 // CardElement + confirmCardPayment. 3DS challenges open 1100 // a Stripe-managed modal in-browser; on success we 1101 // navigate to the results page ourselves (CardElement 1102 // path doesn't auto-redirect like deferred-intent). 1103 var clientSecret = data.client_action.data.client_secret; 1104 var bn = (addresses.billing.first_name || '') + ' ' + 1105 (addresses.billing.last_name || ''); 1106 cart.stripe.confirmCardPayment(clientSecret, { 1107 payment_method: { 1108 card: cart.payment_element, 1109 billing_details: { 1110 name: bn.trim() || undefined, 1111 email: email || undefined, 1112 phone: addresses.billing.phone || undefined, 1113 address: { 1114 line1: addresses.billing.address_1 || undefined, 1115 line2: addresses.billing.address_2 || undefined, 1116 city: addresses.billing.city || undefined, 1117 state: addresses.billing.state || undefined, 1118 postal_code: addresses.billing.zip || undefined, 1119 country: addresses.billing.country || undefined, 1120 }, 1121 }, 1122 }, 1123 }).then(function(result) { 1124 if (result.error) { 1125 // Inline error display; submit button stays 1126 // enabled so the customer can retry. The PI is 1127 // still attached to the order â a retry will 1128 // create a fresh order/PI server-side, which 1129 // is fine: the orphaned PI auto-expires. 1130 cart.toggle_checking_out(false); 1131 $('#checkout_message_display').attr('style','display:block;'); 1132 $('#checkout_message_display').html(result.error.message || 'Payment failed.'); 1133 return; 1134 } 1135 // Stripe confirmed the PI. The webhook â Lambda 1136 // will flip payment_state asynchronously; results 1137 // page polls until it lands. 1138 window.location = '/SecureCartCheckoutRe
1138sults'; 1139 }); 1140 1141 } else if (data.redirect_url) { 1142 $('#checkout_message_display').attr('style','display:block;'); 1143 $("#checkout_message_display").html("Order submitted successfully! Please wait while we finish setting things up…"); 1144 window.location = data.redirect_url; 1145 1146 } else { 1147 cart.toggle_checking_out(false); 1148 $('#checkout_message_display').attr('style','display:block;'); 1149 $("#checkout_message_display").html("Hm, it seems like there was a problem with the order. Try submitting it again."); 1150 } 1151 }, 1152 error: function(jqXHR, textStatus, errorThrown) { 1153 cart.toggle_checking_out(false); 1154 console.log("cart submit error - " + errorThrown); 1155 $('#checkout_message_display').attr('style','display:block;'); 1156 $("#checkout_message_display").html("It seems we had problems connecting to the server to finish processing your order. Please try again."); 1157 } 1158 }); 1159 }; 1160 1161 // CardElement does its own inline validation as the customer 1162 // types; no pre-flight elements.submit() round-trip needed 1163 // (that's a deferred-intent-only handshake). The server-side 1164 // confirmCardPayment call will surface any remaining errors. 1165 // 1166 // Legacy Charge path: tokenize the card client-side first and 1167 // carry tok_xxx in the POST. The server's _stripe_payment 1168 // reads $q->param('stripe_token') and creates the Stripe::Charge 1169 // inline. PI path skips this â server creates the PI on submit 1170 // and client confirms with the returned client_secret instead. 1171 // Nothing to charge (gift card covers it, or a coupon discounted 1172 // the cart to zero) means there is no card to tokenize â and no 1173 // card element on the page to read one from. Tokenizing anyway 1174 // fails with "Your card number is incomplete." and the order is 1175 // never posted. The server settles a zero balance without 1176 // contacting Stripe (Checkout::Submit's dispatch_payment). 1177 var nothing_to_charge = cart.cart.giftcard_covers_all || cart.cart.discounted_to_zero; 1178 if (use_stripe && cart.payment_mode === 'legacy' && !nothing_to_charge) { 1179 cart.stripe.createToken(cart.payment_element).then(function(result) { 1180 if (result.error) { 1181 cart.toggle_checking_out(false); 1182 $('#checkout_message_display').attr('style','display:block;'); 1183 $('#checkout_message_display').html(result.error.message || 'Card details could not be tokenized.'); 1184 return; 1185 } 1186 d.stripe_token = result.token.id; 1187 postOrder(); 1188 }); 1189 } else { 1190 postOrder(); 1191 } 1192 1193 1194 } 1195 1196 // prevent click action from propagating 1197 return false; 1198 }; 1199 1200 return this; 1201};
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.