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

如何在WooCommerce Stripe网关收取与授权金额不同的费用?

解决WooCommerce Stripe延迟捕获时无法修改金额的问题

首先得明确你遇到报错的核心原因:当你创建并确认了1美元的PaymentIntent(授权状态)后,它会进入requires_capture状态——这个状态下,PaymentIntent的金额是固定死的,不能再修改,只能捕获你之前授权的1美元。这就是为什么你尝试捕获不同金额时会触发报错。

你的场景是先锁定用户支付方式,后续根据实际消费(通话时长)扣款,这种需求更适合用Stripe SetupIntent来实现,或者退一步用「高金额授权+按需捕获部分金额」的方案,下面分别说两种方案的具体实现:

方案一:用SetupIntent保存支付方式,后续按需创建PaymentIntent扣款(推荐)

这个方案的核心逻辑是:先不进行金额授权,只保存用户的支付方式;等实际产生费用后,再用保存的支付方式创建对应金额的PaymentIntent完成扣款。

具体步骤(结合WooCommerce):

  1. 拦截默认支付流程,创建SetupIntent
    在用户下单时,替换Stripe默认的PaymentIntent创建逻辑,改为生成SetupIntent保存支付方式。你可以把以下代码添加到主题的functions.php或自定义插件中:

    add_action('woocommerce_checkout_order_processed', 'save_payment_method_with_setup_intent', 10, 1);
    function save_payment_method_with_setup_intent($order_id) {
        $order = wc_get_order($order_id);
        if ($order->get_payment_method() !== 'stripe') return;
    
        // 初始化Stripe客户端
        $stripe_secret = get_option('woocommerce_stripe_settings')['secret_key'];
        $stripe = new \Stripe\StripeClient($stripe_secret);
    
        try {
            // 获取或创建用户的Stripe客户ID
            $user_id = $order->get_user_id();
            $stripe_customer_id = get_user_meta($user_id, '_stripe_customer_id', true);
            if (!$stripe_customer_id) {
                $customer = $stripe->customers->create([
                    'email' => $order->get_billing_email(),
                ]);
                $stripe_customer_id = $customer->id;
                update_user_meta($user_id, '_stripe_customer_id', $stripe_customer_id);
            }
    
            // 创建SetupIntent
            $setup_intent = $stripe->setupIntents->create([
                'customer' => $stripe_customer_id,
                'payment_method_types' => ['card'],
                'metadata' => ['order_id' => $order_id]
            ]);
    
            // 注:实际开发中需前端配合完成SetupIntent的确认(比如3DS验证),这里假设已拿到绑定的支付方式ID
            $payment_method_id = $setup_intent->payment_method;
    
            // 保存支付方式ID到订单元数据
            update_post_meta($order_id, '_stripe_saved_payment_method', $payment_method_id);
    
            // 设置订单为Hold状态
            $order->update_status('on-hold', '已保存支付方式,等待通话结束后扣款');
            $order->save();
        } catch (\Stripe\Exception\ApiErrorException $e) {
            $order->update_status('failed', '保存支付方式失败:' . $e->getMessage());
            wc_add_notice(__('支付方式保存失败:' . $e->getMessage()), 'error');
        }
    }
    
  2. 通话结束后执行扣款
    当通过Twilio获取到通话时长并算出实际费用后,调用下面的函数完成扣款:

    function process_post_call_payment($order_id, $actual_amount) {
        $order = wc_get_order($order_id);
        $payment_method_id = get_post_meta($order_id, '_stripe_saved_payment_method', true);
        if (!$payment_method_id) {
            $order->update_status('failed', '未找到保存的支付方式');
            return;
        }
    
        $stripe_secret = get_option('woocommerce_stripe_settings')['secret_key'];
        $stripe = new \Stripe\StripeClient($stripe_secret);
    
        try {
            // 创建并确认PaymentIntent(金额需转换为最小货币单位,比如美元用分)
            $payment_intent = $stripe->paymentIntents->create([
                'amount' => $actual_amount * 100,
                'currency' => $order->get_currency(),
                'payment_method' => $payment_method_id,
                'customer' => get_user_meta($order->get_user_id(), '_stripe_customer_id', true),
                'confirm' => true,
                'description' => '电话咨询费用 - 订单#' . $order->get_order_number(),
                'metadata' => [
                    'order_id' => $order_id,
                    'call_duration' => '替换为实际通话时长'
                ]
            ]);
    
            // 处理扣款结果
            if ($payment_intent->status === 'succeeded') {
                $order->update_status('completed', '已成功扣款:' . wc_price($actual_amount));
                $order->add_order_note('实际扣款金额:' . wc_price($actual_amount) . ',Stripe交易ID:' . $payment_intent->id);
                $order->payment_complete($payment_intent->id);
            } elseif ($payment_intent->status === 'requires_capture') {
                // 手动捕获款项(若开启了延迟捕获设置)
                $captured_intent = $stripe->paymentIntents->capture($payment_intent->id);
                if ($captured_intent->status === 'succeeded') {
                    $order->update_status('completed', '已成功扣款:' . wc_price($actual_amount));
                    $order->add_order_note('实际扣款金额:' . wc_price($actual_amount) . ',Stripe交易ID:' . $captured_intent->id);
                    $order->payment_complete($captured_intent->id);
                }
            } else {
                $order->update_status('failed', '扣款未完成,状态:' . $payment_intent->status);
            }
        } catch (\Stripe\Exception\ApiErrorException $e) {
            $order->update_status('failed', '扣款失败:' . $e->getMessage());
            $order->add_order_note('扣款失败原因:' . $e->getMessage());
        }
    }
    

方案二:高金额授权+按需捕获部分金额(备选)

如果能预估用户的最高可能消费(比如每次咨询最多100美元),可以用这个方案:

  1. 下单时创建一个金额为最高消费额的PaymentIntent,在WooCommerce Stripe设置中开启「延迟捕获」,让订单进入requires_capture状态并设置为Hold。
  2. 通话结束后,计算实际费用,调用Stripe API的capture方法传入实际金额——这样只会捕获实际费用,剩余的授权金额会在7天后自动过期(Stripe默认授权有效期)。

注意事项:

  • 必须确保授权金额足够覆盖所有可能的消费,否则无法扣款超过授权的部分。
  • 需在Stripe后台确认授权有效期设置,避免提前过期导致无法扣款。

关键提醒

  • 无论用哪个方案,都需要处理支付验证(比如3DS),尤其是SetupIntent的确认步骤,必须前端配合完成。
  • 确保你的Stripe账户已开启对应的权限(比如手动捕获、SetupIntent功能)。
  • 要处理异常情况:比如支付方式过期、扣款失败、用户取消支付等,避免订单状态异常。

内容的提问来源于stack exchange,提问作者sunny k

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 20:38:11