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

Stripe支付后钱包余额更新失败问题求助

问题根源分析

从报错信息和代码来看,核心问题有两个:

1. 控制器函数被错误调用

updateWalletById是Express的路由控制器,设计用来接收req, res, next三个HTTP请求相关参数,但你在initStripePaymentIntent里直接传入walletId, amount,导致:

  • 第一个参数req被赋值为walletId字符串,自然req.params不存在,触发Cannot read properties of undefined (reading 'walletId')
  • 第二个参数res被赋值为amount数字,不是Express的响应对象,调用res.status()时触发res.status is not a function

2. 支付流程逻辑错误

当前代码在创建Stripe Payment Intent后就直接更新钱包,这是错误的——Payment Intent创建仅代表支付初始化,用户还未完成支付,此时更新钱包会导致余额与实际支付状态不符。正确的做法是通过Stripe Webhook监听payment_intent.succeeded事件,确认支付成功后再更新钱包。


修复方案

步骤1:拆分钱包更新的业务逻辑与控制器

把钱包更新的核心逻辑从控制器中抽离,变成可直接调用的业务函数,避免控制器和业务逻辑耦合:

修改wallet.controllers.js,新增业务函数

// 新增:纯业务逻辑函数,用于更新钱包余额
const updateWalletBalance = async (walletId, amountToAdd, updaterId) => {
  try {
    const existingWallet = await findWalletById(walletId);
    if (existingWallet.status === NOT_FOUND) {
      return {
        status: NOT_FOUND,
        hasError: true,
        message: 'Wallet not found',
        error: existingWallet.error
      };
    }

    const currentBalance = existingWallet.data.balance || 0;
    const updatedBalance = currentBalance + (amountToAdd || 0);

    const updatedWallet = await findWalletByIdAndUpdate(
      { balance: updatedBalance },
      walletId,
      updaterId
    );

    if (updatedWallet.status === NOT_FOUND) {
      return {
        status: NOT_FOUND,
        hasError: true,
        message: 'Wallet not found',
        error: updatedWallet.error
      };
    }

    return {
      status: SUCCESS,
      hasError: false,
      message: 'Wallet updated successfully',
      data: { wallet: updatedWallet.data }
    };
  } catch (error) {
    logError('ERROR @ updateWalletBalance -> wallet.controllers.js', error);
    return {
      status: SERVER_ERROR,
      hasError: true,
      message: 'ERROR: Requested operation failed.',
      error: { error: 'An unexpected error occurred on the server.' }
    };
  }
};

// 原控制器函数,调用业务逻辑
const updateWalletById = async (req, res, next) => {
  const walletId = req.params.walletId;
  const balanceToAdd = req.body.balance;
  const updaterId = req.tokenData.sub;

  const result = await updateWalletBalance(walletId, balanceToAdd, updaterId);
  
  if (result.hasError) {
    return res.status(result.status).json(result);
  }
  return res.status(SUCCESS).json(result);
};

// 导出新增的业务函数
module.exports = { updateWalletById, updateWalletBalance };

步骤2:修正Stripe支付流程,改用Webhook处理支付成功

2.1 修改stripe.services.js,移除提前更新钱包的代码

const { logError } = require(`../../dependencies/helpers/console.helpers`);
// 导入新增的业务函数(暂时先不用,等Webhook里用)
const { updateWalletBalance } = require(`../../api/controllers/wallet.controllers`);

const {
  HTTP_STATUS_CODES: { SUCCESS, SERVER_ERROR }
} = require(`../../dependencies/config`);
const { STRIPE_API_SECRET_KEY } = require(`../../dependencies/credentials`);

const initStripePaymentIntent = async (amount, currency, walletId, updaterId) => {
  try {
    const stripe = require(`stripe`)(STRIPE_API_SECRET_KEY);

    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency,
      payment_method_types: [`card`],
      metadata: {
        amount,
        walletId,
        currency,
        paymentMethod: `CARD`,
        updaterId // 把更新者ID存入metadata,方便Webhook使用
      }
    });

    // 移除这里的updateWalletById调用!
    // await updateWalletById(walletId, amount);

    return {
      status: SUCCESS,
      data: {
        clientSecret: paymentIntent.client_secret
      }
    };
  } catch (error) {
    logError(`ERROR @ initStripePaymentIntent -> stripe.services.js`, error);
    return {
      status: SERVER_ERROR,
      error: `An unhandled exception occurred on the server.`
    };
  }
};

module.exports = { initStripePaymentIntent };

2.2 新增Stripe Webhook路由,处理支付成功事件

在项目中新增Webhook处理路由(比如stripe.webhook.js):

const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_API_SECRET_KEY);
const { updateWalletBalance } = require('../api/controllers/wallet.controllers');
const { logError } = require('../dependencies/helpers/console.helpers');

const stripeWebhookRouter = express.Router();

// Stripe Webhook签名验证(必须配置,防止伪造请求)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

stripeWebhookRouter.post('/', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
  } catch (err) {
    logError(`Webhook signature verification failed: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // 处理支付成功事件
  if (event.type === 'payment_intent.succeeded') {
    const paymentIntent = event.data.object;
    const { walletId, amount, updaterId } = paymentIntent.metadata;
    
    // 转换金额:Stripe的amount是最小单位(比如分),需要转换为实际金额
    const amountToAdd = parseInt(amount) / 100;
    
    // 更新钱包余额
    await updateWalletBalance(walletId, amountToAdd, updaterId);
  }

  res.json({received: true});
});

module.exports = stripeWebhookRouter;

2.3 在主路由中注册Webhook

const stripeWebhookRouter = require('./path/to/stripe.webhook');
app.use('/stripe/webhook', stripeWebhookRouter);

步骤3:配置Stripe Dashboard的Webhook

  1. 登录Stripe Dashboard,进入【Developers】→【Webhooks】
  2. 点击【Add endpoint】,输入你的Webhook URL(比如https://your-domain.com/stripe/webhook)
  3. 选择要监听的事件:payment_intent.succeeded
  4. 复制生成的Webhook Secret,存入环境变量STRIPE_WEBHOOK_SECRET

额外注意事项
  • Stripe的金额单位:所有金额都是最小货币单位(比如美元用分,人民币用分),确保在转换时处理正确
  • Webhook必须使用HTTPS(本地开发可以用Stripe CLI转发请求)
  • 支付流程中,前端完成支付后,建议轮询后端接口确认钱包余额更新,避免Webhook延迟导致的用户体验问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 20:15:55