You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在客户端处理Stripe创建客户时的异常错误?

问题

使用Stripe处理支付业务,通过PHP后端创建Payment Intent,前端JavaScript完成调用并处理结果。目前卡被拒这类支付相关错误可正常处理,但后端捕获的其他类型错误(例如超长手机号触发的\Stripe\Exception\InvalidRequestException)无法在前端获取并展示给用户。

测试时后端返回错误信息:

{"error":"Invalid string: 0123456789...0123456789; must be at most 20 characters"}

但前端控制台显示状态码为200的Response对象,Promise会继续执行后续逻辑,无法捕获错误并终止流程。

前端代码

var stripe = "***HIDDEN***";
var stripesetup = "/stripecreate.php";
var subscriptionid = document.getElementById("subscriptionid").value;
var email = document.getElementById('email').value;

// 客户购买的商品
var purchase = {
  items: [{ id: subscriptionid }]
};

// 初始化时禁用支付按钮
document.getElementById("btn-checkout").disabled = true;
fetch(stripesetup, {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(purchase)
}).then(function(result) {
     /* 此处本应捕获错误,但result并非后端返回的错误内容 */
    return result.json();
}).catch( (message) => {
     /* 尝试在此处理错误但未成功 */
}).then(function(data) {
    var elements = stripe.elements();

    var style = {
      base: {
        color: "#32325d",
        fontFamily: 'Arial, sans-serif',
        fontSmoothing: "antialiased",
        fontSize: "16px",
        "::placeholder": {
          color: "#32325d"
        }
      },
      invalid: {
        fontFamily: 'Arial, sans-serif',
        color: "#fa755a",
        iconColor: "#fa755a"
      }
    };

    // 注入Stripe卡片输入框
    var card = elements.create("card", { style: style });
    card.mount("#card-element");
    
    /*
    var cardNumberElement = elements.create('cardNumber');
    cardNumberElement.mount("#card-element");

    var cardExpiryElement = elements.create('cardExpiry');
    cardExpiryElement.mount("#cardexpiry-element");
    */
    
    card.on("change", function (event) {
      // 无卡片信息时禁用按钮
      document.getElementById("btn-checkout").disabled = event.empty;
      document.querySelector("#shopvalidation").textContent = event.error ? event.error.message : "";
    });

    var form = document.getElementById("shopform5");
    form.addEventListener("submit", function(event) {
      event.preventDefault();
    
      // 提交支付
      payWithCard(stripe, card, data.clientSecret);
    });
    
});

// 调用Stripe支付确认接口
var payWithCard = function(stripe, card, clientSecret) {
  loading(true);
  stripe
    .confirmCardPayment(clientSecret, {
      receipt_email: email,
      payment_method: {
        card: card
      }
    })
    .then(function(result) {
      if (result.error) {
        // 展示支付错误
        showError(result.error.message);
      } else {
        // 支付成功跳转
        orderComplete(result.paymentIntent.id);
      }
    });
};

/* UI工具函数 */

// 支付成功处理
var orderComplete = function(paymentIntentId) {
  loading(false);
  document.getElementById("btn-checkout").disabled = true;
  location.href="/thank-you";  
};

// 展示错误信息
var showError = function(errorMsgText) {
  loading(false);
  var errorMsg = document.querySelector("#shopvalidation");
  errorMsg.textContent = "很抱歉,支付失败:"+errorMsgText+",请联系我们获取更多帮助";
  setTimeout(function() {
    errorMsg.textContent = "";
  }, 10000);
};

// 加载状态切换
var loading = function(isLoading) {
  if (isLoading) {
    document.getElementById("btn-checkout").disabled = true;
    document.querySelector("#lds-ring").classList.remove("invisible");
    document.querySelector("#button-text").classList.add("invisible");
  } else {
    document.getElementById("btn-checkout").disabled = false;
    document.querySelector("#lds-ring").classList.add("invisible");
    document.querySelector("#button-text").classList.remove("invisible");
  }
};

后端代码

try {
    $customer = \Stripe\Customer::create([
        'name' => $fullname,
        'email' => $email,
        'phone' => $telephone,
        "shipping" => [
            'name' => $fullname,
            "address" => [
                "line1" => $address1,
                "line2" => $address2,
                "city" => $town,
                "state" => $county,
                "country" => $countrycode
            ],
        ],
        "address" => [
            "line1" => $billingaddress1,
            "line2" => $billingaddress2,
            "city" => $billingtown,
            "state" => $billingcounty,
            "country" => $billingcountrycode
        ],
        'description' => $userid
    ]);

    $stripeid = $customer->id;

} catch(\Stripe\Exception\CardException $e) {
  // 卡相关错误处理
  echo 'Status is:' . $e->getHttpStatus() . '\n';
  echo 'Type is:' . $e->getError()->type . '\n';
  echo 'Code is:' . $e->getError()->code . '\n';
  echo 'Param is:' . $e->getError()->param . '\n';
  echo 'Message is:' . $e->getError()->message . '\n';
  die();
} catch (\Stripe\Exception\RateLimitException $e) {
  // 请求频率超限
  echo json_encode(['error' => $e->getMessage()]);
  die();
} catch (\Stripe\Exception\InvalidRequestException $e) {
  // 参数错误
  echo json_encode(['error' => $e->getMessage()]);
  die();
} catch (\Stripe\Exception\AuthenticationException $e) {
  // 认证失败
  echo json_encode(['error' => $e->getMessage()]);
  die();
} catch (\Stripe\Exception\ApiConnectionException $e) {
  // 网络连接错误
  echo json_encode(['error' => $e->getMessage()]);
  die();
} catch (\Stripe\Exception\ApiErrorException $e) {
  // 其他Stripe API错误
  echo json_encode(['error' => $e->getMessage()]);
  die();
} catch (Exception $e) {
  // 非Stripe相关错误
  echo json_encode(['error' => $e->getMessage()]);
  die();
}

解决方案

1. 后端调整:返回错误状态码

当前后端返回错误时HTTP状态码仍为200,fetch仅在网络错误或状态码≥400时触发catch。需在捕获错误时设置对应状态码:

修改所有非CardException的catch块,以InvalidRequestException为例:

catch (\Stripe\Exception\InvalidRequestException $e) {
  http_response_code(400); // 400表示请求参数错误
  echo json_encode(['error' => $e->getMessage()]);
  die();
}

同时统一CardException的返回格式,避免纯文本导致前端解析失败:

catch(\Stripe\Exception\CardException $e) {
  http_response_code(402); // 402表示支付失败
  echo json_encode([
    'error' => $e->getError()->message
  ]);
  die();
}

2. 前端调整:修正Promise流程

当前Promise链中catch之后的then仍会执行,因为catch会返回resolved状态的Promise。需在解析响应后检查错误,或提前判断响应状态:

修改前端fetch逻辑:

fetch(stripesetup, {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(purchase)
})
.then(function(result) {
  // 先检查响应状态是否正常
  if (!result.ok) {
    // 状态码异常时解析错误信息并抛出
    return result.json().then(err => { throw new Error(err.error); });
  }
  return result.json();
})
.then(function(data) {
  // 仅成功时初始化Stripe Elements
  var elements = stripe.elements();
  // ... 原有初始化代码保持不变
})
.catch(function(error) {
  // 统一处理所有错误
  loading(false);
  showError(error.message);
  document.getElementById("btn-checkout").disabled = false;
});

3. 额外优化:前端提前校验

在发送请求前对手机号等字段做前端校验,减少不必要的后端请求:

var telephone = document.getElementById('telephone').value;
if (telephone.length > 20) {
  showError("手机号长度不能超过20位");
  document.getElementById("btn-checkout").disabled = false;
  return;
}

调整后,后端返回的错误会触发前端catch块,终止后续流程并展示错误信息给用户。

内容的提问来源于stack exchange,提问作者Rambly Runner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 13:50:24