When you return a bad request from Controller a global exception is triggered.
Probably a error page is displayed in the client, so jquery get 200 response.
Solution 1:
Controller
[HttpPost]
public ActionResult FooAction(string id, string[] orderFields)
{
    bool hasError = true; //TODO: Validation
    if (hasError)
    {
       Response.Clear();
       Response.TrySkipIisCustomErrors = true; //the magic
       Response.StatusCode = (int)HttpStatusCode.InternalServerError;
       return Json(new { success = false, message = "test error", status = 500 });
    }
    else
    {
       return Json(new { success = true, message = "ok", status = 200 });
    }
}
View:
<script type="text/javascript">
    $.ajax({
        type: "POST",
        url: url,
        data: { orderFields: order },
        success: function (response) {
            if (response.success) {
                alert("Ok");
            }
        },
        error: function (xhr, status, error) {
            if (xhr.responseText != "") {
                var err = JSON.parse(xhr.responseText);
                if (err.status == 440) {
                    alert("Session expired");
                }
                else {
                    alert(err.message);
                }
            }
            else {
                alert("Crash");
            }
        }
    });
</script>
Solution 2:
(More Elegant) 
Create a custom attribute
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Net;
using System.Web.Mvc;
public class ExceptionJsonMvcAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        if (context.Exception == null)
            return;
        int status;
        string message;
        var ex = context.Exception;
        var exceptionType = ex.GetType();
        if (exceptionType == typeof(UnauthorizedAccessException))
        {
            var exAccess = (UnauthorizedAccessException)ex;
            message = exAccess.Message;
            status = (int)HttpStatusCode.Unauthorized;
        }
        else if (exceptionType == typeof(SqlException))
        {
            var exSql = (SqlException)ex;
            message = GetDbMessage(exSql);
            status = (int)HttpStatusCode.BadRequest;
        }
        else if (exceptionType == typeof(KeyNotFoundException))
        {
            var exNotFound = (KeyNotFoundException)ex;
            message = exNotFound.Message;
            status = (int)HttpStatusCode.NotFound;
        }
        else
        {
            message = ex.Message;
            status = (int)HttpStatusCode.InternalServerError;
        }
        string json = ""; // TODO: Json(new { success = false, message = message, status = status });
        context.ExceptionHandled = true;
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.TrySkipIisCustomErrors = true;
        context.HttpContext.Response.StatusCode = status;
        context.HttpContext.Response.ContentType = "application/json";
        context.HttpContext.Response.Write(json);
    }
    private string GetDbMessage(SqlException exSql)
    {
        //TODO: Remove generic from database
        return "DataBase Error see log";
    }
}
pay attention for ApiController use System.Net.Http instead of System.Web.Mvc
Controller:
[ExceptionJsonMvc]
[HttpPost]
public ActionResult FooAction(string id, string[] orderFields)
{
    bool hasError = true; //TODO: Validation
    if (hasError)
    {
       throw new Exception("test error");
    }
    else
    {
       return Json(new { success = true, message = "ok" });
    }
}