如何在客户端处理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
相关产品推荐
相关产品推荐

