Hapi 框架实现微信支付,遇到的 Bug 及处理方式

在前端开发中,支付功能是必不可少的一部分。而微信支付作为移动支付中的佼佼者,它的快速、安全和方便性也让它成为了具有广泛应用场景的支付方式之一。本文将详细介绍如何使用 Hapi 框架实现微信支付功能,并分享在开发过程中遇到的 Bug 及处理方式,帮助读者更好地掌握 Hapi 和微信支付的实现技巧。

环境搭建

在开始开发之前,我们需要准备好以下环境:

  • 一个微信公众号(或小程序)的 appId 和 secretKey
  • Node.js 开发环境
  • Hapi 框架

我们先介绍一下如何申请微信支付需要的信息。在微信支付开发者文档中,选择"申请开发者权限",依次填写相关信息如商户账号、邮箱等,提交后会收到审核通过的邮件,并获得自己的 appId 和 secretKey。此时我们已经可以开始在 Hapi 框架中实现微信支付功能。

功能实现

统一下单接口

微信支付流程的第一步是通过微信支付接口向微信后台发起请求,生成预付款订单,之后微信会返回响应结果并附带了 prepay_id(预付款订单号),接口定义如下:

// 统一下单
async function unifiedOrder(productId, ipAddress, openid, amount) {
  let nonceStr = getNonceStr() // 微信签名所需的随机字符串
  let timeStamp = getTimeStamp() // 时间戳
  let outTradeNo = `${getOutTradeNo()}${productId}` // 商户订单号

  // 构建请求体
  let data = {
    appid: process.env.APPID,
    mch_id: process.env.MCH_ID,
    nonce_str: nonceStr,
    body: '购买商品', // 商品描述
    out_trade_no: outTradeNo, // 商户订单号
    total_fee: amount, // 订单总金额,单位为分
    spbill_create_ip: ipAddress, // 用户IP
    notify_url: `${process.env.PAY_NOTIFY_SERVER}/api/pay/notify`, // 支付成功后的回调地址
    trade_type: 'JSAPI', // 微信小程序支付类型
    openid: openid // 用户 openid
  }

  // 统一下单接口签名,将参数和签名并上传至微信支付接口
  let sign = signData(data)
  data.sign = sign
  let result = await axios.post(`${payBaseUrl}/unifiedorder`, json2xml(data))

  // 解析结果
  let prepayId = result.data.prepay_id
  // ……
}

其中,重点需要注意的是在调用微信支付前需要对请求参数进行签名处理。签名算法有比较严格的要求,具体可以参考微信支付开发者文档中的操作方法,或者在第三方的微信支付 SDK 中查找算法。

订单查询接口

订单查询接口是用于查询订单最新状态,但需要注意的是,退款也属于一种 order_status 的状态,查询到“order_status”为“REFUND”并不意味着该订单已完成支付,接口定义如下:

// 订单查询
async function orderQuery(outTradeNo) {
  let nonceStr = getNonceStr() // 微信签名所需的随机字符串
  let timeStamp = getTimeStamp() // 时间戳

  // 构建请求体
  let data = {
    appid: process.env.APPID,
    mch_id: process.env.MCH_ID,
    nonce_str: nonceStr,
    out_trade_no: orderNo // 商户订单号
  }

  // 订单查询接口签名,将参数和签名并上传至微信支付接口
  let sign = signData(data)
  data.sign = sign
  let result = await axios.post(`${payBaseUrl}/orderquery`, json2xml(data))

  // 解析结果
  let resultCode = result.data.result_code
  let tradeState = result.data.trade_state // 交易状态
  let totalFee = result.data.total_fee // 订单总金额
  // ……
}

定义微信支付回调

微信支付支付成功后将异步回调商户服务器,通知支付结果。所以我们需要在服务器端定义回调逻辑。在 Hapi 框架中,可以使用 server.route() 函数创建一个 POST 请求处理路由,接收微信支付回调的 XML 报文,并对其进行验签和业务处理,示例代码如下:

// 定义微信支付回调
server.route({
  method: 'POST',
  path: '/api/pay/notify',
  handler: async function (request, reply) {
    try {
      let data = await parseXML(request.payload) // 解析微信支付回调请求体 XML 格式

      if (validateSign(data)) { // 验证请求参数签名是否正确
        let { out_trade_no, result_code } = data.xml
        let tradeState = data.xml.trade_state // 交易状态

        if (result_code === 'SUCCESS' && tradeState === 'SUCCESS') { // 验证业务状态是否正确
          console.log(`微信支付成功,商户订单号:${out_trade_no}`)

          // 对于一个商户订单号只能调用一次回调,在业务处理成功后,需要将订单状态以及数据保存入库
          // ……
        }
      }
    } catch (e) {
      console.log(e)
    }

    reply('success') // 要及时返回处理结果,让微信支付知道该订单已处理完毕
  }
})

遗留 Bug 及解决方案

在实际开发过程中,我们也会遇到一些问题,例如开发中出现提示“ERR_SSL_VERSION_OR_CIPHER_MISMATCH”,这是因为微信支付接口采用了 TLS1.2 协议,所以需要升级 Node.js 版本以支持该协议。同时,如果时间戳配置不正确可能会导致签名校验失败,所以需要开启 NTP 服务,并将时间同步为北京时间。

此外,还有一种比较常见的问题:对于一些特殊字符(如“+”、“&”等),微信支付会对其进行 URL 编码,而如果在验签的过程中没有进行 URL 解码,则会导致签名验证失败。所以,在验证签名之前需要将 XML 报文中的字符数据进行 URL 解码。

总结

本文详细介绍了如何使用 Hapi 框架实现微信支付功能,并分享了在开发过程中遇到的 Bug 及处理方式,希望能够帮助读者更好地掌握 Hapi 和微信支付的实现技巧。我们需要注意签名算法的处理、订单状态查询的注意事项,在回调业务处理成功之后,需要及时保存订单状态及相关数据到数据库。

在实际开发中,如果我们需要进行更加复杂的逻辑处理,例如多个订单的批量处理等,可以选择使用队列、定时器等技术方案。本文提供的示例代码可以作为一个参考,并在实际开发中加以调整和优化,以适应不同场景的需求。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a77cdcadd4f0e0ff09cc47


纠错反馈