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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:52:47