title: Invisible reCaptcha: can't make a working AJAX call, getting infinite loop
body: <p>The form had a JS validation and worked OK. Then I had to implement invisible reCaptcha with working validation.<br>
  The code looks like this:<br>
  <strong>index.html</strong></p>
  
  <pre><code>//In the head I've
  
  &lt;script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"&gt;&lt;/script&gt;
  &lt;script src="https://google.com/recaptcha/api.js?onload=initRecaptcha&amp;render=explicit"&gt;&lt;/script&gt;
  
  //then form comes
  
  &lt;form enctype="multipart/form-data" method="POST" id="myForm" class="callback"&gt;                                    
      &lt;div class="inputs"&gt;
          &lt;label class="input input-name required"&gt;
              &lt;span class="placeholder"&gt;Name&lt;/span&gt;
              &lt;input type="text" name="name"&gt;
          &lt;/label&gt;
          &lt;label class="input input-phone required"&gt;
              &lt;span class="placeholder"&gt;Phone&lt;/span&gt;
              &lt;input type="text" name="phone" maxlength="12"&gt;
          &lt;/label&gt;
          &lt;label class="input input-mail"&gt;
              &lt;span class="placeholder"&gt;Email&lt;/span&gt;
              &lt;input type="text" name="email"&gt;
          &lt;/label&gt;
      &lt;/div&gt;
  
      &lt;div class="check"&gt;
          &lt;label&gt;
              &lt;input class="check-agree" type="checkbox" name="agree" checked="checked" value="1"&gt;
              &lt;span class="label"&gt;I agree &lt;span class="agree-text js-agree-text"&gt;with terms and conditions&lt;/span&gt;&lt;/span&gt;
          &lt;/label&gt;
          &lt;div id="status"&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class="submit-wrapper"&gt;
          &lt;label class="submit"&gt;
              &lt;input class="submit-form-button js-submit-form" type="submit" value="Submit"&gt;
          &lt;/label&gt;            
  
     &lt;/div&gt;       
      &lt;div id="form-recaptch"&gt;&lt;/div&gt;
  &lt;/form&gt;
  
  
  //and in the end
  &lt;script type="text/javascript" src="js/form.js"&gt;&lt;/script&gt; 
  </code></pre>
  
  <p>the <strong>form.js</strong> comes next:   </p>
  
  <pre><code>var submit_form_outer;
  
  var initRecaptcha = function(){
      if ( document.getElementById("form-recaptch") ) {
  
          recaptcha = grecaptcha.render("form-recaptch", {
              'sitekey' : 'code',
              'theme'    : 'dark',            
              'badge'    : 'inline',
              'size'     : 'invisible',
              'callback' : submit_form_outer
          });
      }
  }
  
  $(function () {        
  
      $('.callback').submit(function (e) {
          submit_form($(this));
          e.preventDefault();
          /*stop refresh*/
          e.stopPropagation();
      });
  
      /*More Functions*/
  
      function validateEmail(form) {
          var email = form.find('[name="email"]');
          var re = /.+@.+\..+/g;
          if( re.test(email.val()) || email.val() === ""){
              return true;
          } else {
              email.closest('.input').addClass('error');
              return false;
          }
      }
      function hasErrors (form) {
          var required = form.find('.required');
          var has_errors = false;
          required.each(function(){
              if(!$(this).find('input').val()){
                  $(this).addClass('error');
                  has_errors = true;
              }
          });
          return has_errors;
      }
  
  
      function submit_form() {
  
          if( hasErrors($("form#myForm")) || !validateEmail($("form#myForm"))){
              return false;
          }
      grecaptcha.execute();
  
      var container = $("#form-recaptch").parents("form");
  
      $.ajax({
          method: "POST",
          url: "ajax.php",
          data: container.serialize(),
  
          success: function (responseObj) {
                  container.submit();                
                  $('#myForm input').val('');
                  $('#myForm textarea').val('');                            
                  $('#status').html(responseObj);                             
          },
          error: function (xhr, error) {
            console.debug(xhr); 
            console.debug(error);
            $("#status").html("Failed.");
          },        
  
      });        
      }
  
      submit_form_outer = submit_form;
  
      function Form () {
          /*...*/
  
  
          /*More Functions*/
          $('.form').on('click', '.js-agree-text', function () {
              showAgreeBox();
              openFormPanel();
              return false;
          });
  
          $('.form').on('click', '.js-no-agree', function () {
              agreeNo();
              return false;
          });
  
  
          $('.form').on('click', '.js-yes-agree', function () {
              agreeYes();
              return false;
          });
  
          $('.check-agree').change(function() {
              var form = $(this).closest('.form');
              if($(this).is(":checked")) {
                  form.find('.js-submit-form').removeClass('disabled');
              } else {
                  form.find('.js-submit-form').addClass('disabled');
              }
  
          }
          /*More Functions*/
          this.callback = function () {
  
              showThankBox();
              openFormPanel();
  
              $.fn.fullpage.setAllowScrolling(false);
  
          }
  
      }
      var mainform = new Form ();
  });
  </code></pre>
  
  <p>And the <strong>ajax.php</strong>  with phpMailer:</p>
  
  <pre><code>&lt;?php
  $captcha;
  
  if ( isset($_POST['g-recaptcha-response']) ) {
      $captcha = $_POST['g-recaptcha-response'];
  }
  
  if( !$captcha ){
      $response = array (
          'status' =&gt; 'error',
          'info' =&gt; 'Please check the the captcha form.'
      );
      echo json_encode($response);
      exit;
  }
  
  
  if ($_SERVER["REQUEST_METHOD"] == "POST") {
      // Get the form fields and remove whitespace.
      $name = strip_tags(trim($_POST["name"]));
              $name = str_replace(array("\r","\n"),array(" "," "),$name);
      $email = filter_var(trim($_POST["email"]), FILTER_SANITIZE_EMAIL);
      $phone = trim($_POST["phone"]);
  }
  
  
  $secretKey = "key";
  
  $ip = $_SERVER['REMOTE_ADDR'];
  
  $fields = array(
          'secret'    =&gt;  $secretKey,
          'response'  =&gt;  $captcha,
          'remoteip'  =&gt;  $ip
      );
      $ch = curl_init("https://www.google.com/recaptcha/api/siteverify");
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, 15);
      curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));
      $responseKeys = json_decode(curl_exec($ch));
      curl_close($ch);
  
  
  if(intval($responseKeys["success"]) !== 1) {
      $response = array (
          'status' =&gt; 'spam',
          'info' =&gt; 'You look like a spam.'
      );
  
      echo '&lt;span style="color:red;"&gt;'.json_encode($response).'&lt;/span&gt;';
      exit;
  } else {
  
      require_once dirname(__FILE__) . '/phpmailer/PHPMailerAutoload.php';    
  
      $mail = new PHPMailer;
  
      if(!$mail-&gt;send()) 
      {
          echo "Mailer Error: " . $mail-&gt;ErrorInfo;
      } 
      else 
      {
          echo "Message has been sent successfully";
      }   
  
  
  
  }
  
  ?&gt;
  </code></pre>
  
  <p>So, the problem is that CAPTCHA doesn't even check the form.<br>
  For instance I have another website where configuration is pretty simillar but with other validation, so each time I press submit button the images of CAPTCHA are shown and I should pass the test.<br>
  In this case nothing is shown. Only <code>{"status":"error","info":"Please check the the captcha form."}</code> message does appear which says that CAPTCHA test is skipped and not executed.</p>
  
  <p>I want to keep the working form validation and make everything work together.
  Any modifications/optimizations are welcome.</p>
  
  <p><strong>UPDATE</strong>:  This is new <strong>form.js</strong> code, now I'm getting infinte loops somewhere between <code>success: function</code> and the input <strong>validation</strong> has stopped working:</p>
  
  <pre><code>var submit_form_outer;
  
  var initRecaptcha = function(){
      if ( document.getElementById("form-recaptcha") ) {
  
          recaptcha = grecaptcha.render("form-recaptcha", {
              'sitekey' : '',
              'theme'    : 'dark',
              'type'     : 'image',
              'badge'    : 'inline',
              'size'     : 'invisible',
              'callback' : submit_form_outer
          });
      }
  }
  
  $(function () {      
  
      submit_form_outer = submit_form; 
  
      $('.callback').submit(function (e) {        
          submit_form($(this));
          e.preventDefault();
          /*stop refresh*/        
      /*    e.stopPropagation();*/
      });
  
  
  
      var phone = $('[name="phone"]'),
          input = $('input[type="text"], textarea');
  
  
  
      phone.click(function () {
          $(this).parent().removeClass('error');
          if ($(this).val().length == 0) {
              $(this).val('+');
          }
      });
      phone.focusout(function () {
          if ($(this).val().length &lt; 2) {
              $(this).val('');
          }
      });
  
      phone.bind("change keyup input click", function() {
          if (this.value.match(/[^0-9+ ]/g)) {
              this.value = this.value.replace(/[^0-9+ ]/g, '');
          }
      });
  
      input.focus(function () {
          $(this).parent().removeClass('error');
          $(this).parent().find('span').addClass('focused');
      });
      input.focusout(function(){
          if ($(this).val() == ''){
              $(this).parent().find('span').removeClass('focused');
          }
      });
  
      function validateEmail(form) {
          var email = form.find('[name="email"]');
          var re = /.+@.+\..+/g;
          if( re.test(email.val()) || email.val() === ""){
              return true;
          } else {
              email.closest('.input').addClass('error');
              return false;
          }
      }
      function hasErrors (form) {
          var required = form.find('.required');
          var has_errors = false;
          required.each(function(){
              if(!$(this).find('input').val()){
                  $(this).addClass('error');
                  has_errors = true;
              }
          });
          return has_errors;
      }
  
  
      function submit_form() {
  
          if( hasErrors($("form#myForm")) || !validateEmail($("form#myForm"))){
              return false;
          } 
  
  
      var container = $("#form-recaptcha").parents("form");
  
  
      $.ajax({
          method: "POST",
          url: "ajax.php",
          data: container.serialize(),
  
          success: function (responseObj, textStatus, xhr) {                            
  
              console.log('success');
              console.log(responseObj);
  
              container.submit();                 
              $('#myForm textarea').val('');                            
              $('#status').html(responseObj);
  
             /*if(xhr.status === '200'){
                  $("#status").html('200');               
              }
              else{
                  console.log('else');
                  grecaptcha.reset();
              }*/         
  
          },
          error: function (xhr, error) {      
            console.log('error');     
            console.debug(error);
            console.debug(xhr);           
            $("#status").html("Failed.");
            grecaptcha.reset();
          },        
  
      });
          grecaptcha.reset();
      }
  
  
  
      function Form () {
  
          function openFormPanel () {
              $('body').addClass('form-panel-opened form-panel-close-callback');
              $.fn.fullpage.setAllowScrolling(false);
          }
          function closeFormPanel () {
              $('body').removeClass('form-panel-opened');
              $('.button-menu').removeClass('active');
              window.setTimeout(function () {
                  $('body').removeClass('form-panel-close-callback');
                  $.fn.fullpage.setAllowScrolling(true);
              }, 700);
          }
  
          function showAgreeBox () {
              $('.form .agreement-box').addClass('show');
              $('.form .thank-box').removeClass('show');
              $('.agreement .scrollable').scrollTop(0);
          }
          function showFormBox () {
              $('.form .agreement-box').removeClass('show');
              $('.form .thank-box').removeClass('show');
          }
          function showThankBox () {
              $('.form .agreement-box').removeClass('show');
              $('.form .thank-box').addClass('show');
          }
  
          function agreeYes(){
              if($('body').hasClass('fp-viewing-2')){
                  closeFormPanel();
              } else {
                  showFormBox();
              }
              $('.js-submit-form').removeClass('disabled');
              $('.check-agree').prop('checked', true);
          }
          function agreeNo(){
              if($('body').hasClass('fp-viewing-2')){
                  closeFormPanel();
              } else {
                  showFormBox();
              }
              $('.js-submit-form').addClass('disabled');
              $('.check-agree').prop('checked', false);
          }
  
  
          $('.form').on('click', '.js-agree-text', function () {
              showAgreeBox();
              openFormPanel();
              return false;
          });
  
          $('.form').on('click', '.js-no-agree', function () {
              agreeNo();
              return false;
          });
  
  
          $('.form').on('click', '.js-yes-agree', function () {
              agreeYes();
              return false;
          });
  
          $('.check-agree').change(function() {
              var form = $(this).closest('.form');
              if($(this).is(":checked")) {
                  form.find('.js-submit-form').removeClass('disabled');
              } else {
                  form.find('.js-submit-form').addClass('disabled');
              }
          }).mouseover(function () {
              $(this).addClass('hover');
          }).mouseout(function () {
              $(this).removeClass('hover');
          });
  
          $('.form').on('click', '.js-submit-form', function () {
              var form = $(this).closest('.form');
              var agree = form.find('.check-agree').prop('checked');
              if(!agree) return false;
          });     
  
          $('.form').on('click', '.js-submit-form', function () {
              if( !hasErrors($("form#myForm")) ||       validateEmail($("form#myForm"))){
              if ( grecaptcha.getResponse() !== 0 ) {
                  grecaptcha.execute(); 
              }   
          } 
  
      });
  
          function resetForm () {
              $('.form input[type="text"], .form textarea').each(function () {
                  $(this).val('');
              });
              $('.form .placeholder').removeClass('focused');
  
  
          }
  
          this.callback = function () {
  
              showThankBox();
              openFormPanel();
              $.fn.fullpage.setAllowScrolling(false);
  
          }
      }
      var mainform = new Form ();
  });
  </code></pre>