微信支付-业务流程图+时序图梳理微信支付链路+封装对接微信API工具类

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 微信支付-业务流程图+时序图梳理微信支付链路+封装对接微信API工具类

微信支付-业务流程图+时序图梳理微信支付链路+封装对接微信API工具类

微信支付模块超时关单业务流程图

image.png

通过时序图剖析微信支付子系统链路


image.png

pay.weixin.qq.com/wiki/doc/ap…

重点步骤说明:

步骤2用户确认支付后,商户调用微信支付Native下单API生成预支付交易以获取支付二维码链接code_url;

商户调用NativeNative下单API后,分正常返回和异常返回情况:

  • 正常返回:返回code_url,商户可根据返回的code_url来生成调用OpenSDK的签名以执行下一步。
  • 异常返回:返回http code或错误码,商户可根据http code列表错误码说明来排查原因并执行下一步操作

步骤4: 商户根据返回的code_url生成二维码供用户扫描,有关二维码的规则请参考3.2.2部分的说明

步骤9-11: 用户支付成功后,商户可通过以下两种方式获取订单状态

方法一: 支付结果通知。用户支付成功后,微信支付会将支付成功的结果以回调通知的形式同步给商户,商户的回调地址需要在调用Native下单API时传入notify_url参数。

方法二: 当因网络抖动或本身notify_url存在问题等原因,导致无法接收到回调通知时,商户也可主动调用查询订单API来获取订单状态

一、支付二维码-请求微信服务器获取支付二维码签名验证封装

签名生成

签名验证封装整体流程:

pay.weixin.qq.com/wiki/doc/ap…

准备工作

插件安装

yarn add jsrsasign@10.6.1 string-random@0.1.3 urllib@3.5.1
jsrsasign:js加密插件
string-random:随机生成字符串插件
urllib:接口请求插件

封装对接微信API工具类

WxPayment.js

1. construtor获取基础配置数据
class WxPayment {
    constructor({ appid, mchid, private_key, serial_no, apiv3_private_key, notify_url } = {}) {
        this.appid = appid; // 公众号appid
        this.mchid = mchid; // 商户号mchid 
        this.private_key = private_key; // 商户私钥 加密传输的数据给微信平台
        this.serial_no = serial_no; // 证书序列号,用于声明所使用的证书。 根据序列号微信平台用于选择对应的商户公钥
        this.apiv3_private_key = apiv3_private_key; // APIv3密钥,解密平台证书 拿到平台公钥
        this.notify_url = notify_url; // 回调地址 支付成功后的回调通知url
        this.requestUrls = {
            // pc端native下单API
            native: () => {
                return {
                    url: 'https://api.mch.weixin.qq.com/v3/pay/transactions/native',
                    method: 'POST',
                    pathname: '/v3/pay/transactions/native',
                }
            },
        }
    }
}

requestUrls是发起下单的请求链接具体看这里:Native下单API

pay.weixin.qq.com/wiki/doc/ap…

2.构造签名串

构造模板

GET\n  
/v3/certificates\n  
1554208460\n  
593BEC0C930BF1AFEB40B4A08C8FB242\n  
\n
HTTP请求方法\n 
URL\n 
请求时间戳\n
请求随机串\n 
请求报文主体\n
代码对应:
请求方法:method
请求路径:pathname
时间戳:timestamp
随机数:onece_str
请求报文主体:bodyParamsStr
// RandomTool.js 
const randomString = require('string-random')
static randomString(num) {
  return randomString(num)
}
const RandomTool = require('./RandomTool')
class WxPayment {
    constructor(...) {...}
    // 请求微信服务器签名封装
    async wxSignRequest({ pathParams, bodyParams, type }) {
    1. 构造签名串
        let { url, method, pathname } = this.requestUrls[type]({ pathParams })
        let timestamp = Math.floor(Date.now() / 1000) // 时间戳
        let onece_str = RandomTool.randomString(32);  // 随机串 
        let bodyParamsStr = bodyParams && Object.keys(bodyParams).length ? JSON.stringify(bodyParams) : '' // 请求报文主体
        let sign = `${method}\n${pathname}\n${timestamp}\n${onece_str}\n${bodyParamsStr}\n`
    }
}
module.exports = WxPayment;
3.计算签名值

绝大多数编程语言提供的签名函数支持对签名数据进行签名。强烈建议商户调用该类函数,使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。

const { KJUR, hextob64 } = require('jsrsasign')
const RandomTool = require('./RandomTool')
class WxPayment {
    constructor(...) {
   ...
    }
    // 请求微信服务器签名封装
    async wxSignRequest({ pathParams, bodyParams, type }) {
        let { url, method, pathname } = this.requestUrls[type]({ pathParams })
        let timestamp = Math.floor(Date.now() / 1000) // 时间戳
        let onece_str = RandomTool.randomString(32);  // 随机串 
        let bodyParamsStr = bodyParams && Object.keys(bodyParams).length ? JSON.stringify(bodyParams) : '' // 请求报文主体
        let signature = this.rsaSign(`${method}\n${pathname}\n${timestamp}\n${onece_str}\n${bodyParamsStr}\n`, this.private_key, 'SHA256withRSA')
        // 请求头传递签名
        let Authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this.mchid}",nonce_str="${onece_str}",timestamp="${timestamp}",signature="${signature}",serial_no="${this.serial_no}"`
    }
    /**
     * rsa签名
     * @param content 签名内容
     * @param privateKey 私钥,PKCS#1
     * @param hash hash算法,SHA256withRSA
     * @returns 返回签名字符串,base64
     */
    rsaSign(content, privateKey, hash = 'SHA256withRSA') {
        // 创建 Signature 对象
        const signature = new KJUR.crypto.Signature({
            alg: hash,
            // 私钥
            prvkeypem: privateKey
        })
        // 传入待加密字符串
        signature.updateString(content)
        // 生成密文
        const signData = signature.sign()
        // 将内容转成base64
        return hextob64(signData)
    }
}
module.exports = WxPayment;

这里的具体步骤就是

  1. 首先创建一个 Signature 对象,该对象使用 KJUR.crypto.Signature() 方法创建,参数包括要使用的 Hash 算法(默认为 SHA256withRSA)和私钥 privateKey,这里 privateKey 参数应该是已经经过 PKCS#1 编码的字符串。创建成功后,可用于对内容进行签名。
  2. signature.updateString(content) 方法用于传入待签名的字符串 content,将其作为签名输入内容。
  3. signature.sign() 方法将执行签名操作,并返回签名结果。
  4. 需要将签名结果转换成 Base64 编码格式,使用 hextob64() 方法完成,然后将结果返回即可。
4. 设置HTTP头

image.png

  // 请求头传递签名
let Authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this.mchid}",nonce_str="${onece_str}",timestamp="${timestamp}",signature="${signature}",serial_no="${this.serial_no}"`
5.发起请求
// 接口请求
let { status, data } = await urllib.request(url, {
    method: method,
    dataType: 'text',
    data: method == 'GET' ? '' : bodyParams,
    timeout: [10000, 15000],
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': Authorization // 自定义生成
    },
})
return { status, data }

在封装对外暴露的同一接口

    //native统一下单
    async native(params) {
        let bodyParams = {
            ...params,
            appid: this.appid,
            mchid: this.mchid,
            notify_url: this.notify_url,
        }
        return await this.wxSignRequest({ bodyParams, type: 'native' })
    }

在其他地方使用下单时都是统一使用navtive接口。

全部代码

j

const urllib = require('urllib');
const { KJUR, hextob64 } = require('jsrsasign')
const RandomTool = require('./RandomTool')
class WxPayment {
    constructor({ appid, mchid, private_key, serial_no, apiv3_private_key, notify_url } = {}) {
        this.appid = appid; // 公众号appid
        this.mchid = mchid; // 商户号mchid
        this.private_key = private_key; // 商户私钥
        this.serial_no = serial_no; // 证书序列号,用于声明所使用的证书
        this.apiv3_private_key = apiv3_private_key; // APIv3密钥,解密平台证书
        this.notify_url = notify_url; // 回调地址
        this.requestUrls = {
            // pc端native下单API
            native: () => {
                return {
                    url: 'https://api.mch.weixin.qq.com/v3/pay/transactions/native',
                    method: 'POST',
                    pathname: '/v3/pay/transactions/native',
                }
            },
        }
    }
    // 请求微信服务器签名封装
    async wxSignRequest({ pathParams, bodyParams, type }) {
        let { url, method, pathname } = this.requestUrls[type]({ pathParams })
        let timestamp = Math.floor(Date.now() / 1000) // 时间戳
        let onece_str = RandomTool.randomString(32);  // 随机串 
        let bodyParamsStr = bodyParams && Object.keys(bodyParams).length ? JSON.stringify(bodyParams) : '' // 请求报文主体
        let signature = this.rsaSign(`${method}\n${pathname}\n${timestamp}\n${onece_str}\n${bodyParamsStr}\n`, this.private_key, 'SHA256withRSA')
        // 请求头传递签名
        let Authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this.mchid}",nonce_str="${onece_str}",timestamp="${timestamp}",signature="${signature}",serial_no="${this.serial_no}"`
        // 接口请求
        let { status, data } = await urllib.request(url, {
            method: method,
            dataType: 'text',
            data: method == 'GET' ? '' : bodyParams,
            timeout: [10000, 15000],
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'Authorization': Authorization
            },
        })
        return { status, data }
    }
    //native统一下单
    async native(params) {
        let bodyParams = {
            ...params,
            appid: this.appid,
            mchid: this.mchid,
            notify_url: this.notify_url,
        }
        return await this.wxSignRequest({ bodyParams, type: 'native' })
    }
    /**
     * rsa签名
     * @param content 签名内容
     * @param privateKey 私钥,PKCS#1
     * @param hash hash算法,SHA256withRSA
     * @returns 返回签名字符串,base64
     */
    rsaSign(content, privateKey, hash = 'SHA256withRSA') {
        // 创建 Signature 对象
        const signature = new KJUR.crypto.Signature({
            alg: hash,
            // 私钥
            prvkeypem: privateKey
        })
        // 传入待加密字符串
        signature.updateString(content)
        // 生成密文
        const signData = signature.sign()
        // 将内容转成base64
        return hextob64(signData)
    }
}
module.exports = WxPayment;

验证请求微信服务器签名封装

基于上文封装的请求下单的工具进一步的配置支付,将工具对象实例化。

wechatPay.js

const Payment = require('../utils/WxPayment')
const fs = require('fs')
const { resolve } = require('path')
const appid = 'xxxxxxxxx' // 公众号appID
const mchid = '11111111'  // 商户号mchID
const serial_no = 'xxxx' // 证书序列号,用于声明所使用的证书
const apiv3_private_key = 'xxxx' // APIv3密钥,用于解密平台证书,拿到平台公钥,解密回调返回的信息。
const notify_url = 'https://740833px45.yicp.fun/api/order/v1/callback' // 回调地址,用户微信通知消息 ,这里使用花生壳内网穿透,处理本地的http://127.0.0.1:8081。
const private_key = fs.readFileSync(resolve(__dirname, '../apiclient_key.pem')).toString() // 秘钥,用于发起微信请求加密
const payment = new Payment({
    appid, mchid, private_key, serial_no, apiv3_private_key, notify_url
})
module.exports = { appid, mchid, private_key, serial_no, apiv3_private_key, notify_url, payment }

解读一下上面的逻辑:

image.png

image.png

  1. 定义了常量变量,包括公众号 appid、商户号 mchid、证书序列号 serial_no、APIv3 密钥 apiv3_private_key、回调地址 notify_url 和秘钥 private_key。为了进行对象实例化。
  2. 这里 private_key的获得是使用 fs.readFileSync() 方法读取了一个指定路径下的 PEM 格式私钥文件,该私钥用于发起微信支付请求时进行加密。
  3. 将这些变量创建WxPayment 实例对象,并通过 module.exports 导出。

实例化对象之后书写对应的接口请求。

router

const express = require('express')
const router = express.Router()
const OrderController = require('../controller/OrderController.js')
// 获取微信支付二维码
router.post('/pay', OrderController.pay)
module.exports = router

controller

/**
 * @param query_pay 查询课程是否购买接口
 * @param latest 查询课程最近购买动态接口
 * @param pay PC微信支付二维码 
 * @param callback 微信回调
 * @param query_state 轮询用户扫码与否
 */
const OrderService = require('../service/OrderService.js')
const OrderController = {
    pay: async (req, res) => {
        let handleRes = await OrderService.pay(req)
        res.send(handleRes);
    },
}
module.exports = OrderController

service

const DB = require('../config/sequelize')
const BackCode = require('../utils/BackCode')
const CodeEnum = require('../utils/CodeEnum')
const RandomTool = require('../utils/RandomTool')
const SecretTool = require('../utils/SecretTool')
const GetUserInfoTool = require('../utils/GetUserInfoTool')
const { payment } = require('../config/wechatPay')
const dayjs = require('dayjs')
const redisConfig = require('../config/redisConfig')
const OrderService = {
  pay: async (req) => {
    let { id, type } = req.body
    let token = req.headers.authorization.split(' ').pop()
    // 获取用户信息
    let userInfo = SecretTool.jwtVerify(token)
    // 用户的ip
    let ip = GetUserInfoTool.getIp(req)
    // 生成32位字符串 商户订单号
    let out_trade_no = RandomTool.randomString(32)
    // 根据商品的ID查询商品价格
    let productInfo = await DB.Product.findOne({ where: { id }, raw: true })
    // 拼装用户和商品信息插入数据库
    let userPro = {
      account_id: userInfo.id,
      username: userInfo.username,
      user_head_img: userInfo.head_img,
      out_trade_no: out_trade_no,
      total_amount: productInfo.amount,
      pay_amount: productInfo.amount,
      product_id: productInfo.id,
      product_type: productInfo.product_type,
      product_title: productInfo.title,
      product_img: productInfo.cover_img,
      order_state: 'NEW',
      ip: ip
    }
    // 新订单信息插入数据库
    await DB.ProductOrder.create(userPro)
    // 微信支付二维码
    if (type === 'PC') {
      let result = await payment.native({
        description: '测试',
        out_trade_no,  // 正式
        amount: {
           total: Number(productInfo.amount) * 100,  // 正式
        }
      })
      return BackCode.buildSuccessAndData({ data: { code_url: JSON.parse(result.data).code_url, out_trade_no } })
    }
  },
}
module.exports = OrderService

解读一下Service的逻辑:

这里使用了很多封装的工具,简单理解调用思路就可以了。

引用模块:

引用了一些自定义模块和第三方模块,包括数据库模块 DB、返回码模块 BackCode、状态码枚举模块 CodeEnum、随机字符串工具模块 RandomTool、加密工具模块 SecretTool、获取用户信息工具模块 GetUserInfoTool、微信支付配置模块 wechatPay、日期时间工具模块 dayjs、Redis 配置模块 redisConfig

pay 的异步函数,从请求体中获取订单号 id 和支付类型 type。通过请求头中的 Authorization 字段解析出 JWT Token 并获取用户信息 userInfo。获取客户端的 IP 地址 ip。利用随机字符串工具生成32位的商户订单号 out_trade_no。查询商品信息并获取价格 productInfo。然后将组装用户与商品相关信息,将其插入数据库中。判断支付类型,如果支付类型为 PC,则调用 payment.native() 方法生成微信支付二维码,并返回二维码图片URL code_url 和商户订单号 out_trade_no,否则直接返回空值。

在生成的url拿到之后使用二维码转换器就可以生成二维码了。


image.pngimage.png

image.png

目录
相关文章
|
3月前
|
开发框架 缓存 前端开发
基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求
基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求
|
4天前
|
JSON API 数据格式
淘宝 / 天猫官方商品 / 订单订单 API 接口丨商品上传接口对接步骤
要对接淘宝/天猫官方商品或订单API,需先注册淘宝开放平台账号,创建应用获取App Key和App Secret。之后,详细阅读API文档,了解接口功能及权限要求,编写认证、构建请求、发送请求和处理响应的代码。最后,在沙箱环境中测试与调试,确保API调用的正确性和稳定性。
|
26天前
|
监控 安全 测试技术
如何确保API对接过程中的数据安全?
确保API对接过程中的数据安全至关重要。最佳实践包括:使用HTTPS协议、强化身份验证和授权、数据加密、输入验证、访问控制、限流限速、日志记录和监控、安全测试、数据脱敏、错误处理、API网关、Web应用程序防火墙(WAF)、审计和合规性。这些措施能有效提升API的安全性,保护数据免受恶意攻击和泄露风险。
|
2月前
|
程序员 数据库 UED
微信也在用的消息时序性技术,你知道多少?
本文由程序员小米撰写,探讨了在个人项目中如何保证消息的时序性。文章详细介绍了消息时序性的概念及其重要性,并提出了三种方案:ID设计(借鉴微信号段与跳跃式生成)、单聊场景下的单点序列化同步,以及群聊场景中的单点序列化处理。此外,还提供了多种优化方法,如消息时序对齐、本地时序记录等,帮助读者更好地解决消息乱序问题。适合所有关心即时通讯和社交应用技术细节的开发者阅读。
50 4
|
1月前
|
API 定位技术
api接口如何对接?(带你了解api接口的相关知识)
API接口是在产品和研发领域广泛应用的专业术语,主要用于公司内部系统衔接及公司间合作。本文将详细讲解API接口的概念、必要性及其核心要素。首先介绍API接口的基本原理与应用场景,随后阐述其重要性,最后解析API接口的核心组成部分,帮助读者深入理解API接口的工作机制。适合产品小白和求职者阅读,提升专业知识。
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
94 11
|
2月前
|
小程序 前端开发 API
微信小程序 - 调用微信 API 回调函数内拿不到 this 问题(解决方案)
本文讨论了在微信小程序中调用API回调函数时无法获取到`this`上下文的问题,并提供了解决方案。在回调函数中,使用一个变量(如`that`)来保存当前的`this`引用,然后在回调内部使用这个变量来访问当前页面的数据和方法。
|
3月前
|
小程序 数据安全/隐私保护
Taro@3.x+Vue@3.x+TS开发微信小程序,网络请求封装
在 `src/http` 目录下创建 `request.ts` 文件,并配置 Taro 的网络请求方法 `Taro.request`,支持多种 HTTP 方法并处理数据加密。
120 0
Taro@3.x+Vue@3.x+TS开发微信小程序,网络请求封装
|
3月前
|
小程序 前端开发 JavaScript
微信小程序实现微信支付(代码和注释很详细)
微信小程序实现微信支付(代码和注释很详细)
|
4月前
|
文字识别 小程序 安全
印刷文字识别操作报错合集之微信小程序调用API时路径总是返回不对,该如何处理
在使用印刷文字识别(OCR)服务时,可能会遇到各种错误。例如:1.Java异常、2.配置文件错误、3.服务未开通、4.HTTP错误码、5.权限问题(403 Forbidden)、6.调用拒绝(Refused)、7.智能纠错问题、8.图片质量或格式问题,以下是一些常见错误及其可能的原因和解决方案的合集。

热门文章

最新文章