node 微信小程序支付及通知

1. 开通微信支付?

要在小程序里集成微信支付,小程序自身必须是通过微信认证的。从微信公众平台扫码进去,点击左侧菜单的微信认证
在这里插入图片描述
大家按照流程认证就好:

  • 认证的主体类型。
  • 认证费用,需要30元。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

1.2. 检查小程序 AppId

  • 要将.env里,改为认证通过的小程序AppId。
  • 此外,微信开发者工具里,点击右上角的详情,也要将这里的AppId改掉。
    在这里插入图片描述

1.3. 注册微信支付商户号

认证通过后,还需要额外注册微信支付商户号,才能实现支付。

请大家访问微信支付官网,右上角有个接入微信支付按钮,点进去:
在这里插入图片描述

1.4. 开通产品

注册成功后,回到微信支付官网。扫码登录后,点击产品中心。这里和支付宝一样,也需要开通对应的产品。在小程序里支付,需要开通的是JSAPI支付:
在这里插入图片描述

1.5. 开发配置

接着点击开发配置,这里可以在JSAPI支付里,添加线上接口地址。这样将来小程序上线了,才能正常支付。例如我这里填的是:https://api.clwy.cn`

在这里插入图片描述

1.6. 获取商户号

页面顶上有个商户号,这个值在对接微信支付的时候会用到。
在这里插入图片描述
打开.env,增加

WECHAT_MCH_ID=你的商户号

1.7.关联小程序

继续点击AppID账号管理,这里要关联自己的小程序,才能在小程序里支付。点击关联AppID

在这里插入图片描述

里面填通过认证的小程序AppID

在这里插入图片描述

1.8. 商户 APIv2 密钥

继续点击账户中心,左侧选择API 安全,在这里要添加密钥。

在这里插入图片描述
密钥现在是分了v2v3版本。v2密钥非常简单,自己设置一串32位的字符就好,里面支持数字和大小写字母。大家可以自己随便打一串,或者直接让AI生成都可以。

至于v3的,跟支付宝类似,需要下载微信的秘钥工具,再生成各种公钥私钥什么的,非常繁琐。为了简单,我们这里就直接用v2版本进行开发。

v2密钥设置好了以后,再打开.env文件,添加:

WECHAT_MCH_KEY=你的商户APIv2密钥

1.9. 微信支付通知地址

微信支付和支付宝一样,在支付成功后,也要设置一个通知地址,用来更新订单状态。.env里,继续加上:

WECHAT_NOTIFY_URL=你的微信

我设置为https://api.clwy.cn/wechat/notify,大家不要照着我的填,请填自己的线上接口地址。

地址最后的路径是notify,很显然,回头会在微信的路由文件里,新增一个notify路由。

1.10. 检查环境变量

WECHAT_APPID=微信小程序APPID
WECHAT_SECRET=微信小程序SECRET
WECHAT_MCH_ID=微信小程序支付商户号
WECHAT_MCH_KEY=商户APIv2密钥
WECHAT_NOTIFY_URL=微信支付通知地址

2. 集成微信支付

2.1. 安装 tenpay

npm i tenpay

2.2. 实例化微信支付

接着,就要将微信的环境变量用起来了,参考文档的实例化这一节。

在这里插入图片描述
utils目录,新增一个wechat.js文件,里面加上对应的配置:

const tenpay = require('tenpay');

const config = {
  appid: process.env.WECHAT_APPID, // 小程序 appid
  mchid: process.env.WECHAT_MCH_ID, // 微信商户号
  partnerKey: process.env.WECHAT_MCH_KEY, // 微信支付安全密钥
  notify_url: process.env.WECHAT_NOTIFY_URL // 支付通知地址
};

// 调用 tenpay,生成微信 JSSDK 支付参数
const wechatSdk = new tenpay(config, true);

module.exports = wechatSdk;

2.3. 实现微信支付

继续看文档的获取微信 JSSDK 支付参数这里:
在这里插入图片描述
打开routes/wechat.js文件,顶部先引用一下,刚才初始化的微信支付 SDK:

const wechatApi = require('../utils/wechat');

再增加一个路由,

/**
 * 微信支付
 * POST /wechat/pay
 */
router.post('/pay', userAuth, async function (req, res, next) {
  try {
    // 支付订单信息
    const order = await getOrder(req);
    const { outTradeNo, totalAmount, subject } = order;

    // 查询当前用户,因为发起支付,需要用户的 openid
    const user = await User.findByPk(req.userId);

    // 生成微信支付参数
    const result = await wechatApi.getPayParams({
      out_trade_no: outTradeNo,       // 商户内部订单号
      body: subject,                  // 商品简单描述
      total_fee: totalAmount * 100,   // 因为微信支付以「分」为单位,所以需要 * 100
      openid: user.openid             // 付款用户的 openid
    });

    // 返回给小程序
    success(res, '获取微信支付参数成功。', { result });
  } catch (error) {
    failure(res, error);
  }
});

  • 先查询一下要支付的订单。
  • 然后查一下当前用户。因为发起支付,必须要当前用户的openid
  • 接着就是调用getPayParams了,这块代码与文档的参数完全一样。
  • 但是要注意了,微信支付里的金额是以为单位。而数据库订单表里存的是以为单位。所以要乘以100,这才是
  • 最后就将生成的微信支付参数返回出去。

接着在下面,还要增加一个查询订单的公共方法:

/**
 * 公共方法:查询当前订单
 * @param req
 * @returns {Promise<*>}
 */
async function getOrder(req) {
  const { outTradeNo } = req.body;
  if (!outTradeNo) {
    throw new BadRequest('订单号不能为空。');
  }

  const order = await Order.findOne({
    where: {
      outTradeNo: outTradeNo,
      userId: req.userId,
    },
  });

  // 用户只能查看自己的订单
  if (!order) {
    throw new NotFound(`订单号: ${ outTradeNo } 的订单未找到。`);
  }

  if (order.status > 0) {
    throw new BadRequest('订单已支付或取消。');
  }

  return order;
}

3.微信支付通知

当微信支付成功后,微信服务器会主动发起回调请求,我们在代码里设置好的通知地址,告诉我们订单支付完成了

看到文档的 中间件・微信消息通知 这里

在这里插入图片描述

3.1. 解析 xml

里面说,要先写个app.use,用来解析xml格式的数据。这说明,微信通知发过来的数据格式就是xml

我们按照文档的说明,打开根目录的app.js文件,增加点内容:

const bodyParser = require('body-parser');

app.use(bodyParser.text({ type: '*/xml' }));

在这里插入图片描述

3.2 微信通知中间件

继续看文档:

  • 让我们在通知对应的路由里,增加个中间件
  • req.weixin里,得到的info,就是微信发送过来的数据。
  • 最下面还有个说明,使用res.replay。如果参数,表示回复微信服务器,已收到通知。这样微信就不会重复发出通知了。
  • 如果传其他信息,就表示出错了。
    照文档,打开routes/wechat.js文件,增加一个路由
/**
 * 微信支付通知
 * POST /wechat/notify
 */
router.post('/notify', wechatApi.middlewareForExpress('pay'), async function (req, res) {
  try {
    const info = req.weixin;

    logger.warn('微信支付成功:', info);
    res.reply(''); // 回复微信服务器,表示已收到通知
  } catch (error) {
    logger.warn('微信支付失败:', error);
    failure(res, error);
  }
});

打开数据库客户端,找到logs表,可以看到微信发过来的通知了。
点开meta字段,复制出来,在编辑器里随便建个info.json,粘贴进去,格式化一下:

{
  "service": "clwy-api",
  "appid": "wxf10e4d931aad21c9",
  "bank_type": "OTHERS",
  "cash_fee": "1",
  "fee_type": "CNY",
  "is_subscribe": "N",
  "mch_id": "1230390602",
  "nonce_str": "QKBbtYcLFQirsTT6",
  "openid": "oyDWp5VtDeHOrce1mRIStv-VN9Ag",
  "out_trade_no": "c063e7a6cd9b406e824912a9802bf63f",
  "result_code": "SUCCESS",
  "return_code": "SUCCESS",
  "sign": "C0D31206A6B60E3F2CA1A4071E9761A5",
  "time_end": "20250228105657",
  "total_fee": "1",
  "trade_type": "JSAPI",
  "transaction_id": "4200002560202502284847194037"
}

  • return_coderesult_code:显示的都是SUCCESS。显然这两个,可以用来判断支付状态
  • out_trade_no:是订单号
  • transaction_id:是流水号,对应我们数据库订单表的tradeNo字段。微信这里与支付宝里的命名是不同的,但都是一个意思。
  • time_end:就是支付的时间了。

3.3 完善支付通知

router.post('/notify', wechatApi.middlewareForExpress('pay'), async function (req, res) {
  try {
    const info = req.weixin;

    if (info.return_code === 'SUCCESS' && info.result_code === 'SUCCESS') {
      const {out_trade_no, transaction_id, time_end} = info;
      // 支付时间转成 DATETIME 所需格式
      const paidAt = moment(time_end, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss');

      await paidSuccess(out_trade_no, transaction_id, paidAt);
      res.reply(''); // 回复微信服务器,表示已收到通知
    } else {
      // 支付失败或其他情况处理逻辑
      res.reply('错误消息');
    }

  } catch (error) {
    logger.warn('微信支付失败:', error);
    failure(res, error);
  }
});

3.4 paidSuccess封装

/**
 * 支付成功后,更新订单状态和会员信息
 * @param outTradeNo
 * @param tradeNo
 * @param paidAt
 * @returns {Promise<void>}
 */
async function paidSuccess(outTradeNo, tradeNo, paidAt) {
  try {
    // 开启事务
    await sequelize.transaction(async (t) => {
      // 查询当前订单(在事务中)
      const order = await Order.findOne({
        where: { outTradeNo: outTradeNo },
        transaction: t,
        lock: true, // 增加排它锁
      });

      // 对于状态已更新的订单,直接返回。防止用户重复请求,重复增加大会员有效期
      if (order.status > 0) {
        return;
      }

      // 更新订单状态(在事务中)
      await order.update(
        {
          tradeNo: tradeNo, // 流水号
          status: 1, // 订单状态:已支付
          paymentMethod: 1 , // 支付方式:微信支付
          paidAt: paidAt, // 支付时间
        },
        { transaction: t }
      );

      // 查询订单对应的用户(在事务中)
      const user = await User.findByPk(order.userId, {
        transaction: t,
        lock: true, // 增加排它锁
      });

      // 将用户组设置为大会员。可防止管理员创建订单,并将用户组修改为大会员
      if (user.role === 0) {
        user.role = 1;
      }

      // 使用moment.js,增加大会员有效期
      user.membershipExpiredAt = moment(user.membershipExpiredAt || new Date())
        .add(order.membershipMonths, 'months')
        .toDate();

      // 保存用户信息(在事务中)
      await user.save({ transaction: t });
    });
  } catch (error) {
    // 将错误抛出,让上层处理
    throw error;
  }
}

4.前端代码

pages/orders/pay/index.js

async handlePay() {
  // 只有登录后,才能支付
  const userSignedIn = wx.getStorageSync('userSignedIn')
  if (!userSignedIn) {
    wx.showToast({
      title: '请先登录',
      icon: 'error',
    })
    return
  }

  // 先创建订单
  const orderInfo = await post('/orders', {
    membershipId: 1
  })
  // 获取到订单号
  const { outTradeNo } = orderInfo.data.order

  // 发起支付
  const payInfo = await post(`/wechat/pay`, { outTradeNo })
  const { result } = payInfo.data

  wx.requestPayment({
    timeStamp: result.timeStamp,
    nonceStr: result.nonceStr,
    package: result.package,
    signType: result.signType,
    paySign: result.paySign,
    success: (res) => {
      wx.showToast({
        title: '支付成功',
        icon: 'success',
      })
    },
    fail: (res) => {
      wx.showToast({
        title: '支付失败',
        icon: 'error',
      })
    }
  })
}

React Hooks 是 React 16.8 中新增的特性,它可以让你在函数组件中使用 state、生命周期钩子等 React 特性。使用 Hooks 可以让你写出更简洁、可复用且易于测试的代码。 React Hooks 提供了一系列的 Hook 函数,包括 useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect 和 useDebugValue。每个 Hook 都有特定的用途,可以帮助你处理不同的问题。 下面是 React Hooks 的一些常用 Hook 函数: 1. useState useState 是最常用的 Hook 之一,它可以让你在函数组件中使用 state。useState 接受一个初始状态值,并返回一个数组,数组的第一个值是当前 state 值,第二个值是更新 state 值的函数。 ``` const [count, setCount] = useState(0); ``` 2. useEffect useEffect 可以让你在组件渲染后执行一些副作用操作,比如订阅事件、异步请求数据等。useEffect 接受两个参数,第一个参数是一个回调函数,第二个参数是一个数组,用于控制 useEffect 的执行时机。 ``` useEffect(() => { // 这里可以执行副作用操作 }, [dependencies]); ``` 3. useContext useContext 可以让你在组件树中获取 context 的值。它接受一个 context 对象,并返回该 context 的当前值。 ``` const value = useContext(MyContext); ``` 4. useRef useRef 可以让你在组件之间共享一个可变的引用。它返回一个对象,该对象的 current 属性可以存储任何值,并在组件的生命周期中保持不变。 ``` const ref = useRef(initialValue); ref.current = value; ``` 5. useCallback useCallback 可以让你缓存一个函数,以避免在每次渲染时都创建一个新的函数实例。它接受一个回调函数和一个依赖数组,并返回一个 memoized 的回调函数。 ``` const memoizedCallback = useCallback(() => { // 这里是回调函数的逻辑 }, [dependencies]); ``` 6. useMemo useMemo 可以让你缓存一个计算结果,以避免在每次渲染时都重新计算。它接受一个计算函数和一个依赖数组,并返回一个 memoized 的计算结果。 ``` const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` 以上就是 React Hooks 的一些常用 Hook 函数,它们可以帮助你更好地处理组件状态、副作用、上下文和性能优化等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值