如何在Stripe支付网关中避免为无效卡生成Token
如何在Stripe生成Token时验证卡片支付有效性
嘿,我来帮你搞定这个问题!你现在的代码已经能成功生成Stripe Token,并且处理了部分基础错误,但要确保生成Token后能顺利完成支付,我们可以从两个层面优化:完善Token生成时的错误处理,以及添加预授权验证来确认卡片的实际支付能力。
一、先搞懂Stripe Token生成的内置验证
其实Stripe在创建Card Token的时候,已经会自动验证卡片的基础有效性:
- 卡号格式是否合法(Luhn校验)
- CVC是否符合对应卡种的格式(比如Visa是3位,Amex是4位)
- 卡片是否已过期
- 卡号是否属于有效卡组织
如果这些验证失败,Stripe会返回对应的错误信息,你当前的代码只处理了invalid_number和exp_month,可以扩展错误处理逻辑,覆盖更多场景:
改进后的错误处理代码
if(@$tokendata["error"]) { $errorType = $tokendata['error']['type']; $errorParam = $tokendata['error']['param']; $errorMsg = $tokendata['error']['message']; switch(true) { case $errorParam == 'invalid_number': $this->Flash->error("请输入有效的卡号。"); break; case $errorParam == 'exp_month' || $errorParam == 'exp_year': $this->Flash->error("请输入有效的有效期。"); break; case $errorParam == 'cvc': $this->Flash->error("CVC码无效,请检查后重试。"); break; case $errorType == 'card_error' && strpos($errorMsg, 'expired') !== false: $this->Flash->error("卡片已过期,请更换卡片。"); break; default: $this->Flash->error($errorMsg); break; } }
二、添加预授权验证(确保卡片能实际支付)
上面的基础验证只能确保卡片信息格式合法,但无法确认卡片是否有足够余额、是否能通过发卡行的风控验证。要解决这个问题,我们可以在生成Token后,创建一个**$0或$1的预授权Charge**,然后立即退款,这样既能验证卡片的支付能力,又不会给用户造成实际扣费。
完整的改进代码示例
if ($this->request->is('post') && $this->request->getData('gtoken') == 'gtoken') { $planId = $this->request->getData('planid'); $amount = $this->request->getData('finalamount'); $cc = $this->request->getData('cc'); $cardHolder = $this->request->getData('cardHolder'); $expiryMonth = $this->request->getData('expiryMonth'); $expiryYear = $this->request->getData('expiryYear'); $cvc = $this->request->getData('cvc'); if($cc) { // 1. 生成Card Token $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://api.stripe.com/v1/tokens"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'card[number]' => $cc, 'card[exp_month]' => $expiryMonth, 'card[exp_year]' => $expiryYear, 'card[cvc]' => $cvc, 'card[name]' => $cardHolder // 建议添加持卡人姓名,提升验证准确性 ])); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_USERPWD, STRIPE_SECRET_KEY . ":" . ""); $headers = array(); $headers[] = "Content-Type: application/x-www-form-urlencoded"; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $tokendata = json_decode(curl_exec($ch), true); if(curl_errno($ch)) { $this->Flash->error("网络错误:" . curl_error($ch)); curl_close($ch); return; } curl_close($ch); // 处理Token生成错误 if(@$tokendata["error"]) { $errorType = $tokendata['error']['type']; $errorParam = $tokendata['error']['param']; $errorMsg = $tokendata['error']['message']; switch(true) { case $errorParam == 'invalid_number': $this->Flash->error("请输入有效的卡号。"); break; case $errorParam == 'exp_month' || $errorParam == 'exp_year': $this->Flash->error("请输入有效的有效期。"); break; case $errorParam == 'cvc': $this->Flash->error("CVC码无效,请检查后重试。"); break; case $errorType == 'card_error' && strpos($errorMsg, 'expired') !== false: $this->Flash->error("卡片已过期,请更换卡片。"); break; default: $this->Flash->error($errorMsg); break; } return; } $token = $tokendata['id']; // 2. 预授权验证:创建$1的Charge(也可以用$0,但部分卡种不支持$0预授权) $chCharge = curl_init(); curl_setopt($chCharge, CURLOPT_URL, "https://api.stripe.com/v1/charges"); curl_setopt($chCharge, CURLOPT_RETURNTRANSFER, 1); curl_setopt($chCharge, CURLOPT_POSTFIELDS, http_build_query([ 'amount' => 100, // 单位:分,即$1 'currency' => 'usd', // 根据你的业务修改币种 'source' => $token, 'description' => '预授权验证', 'capture' => 'false' // 设置为false表示只预授权,不实际扣费 ])); curl_setopt($chCharge, CURLOPT_POST, 1); curl_setopt($chCharge, CURLOPT_USERPWD, STRIPE_SECRET_KEY . ":" . ""); $headersCharge = array(); $headersCharge[] = "Content-Type: application/x-www-form-urlencoded"; curl_setopt($chCharge, CURLOPT_HTTPHEADER, $headersCharge); $chargeData = json_decode(curl_exec($chCharge), true); if(curl_errno($chCharge)) { $this->Flash->error("预授权验证失败:" . curl_error($chCharge)); curl_close($chCharge); return; } curl_close($chCharge); // 处理预授权错误 if(@$chargeData["error"]) { $this->Flash->error("卡片无法用于支付:" . $chargeData['error']['message']); return; } // 3. 立即撤销预授权(因为我们只是验证,不需要实际扣费) $chRefund = curl_init(); curl_setopt($chRefund, CURLOPT_URL, "https://api.stripe.com/v1/refunds"); curl_setopt($chRefund, CURLOPT_RETURNTRANSFER, 1); curl_setopt($chRefund, CURLOPT_POSTFIELDS, http_build_query([ 'charge' => $chargeData['id'] ])); curl_setopt($chRefund, CURLOPT_POST, 1); curl_setopt($chRefund, CURLOPT_USERPWD, STRIPE_SECRET_KEY . ":" . ""); $headersRefund = array(); $headersRefund[] = "Content-Type: application/x-www-form-urlencoded"; curl_setopt($chRefund, CURLOPT_HTTPHEADER, $headersRefund); $refundData = json_decode(curl_exec($chRefund), true); if(curl_errno($chRefund)) { // 即使退款失败,也不影响后续支付,但可以记录日志 error_log("预授权退款失败:" . curl_error($chRefund)); } curl_close($chRefund); // 到这里,卡片验证通过,继续后续流程 $this->request->session()->write('payment',$this->request->getData()); $this->request->session()->write('paymenttoken',$token); $this->request->session()->write('isapplycoupon', false); $this->savePayment(); return $this->redirect(['controller'=>'venues', 'action'=>'add']); } }
关键说明
- 预授权金额:推荐用$1(100分),因为部分发卡行不支持$0预授权,$1的预授权会在退款后很快从用户账单中消失,不会产生实际扣费。
- 持卡人姓名:在生成Token时添加
card[name]参数,能提升Stripe的验证准确性,避免部分卡片因为缺少姓名而验证失败。 - 错误处理:完善了各个环节的错误捕获,确保用户能得到清晰的提示。
内容的提问来源于stack exchange,提问作者Yogendra - eCommerce Developer
相关产品推荐
相关产品推荐

