Cart & Checkout scripts for 2.0?

We recently upgraded from FC 0.7.2 to FC 2.0. We previously had several scripts we were using with cart and checkout processes that worked in version 0.7.2, but no longer work in version 2.0. I have read some of the relevant 2.0 documentation on this, but have been unsuccessful in my attempts to revise the scripts for 2.0 and make them functional again. I would appreciate some guidance or code snippets that I can use as a starting point for the functionality we want to achieve. Here is what we need:

1. Force remove special items from the cart under certain conditions
We have a couple special items that we have set to go directly to checkout with cart=checkout and empty=true, so that they can only be purchased separately from other items. We previously had a script that fired whenever the cart was loaded, in the cases where a customer attempted to go back from checkout via "cancel and continue shopping" and add other items to their cart. It would loop through to check for the special item, and when finding it would first deliver a browser alert message with explanation, and then remove these special item. The special item can be identified by either its FC category, or by its product code.

2. "Future shipping only" for future subscriptions in flat rate shipping script
We previously used "FC.checkout.config.futureShippingOnly = true;" in our flat rate shipping script to ensure shipping charges on subscriptions in the future were not charged at signup. Does this parameter still work as it previously did, or is there a new one in 2.0?

3. Conditional checking for Subscription Cancellation transactions
We also used to load an exit survey at subscription cancellation by conditionally checking "FC.checkout.config.isSubscriptionCancel". Does this parameter still work as it previously did?

4. Targeting specific products in the checkout for presentation adjustments
I used to use the following formats for targeting the presence of products in the cart/checkout to do specific things:

for (var p in fc_json.products) {
if (fc_json.products[p].category == "EXAMPLE") { ...customizations... }
if (fc_json.products[p].code == "SOMTHING") { ...customizations...}
etc...
}


5. Special validation via AJAX to confirm if customer is "first time customer" and eligible for special promotion.
We have a promotional product that is only intended for first time customers to buy, and we had set up an advanced script with AJAX to validate against both email address and customer names. It could also be purchased by returning customers as a gift for someone else, and we validated for that too. I am copying the code for that script below, and would like to know of any adjustments I need to make for it to function correctly in FC 2.0 :


var originalValidateAndSubmit = FC.checkout.validateAndSubmit;

function sValidate1() {

var c_email = jQuery("#customer_email").val();
var cValid;

if ( window.XDomainRequest ) {

var data = "email=" + c_email;
var xdr = new XDomainRequest();
xdr.open("POST", "https://...path-to.../validation.php");
xdr.send(data);
xdr.onerror = function() {
alert('in error');
};
xdr.onload = function() {
checkVal(xdr.responseText);
}
} else {
jQuery.ajax({
type: "POST",
crossDomain : true,
cache: false,
data: {data:c_email},
async: true,
url: "https://...path-to.../validation.php",
beforeSend: function() {
jQuery("#fc_complete_order_button").hide();
jQuery("#safe-order").hide();
jQuery(".return-home").css("display", "none");
jQuery("#fc_complete_order_processing .fc_error").html("Processing Your Order. Please Wait.");
jQuery("#fc_complete_order_processing").css("display", "block");
},
success: function(validated){
checkVal(validated);
}
});
}


function checkVal(result) {

var cValid = result;
var c_fname = jQuery("#customer_first_name").val();
var c_lname = jQuery("#customer_last_name").val();
var s_fname = jQuery("#shipping_first_name").val();
var s_lname = jQuery("#shipping_last_name").val();
var c_fullname = c_fname.toUpperCase() + c_lname.toUpperCase();
var s_fullname = s_fname.toUpperCase() + s_lname.toUpperCase();

if (cValid == "true") {
FC.checkout.config.isValid = true;
} else if (cValid == "false" && document.getElementById('use_different_addresses').checked && c_fullname != s_fullname) {
alert ("success");
FC.checkout.config.isValid = true;
} else {
FC.checkout.config.isValid = false;
alert("Our records indicate that you have ordered from us before. Thanks so much for your support! This special offer is intended for first-time customers. Want to send this as a gift for someone new? You can purchase this item by providing the shipping address of a friend or loved one you would like to introduce to our product. Alternatively, please go back and remove this item from your cart if you would like to complete checkout with other items.");
jQuery('#fc_use_different_address span').html("Check to send this order to a friend or loved one ( with optional gift message ) ");
jQuery('#fc_address_shipping_container h2').html("Gift Shipping Address");
jQuery('#fc_use_different_address span').css("color", "red");
jQuery("#fc_complete_order_button").show();
jQuery("#safe-order").show();
jQuery("#fc_complete_order_processing").css("display", "none");
jQuery(".return-home").css("display", "block");
}
originalValidateAndSubmit();
}
}

for (var p in fc_json.products) {
if (fc_json.products[p].code == "SPECIAL") {
FC.checkout.override("validateAndSubmit", "sValidate1");
}
}
Comments
  • fc_jedfc_jed FoxyCart Team
    @Geoffrey

    We're glad you decided to make the switch. The post is rather lengthy, and we might not be able to get through all of them at once. We'll notify you when we have resolutions for your queries.
  • @fc_jed,

    Thanks, I appreciate it. I will await further info from you.
  • fc_adamfc_adam FoxyCart Team
    1. Force remove special items from the cart under certain conditions

    Could you paste the logic for that please?


    2. "Future shipping only" for future subscriptions in flat rate shipping script

    You can determine that by checking both FC.json.has_current_flat_rate_shipping and FC.json.has_future_flat_rate_shipping


    3. Conditional checking for Subscription Cancellation transactions

    FC.json.is_subscription_cancel


    4. Targeting specific products in the checkout for presentation adjustments

    Products are now in FC.json.items


    5. Special validation via AJAX to confirm if customer is "first time customer" and eligible for special promotion.

    This is actually cleaner in 2.0:
    FC.client.on("checkout-submit", function(params, next) {
    var hasSpecial = false;
    for (var p in FC.json.items) {
    if (FC.json.items[p].code == "SPECIAL") {
    var hasSpecial = true;
    }
    }
    if (hasSpecial) {
    var c_email = jQuery("#customer_email").val();
    var cValid;

    if ( window.XDomainRequest ) {
    var data = "email=" + c_email;
    var xdr = new XDomainRequest();
    xdr.open("POST", "https://...path-to.../validation.php";);
    xdr.send(data);
    xdr.onerror = function() {
    alert('in error');
    next(false);
    };
    xdr.onload = function() {
    checkVal(xdr.responseText, next);
    }
    } else {
    jQuery.ajax({
    type: "POST",
    crossDomain : true,
    cache: false,
    data: {data:c_email},
    async: true,
    url: "https://...path-to.../validation.php",
    beforeSend: function() {
    jQuery("#safe-order").hide();
    jQuery(".return-home").css("display", "none");
    },
    success: function(validated){
    checkVal(validated, next);
    }
    });
    }
    } else {
    next();
    }
    });

    function checkVal(result, next) {
    var cValid = result;
    var c_fname = jQuery("#customer_first_name").val();
    var c_lname = jQuery("#customer_last_name").val();
    var s_fname = jQuery("#shipping_first_name").val();
    var s_lname = jQuery("#shipping_last_name").val();
    var c_fullname = c_fname.toUpperCase() + c_lname.toUpperCase();
    var s_fullname = s_fname.toUpperCase() + s_lname.toUpperCase();

    if (cValid == "true") {
    next();
    } else if (cValid == "false" && document.getElementById('use_different_addresses').checked && c_fullname != s_fullname) {
    alert ("success");
    next();
    } else {
    alert("Our records indicate that you have ordered from us before. Thanks so much for your support! This special offer is intended for first-time customers. Want to send this as a gift for someone new? You can purchase this item by providing the shipping address of a friend or loved one you would like to introduce to our product. Alternatively, please go back and remove this item from your cart if you would like to complete checkout with other items.");
    jQuery('#fc_use_different_address span').html("Check to send this order to a friend or loved one ( with optional gift message ) ");
    jQuery('#fc_address_shipping_container h2').html("Gift Shipping Address");
    jQuery('#fc_use_different_address span').css("color", "red");
    jQuery("#safe-order").show();
    jQuery(".return-home").css("display", "block");
    next(false);
    }
    }</pre>


    You may also need to change some of the references to fields on the page, or access the information from the JSON which you can look at in `FC.json` in your browsers console.
  • GeoffreyGeoffrey Member
    edited August 2015
    Thanks Adam. Regarding #1, something like this:


    for (var i=0;i<fc_json.products.length;i++) {
    if (fc_json.products[i].category == "SUBSCRIPTION") {
    alert("To ensure that we ship your tea correctly, subscriptions must be purchased separately from other product by proceeding directly to checkout. The subscription will be removed from your cart while you browse. Thanks!");
    var domain = window.location.href.split("/");
    window.location.href="http://...custom store url.../cart?cart=update&id="+fc_json.products[i].id+"&quantity=0";
    }
    }


    I had tooled around with variations on the above with "FC.json.items" after reading the 2.0 docs, and just testing an alert message. I could only get it partially functional, where it would fire the alert message multiple times on cart load, and fail to remove the item. I just want it to fire the alert message once and successfully remove the item.
  • fc_adamfc_adam FoxyCart Team
    You'll want to run that logic within an event to prevent it from being run multiple times. Something like this:

    FC.client.on("ready.done", function() {
    for (var i=0; i<FC.json.items.length; i++) {
    if (FC.json.items[i].category == "SUBSCRIPTION") {
    alert('...');
    FC.client.request('https://'+FC.settings.storedomain+'/cart?cart=update&id="+FC.json.items[i].id+"&quantity=0').done(function(dataJSON) {
    FC.cart.recalculateCartTotals();
    FC.cart.render();
    });
    }
    }
    });
  • Great! Thanks Adam.
  • I was just reminded by some of my fulfillment staff that we are in need of one more special function. Is there a way that we can force add a single $0.00 value item to the order when certain conditions are met? It's a "free sample" item that most, but not all, of our orders are normally eligible for, and we want it to appear as an item in the cart/receipt for fulfillment purposes.

    It's not a bundle thing; just one additional item that is automatically added to an order under certain qualifying conditions. How might we best go about this? Thanks again for your help!
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Yep - take a look at http://wiki.foxycart.com/static/redirect/jsonp for the call to add a product to the cart silently. You'd pair that with conditions like shown previously to only add it under certain situations. If you'd like some assistance with the code if you can provide some more specifics we can help there.
  • @fc_adam,

    I'm attempting to get the script for #1 to work on our test site, copying the format you provided in the snippet. It is still not working correctly. I put it at the top of the config footer area like this:


    {% if context == 'cart' %}
    <script type="text/javascript" charset="utf-8">
    FC.client.on("ready.done", function() {
    for (var i=0; i<FC.json.items.length; i++) {
    if (FC.json.items[i].category == "SUBSCRIPTION") {
    alert('To ensure that we ship your tea correctly, subscriptions must be purchased separately from other product by proceeding directly to checkout. The subscription will be removed from your cart while you browse. Thanks!');
    FC.client.request('https://'+FC.settings.storedomain+'/cart?cart=update&id="+FC.json.items[i].id+"&quantity=0').done(function(dataJSON) {
    FC.cart.recalculateCartTotals();
    FC.cart.render();
    });
    }
    }
    });
    </script>
    {% endif %}


    But what I'm seeing that nothing happens when the side-cart is loaded. No alert, no product removal. If I click the link to view cart immediately after a page load, I can get the cart to load in full page view mode, and the alert will fire there, when the cart is in full page mode, but it still isn't removing the product. Am I missing something here?

    We're also using the custom flat rate shipping snippet in the congif footer, and that works fine, but I don't know if it presents any conflicts with the snippet above. Please advise.
  • fc_adamfc_adam FoxyCart Team
    edited August 2015
    @Geoffrey,

    Oh sorry - ready.done doesn't run on sidecart initialisation. The code will need to be slightly different. Try this:
    {% if context == 'cart' %}
    <script type="text/javascript" charset="utf-8">
    var subCheck = function() {
    for (var i=0; i<FC.json.items.length; i++) {
    if (FC.json.items[i].category == "SUBSCRIPTION") {
    alert('To ensure that we ship your tea correctly, subscriptions must be purchased separately from other product by proceeding directly to checkout. The subscription will be removed from your cart while you browse. Thanks!');
    FC.client.request('https://'+FC.settings.storedomain+'/cart?cart=update&id='+FC.json.items[i].id+'&quantity=0').done(function(dataJSON) {
    FC.cart.recalculateCartTotals();
    FC.cart.render();
    });
    }
    }
    };
    FC.client.on("ready.done", subCheck);
    {% if cart_is_fullpage %}subCheck();{% endif %}
    </script>
    {% endif %}
  • @fc_adam,

    Just tried your last suggestion, and still no dice. This code also does nothing on sidecart initialization. When I load the full page cart with this, it successfully removes the item, but fires the alert twice before doing so. I'm assuming it's because the full page cart is running "subCheck();" both in "{% if context == 'cart' %}" and "{% if cart_is_fullpage %}". When I remove the "cart_is_fullpage" conditional, the alert fires once as intended on the full page cart, and removes the item.

    Really need to this work on sidecart though, since 99% of users will interact with sidecart. The "ready.done" event does not seem to be working at all on sidecart. I tried some of the other available events as well, like "render.done", but nothing seemed to work in the intended manner.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    I'm sorry - that was my fault. Change this line:

    {% if cart_is_fullpage %}subCheck();{% endif %}

    To be this:

    {% if not cart_is_fullpage %}subCheck();{% endif %}
  • GeoffreyGeoffrey Member
    edited September 2015
    @fc_adam,

    OK, so that finally worked. Thanks for bearing with me on this Adam. A couple things about this:

    1. Having read the 2.0 documentation for "The Cart" and "foxycart.js", I never would have been able to guess that running the function inside a "{% if not cart_is_fullpage %}" condition would be the only way to target sidecart initialization. Is there any reason that you don't have a more straightforward way of targeting the sidecart load event? In any case, please update your docs so that this is clearer in the future. I lost a lot of hours trying to figure this out.

    2. Even when running the function inside "{% if not cart_is_fullpage %}", the alert message was still firing twice. At first I thought it was my own error, and thought briefly I had fixed it, but it turned out to not be the case. I had to resort to a crude DOM injection to ensure that the alert would only fire once:


    if (jQuery('#errorchecker').length === 0) {
    jQuery('body').append('<span id="errorchecker"></span>');
    alert('To ensure that we ship your tea correctly, subscriptions must be purchased separately from other product by proceeding directly to checkout. The subscription will be removed from your cart while you browse. Thanks!');
    }


    I haven't been able to figure out why the code your provided makes the alert fire twice, but if there's a way to adjust it without having to use my crude fix, please let me know. Thanks again for all your help.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    I'm really sorry for the confusion you've experienced there. We'll try to improve our documentation around targeting sidecart from the header/footer configuration javascript. I'll also think about the double-call issue you're experiencing.
  • @fc_adam,

    There's one more I just discovered I'm having trouble with. How to correctly target item options in 2.0?

    I see the following referenced in the docs:

    items[].options[]
    items[].options[].class
    items[].options[].name
    items[].options[].value

    What used to do in 0.7.2 was this:

    fc_json.products[i].options["optionname"]

    from which I could check for the presence of the named option, and check the named option's value like this:

    var myvalue = new String(fc_json.products[i].options["optionname"]);
    if (myvalue == "something") { ... do stuff ... }


    I have tried many different variations with the 2.0 formatting, but none of them work. I want to target an item option like this:

    for (var i in FC.json.items) {
    if (FC.json.items[i].options["note"]) {
    jQuery('.fc-cart__main__content').append('<span id="cart-notice2">NOTE – Your cart contains 1 or more pre-order items: Please refer to the estimated ship date stated on pre-order product pages.</span>');
    }
    }


    I have also tried the following:

    if (FC.json.items[i].options[].name == "note") {}
    if (FC.json.items[i].options[i].name == "note") {}
    if (FC.json.items[i].options[i].value == "PRE-ORDER") {}
    if (FC.json.items[i].options["note"] == "PRE-ORDER") {}
    if (FC.json.items[i].options[i].note == "PRE-ORDER") {}
    etc...

    None of these work. I wish the docs were more clear on the specifics of how 2.0 formatting is different from previous versions. Any clarification you can provide me would be much appreciated. Thanks again for all your help.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Sorry for the confusion there. The options in 2.0 don't have a key of the option name like it did previously. We have a ticket to look at bringing that back in for the next version though.

    For now, you'll need to loop through the options and check the name within the loop like this:
    for (var o = 0; o < FC.json.items[i].options.length; o++) {
    if (FC.json.items[i].options[o].name == "Note") {
    //...
    }
    }
  • @fc_adam,

    I am now working on the scrip to silently add an item via jsonp request, which we discussed above on Aug 26th. Here is what I have so far:


    {% if is context == 'checkout' %}

    ... script conditions ...

    FC.client.request('https://'+FC.settings.storedomain+'/cart?name=Free+Sample+5g&code=1fsample&price=0&chinese_name=免费的样品&quantity_max=1').done(function(dataJSON) {
    FC.cart.recalculateCartTotals();
    FC.cart.render();
    });

    ... script conditions ...

    {% endif %}


    And I have a script exclusively in the context 'cart' that will remove this item if a customer leaves the checkout and goes back to their cart. I am adding this item at checkout so that it only gets added once per order, and removing it in the cart context so that it doesn't throw repeated error messages for attempts to exceed quantity 1, along with ensuring that it always remains the last order item at checkout even if a customer adds more items after leaving checkout.

    This is all working correctly, except for one critical thing... I can only get it to work with HMAC cart validation disabled, which is not acceptable. I am not sure how to proceed since I have this set up to add the item at checkout. The docs on HMAC validation don't appear to explain how to work with this scenario. Our site uses the integrated HMAC validation provided by FoxyShop, and I have a pretty clear understanding of how to set HMAC validation for an item or item variable that is being being added from our site. But I have no idea how I would go about correctly setting HMAC validation for an item added at checkout. Please advise. Thanks!
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Good question. The answer there is you just need to manually encrypt that link and embed it directly into your checkout javascript. You can do that from the "Sample Code" section of your store's FoxyCart administration. Replace the existing link that is contained in the example code text area with your link from above, and hit the encode button. Then grab the resulting encrypted URL and use that in your javascript.
  • @fc_adam,

    Great! That was easy enough. Thanks for pointing me in the right direction.
  • @fc_adam
    2. "Future shipping only" for future subscriptions in flat rate shipping script

    You can determine that by checking both FC.json.has_current_flat_rate_shipping and FC.json.has_future_flat_rate_shipping
    Can you elaborate what you meant by this, Adam? (see my original #2 question from first post). I'm not sure how to make this work, since it is very different from the previous command of "FC.checkout.config.futureShippingOnly = true;", which was a straightforward conditional rule. I just want to make all instances of shipping charges on future subscriptions to be charged in the future, and not at the time of sign up. Why and how should I be checking for both "has_current_flat_rate_shipping" and "has_future_flat_rate_shipping". Please advise. Thanks!
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Thanks for asking - with what you said there I think I may have misunderstood your request originally. Could you expand on what you were previously doing when setting futureShippingOnly to true?
  • @fc_adam,

    This is what we were doing previously:

    if (only_subs == true && country_code != "US") {
    FC.checkout.config.futureShippingOnly = true;
    addShippingOption(1, 8.50, 'USPS ', '1st Class International Shipping – ');
    }


    Basically, under specific conditions indicate to the checkout that the shipping cost should be applied to the future subscription charges only, and not the current sign up transaction. How can I do the same thing in version 2.0?
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Thanks for clarifying. Unfortunately it's not possible to assign custom shipping rates to subscriptions in 2.0 using the snippets. The shipping is recalculated server-side on subscription renewal based on how the categories are configured, so it can't take into account how the javascript is configured.

    That said, we do have a new native custom shipping endpoint feature coming soon which will allow you to add custom shipping rates natively to your store - and mean that you'll be able to provide custom rates to subscriptions as well. We don't have an exact timeframe for when it will launch, as it's relying on a systems change before we can roll it out, but it is ready to launch as soon as that change is completed.
  • @fc_adam,

    Hm. I'm not sure I understand. The situation we're working with is as follows:

    Subscription Product at $30/mo, with:
    1. Free Shipping to US shipping address
    2. Flat-rate shipping charge of $8 for non-US shipping address

    Subscription is processed monthly on the 15th, regardless of date of sign up. So when a customer subscribes with a non-US shipping address on any day but the 15th, they should be assessed $0.00 charges today and $38 charged on the 15th. Instead what is happening is the customer is charged $8.00 at sign up, and also charged $38 on the 15th, which puts us in the position of having to manually refund the $8.00 sign up charge for those brave few who aren't confused and discouraged by the fact that they're being charged $8.00 for shipping up front, when we have communicated that they need only pay a shipping charge at the time the subscription is processed on the 15th.

    Are you telling me there is currently no way to negate the $8.00 shipping charge at sign up? This was very easy to do in previous versions, as you know. Why was this capability left out? It seems to me a rather significant omission. If you know of anything we can do to resolve this issue in the meantime (before the long awaited shipping control overhaul is rolled out), I would appreciate the assistance. Thanks!

  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    With changes made in 2.0 it's unfortunately not possible to apply custom shipping rates to subscriptions through a javascript snippet anymore. On renewal, the subscription's shipping charges will be applied as dictated by the category.

    The new feature I mentioned will replace the flat rates modification snippet and allow you to set up an endpoint on your own server where you can dictate the shipping charges. The ops change it is waiting on is being prepped for release, but obviously we're running through solid testing to ensure it does what it should.

    In the interim, the best approach would be to make use of our native category based shipping settings. For what you're describing, that would mean you'd need to allow customers to add the product based on their shipping country - and set the category accordingly
  • @fc_adam,

    Thanks for the clarification. So in terms of getting the the customer's shipping country prior to cart add, I'm assuming we would have to explicitly ask for that in the product form and change the category accordingly based on the answer given... Or would there be any other way to get that information prior to checkout? We have this product going directly to checkout.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Yeah it would be best served in your add to cart form if that's possible. It could be a radio input with two options like this:
    <input type="radio" name="category" value="DEFAULT" /> Shipped within the US
    <input type="radio" name="category" value="intl" /> Shipped outside the US
    When using our native shipping set up, it should correctly assign shipping to be future only if there is only a future-dated subscription in the cart that the shipping applies to.
  • @fc_adam,

    Great. Thank you for your help!
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    ​I wanted to quickly follow-up with you again about the custom shipping endpoint functionality we previously discussed.

    It's taken a little longer than we anticipated, but we just launched the custom endpoint feature today. You can see an overview of it, with links to some helper scripts in PHP and Node.js on our wiki at https://wiki.foxycart.com/v/2.0/shipping#custom_shipping_endpoint.

    To reiterate, this feature allows you to specify a custom endpoint for shipping rates. The cart and checkout will send off a request to that endpoint with details about the cart and customer included. On your endpoint then you can generate your own custom rates or make requests on to third-party providers before returning them to display to the customer.
Sign In or Register to comment.