Subscriptions - validating to prevent repeat sign-ups?

GeoffreyGeoffrey Member
in Help edited March 2013
Hi guys. Is there any way I could go about a running a validation at checkout that searches our store's subscription records to see if a given customer has already signed up for a specific subscription in the past and prevents checkout if this is true?

We are running a free trial month promotion on a certain subscription product, and want to mitigate the potential for abuse as much as possible. Because of the way this subscription product is configured, duplicate sign ups by the same person are currently possible, and we want to prevent this to whatever degree we can so as to avoid having to police it all manually.

Any guidance or suggestions on accomplishing this would be deeply appreciated. Thanks for your help!

Geoffrey
Comments
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    So there are a few approaches you could take here.

    Firstly - if you have an SSL cert for your own website, you could send an ajax request off to your website over HTTPS on the checkout, and the endpoint you hit could run an API call based on the email address the customer has entered and check if they have purchased that subscription in the past and return true or false back to the checkout.

    Secondly - you could use our Single Sign-On [http://wiki.foxycart.com/static/redirect/sso] functionality to perform a validation right before they hit the checkout - but that would require you knowing what the email address is of the customer before they get to the checkout (although if you knew that, you could just not show the add to cart option to them too). That said though, you could set up the SSO endpoint to shoot the customer to a page on your website to enter an email address before getting to the checkout if they have that free trial subscription in their cart. That way you can get that email address before they hit the checkout and perform a check.

    Lastly, you could actually let them get the purchase - but using the XML datafeed - perform the check there and prevent any actions from taking place if its a duplicate purchase, and cancel their duplicated subscription.
  • @fc_adam,

    Great! Thanks so much for the suggestions, Adam. I think I would prefer the first option over the other two because it would be easiest for us to implement given our current setup and provide the customer with feedback via an alert at the checkout attempt. We could then direct them to sign up for a regular subscription if they wish to. I'll have to do some studying an tinkering to figure this one out, but I'm sure I'll be able to manage it in good time. I appreciate the quick reply!
  • @fc_adam,

    One additional question occurred to me about the first method. Would running a validation check to see if a customer has previously purchased a subscription with free trial interfere with checkout situations in which the given customer is simply updating their billing/shipping info or paying a past due amount on the subscription in question?
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    You could check to see if the checkout is for a updateinfo or subscription cancel first, and only run that check if it isn't one of those. You would use the flags I mentioned in a previous thread to do that.
  • @fc_adam,

    Hey Adam, so an additional question has come up related to this checkout validation idea I asked about a few weeks ago. I understand that the API call method to check a customer's order history via their email address will definitely work if they have created a customer account, but will this kind of validation check work if the customer has checked out as a guest (and not created an account)?

    My original question pertained to subscription offerings, which I realize will always require the customer to create an account. But now we're about to offer a special non-subscription (one-off) product offering that we would like to limit in two ways:

    1. make it available to new customers only
    2. make sure each customer can only purchase this product one time (prevent repeat purchases)

    Is there any solid way to implement these limitations via a checkout validation, without having to force everyone to use/create a customer account? Will the API call method be able to check records for customers who have checked out as a guest?

    And what about checking customer order history via billing or shipping address for the validation instead of (or in addition to) the email address?

    I recognize that there may be no foolproof method to successfully validate for the specified limitations in every case, but we're trying to at least mitigate the potential for abuse as much as possible. Any further suggestions or guidance you may have on this would be deeply appreciated.

    Let me know if any of this is unclear, and I'll do my best to clarify. Thanks so much for your help!


  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Customer accounts aren't created for guest checkouts, but you could filter the transaction list based on the customers email and loop through their transactions checking if they've purchased one before. Note though that this relies on you having the customers email address *before* they hit the checkout, so you'll need to set up your checkout process in such a way that they have to enter their email before they hit the checkout.

    You could have a page for example that has two options, either login to their existing account, or if checking out as a guest, enter their email address.
  • @fc_adam,

    Hmm, I think I may have misunderstood your explanation of the ajax/API call method the first time. I was under the impression that the checkout validation could be run when the customer attempts to complete the purchase by submitting the checkout form, and at that point return true or false back to the checkout.

    From the way you explained it, I didn't get that this validation would have to run before the customer hits the checkout page. You had mentioned pre-checkout validation as being part of the SSO method, but didn't indicate that for the ajax/API method.

    Can you clarify this for me? I'm a little confused. Thanks.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Sorry - I didn't read my earlier reply and forgot I'd mentioned the ajax call to a secure page on your end. That could definitely still work, I was just explaining the SSO approach.
  • Great! Thanks for the follow up. I will work on implementing this. Deeply appreciate all the help, Adam.
  • GeoffreyGeoffrey Member
    edited April 2013
    @fc_adam,

    Hi again, Adam. I've been working on implementing this ajax custom checkout validation, for the past few days and have encountered a snag that I'm not sure how to resolve. The whole script is a bit complex, but my debugging indicates that all the necessary data appears to be parsing correctly, except that the script is getting stuck on FC.checkout.config.isValid = false.

    After rigorous testing, all the logic I'm using appears to be sound. But there seems to be an issue with where FC.checkout.config.isValid = false does or does not get called in the logic, and how the ajax call gets processed. I'll try to give a brief explanation of what I'm trying to accomplish here (along with the code snippet), and explain what I see happening.

    First, I'm using the customer email as the filter data to be passed via the ajax call to my secure page, checking the transaction list via the API. The secure page filters by the customer email and responds with true or false, based on whether the customer has purchased a specific item before. I have that response passed back as an argument in another function that performs the validation logic, which is supposed to allow checkout if the response is true, or if the response is false but the customer is shipping the item "as a gift" to someone else. In all other cases, the logic should prevent checkout with a response of false and inform the customer of appropriate next steps.

    All of this appears to work except that in those cases where the logic should call FC.checkout.config.isValid = true; the checkout gets stuck and does not submit. A couple of additional notes may provide insight on what the problem is. One is that I begin the whole validation function by setting FC.checkout.config.isValid = false .... the reason being that if I don't set this somewhere before the ajax, the ajax call is for unknown reasons not being allowed to finish and parse the rest of the logic before checkout submits and completes the transaction. The only other way I could prevent the checkout from submitting before the ajax call completed was by adding an "async: false" parameter to the ajax call, and thus making it a synchronous rather than asynchronous call. I'd rather not do that if I don't have to because it freezes the browser and prevents any of the "beforeSend:" ajax functions from working while everything waits for the call to complete.

    Now, when I have the ajax running asynchronously (as it should, by definition), I'm wondering if the problem of the checkout sticking on FC.checkout.config.isValid = false results from it being called before an FC.checkout.config.isValid = true is called later in the script logic. Is the FC.checkout.config.isValid = true call invalidated by the earlier call of FC.checkout.config.isValid = false? It's unclear how to make this work, allowing the ajax to complete and parse the remaining logic, without somehow interrupting the checkout submission before the ajax has a chance to complete.

    Any insight or suggestions you may have would me much appreciated! Code snipped follows:
    <script type="text/javascript">
    
    function sValidate() {
    
    	var c_email = jQuery("#customer_email").val(); 	
    	var cValid;
    	
    	FC.checkout.config.isValid = false;
    
    	for (var p in fc_json.products) {
    		if (fc_json.products[p].code == "FP545TEST") {
    			jQuery.ajax({
    				type: "POST",
    				data: {data:c_email},
    				async: true,
    				url: "https://oursite.com/.../validation.php",
    				beforeSend: function() {
    					$("#fc_complete_order_button").hide();
    					$("#safe-order").hide();
    					$("#return-home").css("display", "none");
    					$("#fc_complete_order_processing .fc_error").html("Processing Your Order. Please Wait.");
    					$("#fc_complete_order_processing").css("display", "block");
    				},
    				success: function(validated){
    					alert(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 + c_lname;
    		var s_fullname = s_fname + s_lname;
    		
    		alert(cValid);
    		if (cValid == "true") {
    			FC.checkout.config.isValid = true;
    		} else if (cValid == "false" && s_fullname != "" && c_fullname != s_fullname) {
    			FC.checkout.config.isValid = true;
    		} else {
    			FC.checkout.config.isValid = false;
    			alert("Our records indicate that you have already purchased the \"Five Teas for Five Dollars\" special offer. This offer is intended for new customers and is limited to one purchase per customer. You can only purchase this item again as a gift, by providing the shipping address of a friend or loved one you would like to introduce to our teas. Alternatively, please go back and remove this item from your cart if you would like to complete checkout with other items.");
    			jQuery("#fc_complete_order_button").show();
    			jQuery("#safe-order").show();
    			jQuery("#fc_complete_order_processing").css("display", "none");
    			jQuery("#return-home").css("display", "block");
    			jQuery('#use_different_addresses').prop('checked', true);
    			jQuery('#fc_address_shipping_container').css("display", "block");
    		}
    	}		
    }
    
    for (var p in fc_json.products) {
    	if (fc_json.products[p].code == "FP545TEST") {
    		// Run our custom validation before the checkout validates
    		FC.checkout.overload("validateAndSubmit", "sValidate", null);
    	}
    }
    </script>
    
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Interesting - so yeah, I'd say what's happening is the checkout's validateAndSubmit function is completing well before your ajax call returns. What I'd suggest doing is changing your approach to make sure your function runs before validateAndSubmit even starts. I'd suggest doing something like this:
    var originalValidateAndSubmit = FC.checkout.validateAndSubmit;
    
    function sValidate() { ... }
    function checkVal() {
      ...
    
      originalValidateAndSubmit();
    }
    
    FC.checkout.override("validateAndSubmit", "sValidate");
    

    That approach will only trigger the normal validateAndSubmit function after your callback has completed successfully. You'll probably want to duplicate the loading animation set up that is part of the validateAndSubmit function is in your sValidate function - so the customer knows something is happening.
  • Interesting solution, Adam. I will definitely give this a try when I'm at the office tomorrow. I think it should work. I'll let you know if there's any further difficulty though. Thanks for the reply!
  • @fc_adam,

    This works! Thanks for the suggestion, Adam. One other thing.... I've duplicated the loading animation from validateAndSubmit for this custom validation, and it works fine for the most part. As I test though, I do see one problem happening. In the case where a customer does not pass the first custom validation and checkout is prevented, they have an option to ship the order as a gift to a friend (use different shipping address). So far this is all well and good, as I've made it clear what the appropriate next steps for the customer can be, and the "complete order" button has returned for them if they wish to send it elsewhere.

    The problem comes if a customer in this situation happens to attempt to submit the form again without filling in one of the required fields; at which point they will get an fc_error flagged on the relevant field, but the "complete order" button has been replaced with the loading animation and they no longer have a button to submit the form when making any further correction.

    Tricky stuff. Do you know of a way I can check for those error display updates in the script, so as to set the "complete order" button to show again when an error like this gets tripped on an attempted form submission?
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    Could you whisper me some steps and details to be able to replicate what you're describing there? I'd like to see it in action if I could - just to make sure I'm on the same page.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey {in response to whispers}

    Ah yep - so the way we handle it in our validateAndSubmit function is that we pull in the loading animation when all the errors have validated and are good to go - but as you're doing the loading animation yourself, and validateAndSubmit fails, it never gets to the point where it does the loading animation itself.

    What I'd suggest is to hijack the updateErrorDisplay function and use that to bring the checkout button back in. Something like this:
    FC.checkout.config.updateErrorDisplay = function (fieldName,showError) {
    	if (showError) {
    		jQuery("label.fc_error[for='" + fieldName + "']").show().parent(".fc_row").addClass("fc_row_error");
    	} else {
    		jQuery("label.fc_error[for='" + fieldName + "']").hide().parent(".fc_row").removeClass("fc_row_error");
    	}
    	// Custom logic
    	if (fieldName == "fc_form_checkout") {
    		// Bring back the checkout button here
    	}
    }
    
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    In working with another user who wanted to achieve something similar to you, I think I've found a better solution. Instead of keying off of the updateErrorDisplay function, simply checking after validateAndSubmit has completed to check if the progress bar is visible or not. If the progress bar is visible, then the page is submitting correctly. This user wanted to hide the "cancel and continue shopping" link when the customer clicks to check out - this is the code they will use:
    FC.checkout.overload("validateAndSubmit", function() { jQuery("#fc_cancel_continue_shopping").hide(); }, function() { if (!jQuery("#fc_complete_order_processing").is(":visible")) { jQuery("#fc_cancel_continue_shopping").show(); }});
    

    The second function in that call is where you'd place your code to bring back the checkout button if something has caused an error within the form, and you wouldn't need the first function for what you're doing - something like this would work:
    FC.checkout.overload("validateAndSubmit", null, function() { if (!jQuery("#fc_complete_order_processing").is(":visible")) { // Bring back stuff here }});
    
  • Hi Adam. I've returned to this validation script after putting it on the shelf for a while, as the offering we were using it on went dormant. But now it is going to be available again, and I'm in the final stages of testing its integration with the FoxyCart API. I've got everything working fine except one thing... The response I get from the API on the filter I'm using is not consistent with actual data in every case.

    Basically, I'm filtering this:
    $foxyData["api_action"] = "transaction_list";
    $foxyData["customer_email_filter"] = $email;
    

    The point is to determine if a customer has ordered from us before. And if the returned response is "No transactions found", it allows checkout to proceed. The offer in question is for first-time customers only. Well, I've been testing this against email addresses from our order records, and sometimes it returns the transactions (or some of the transactions) for a given customer, but in other cases it doesn't return any transactions, but I know for a fact that the given email address is associated with multiple transactions.

    Now to make things a little more confounding, I tried changing the the filter to this:
    $foxyData["api_action"] = "customer_list";
    $foxyData["customer_email_filter"] = $email;
    

    And where one of the email addresses tested didn't return any transaction records, it did return a customer record. I don't understand how the API can take the same email address and return data from the customer list, but return no data from the transaction list. Assuming that the customer_list filter only draws from the list of registered customers, I can see how the API might return data on a specific email address from the transaction_list filter, but not from the customer_list filter (as in the case of guest checkout). But I cannot understand why the inverse would occur.

    Is there something I'm missing here? Any help would be deeply appreciated. I would hate to deploy a validation that if full of holes and exceptions.
  • fc_adamfc_adam FoxyCart Team
    @Geoffrey,

    That's very weird! Could you whisper us some of the email addresses you're trying that is displaying this issue? We'll take a look from our side what might be going on.
Sign In or Register to comment.