微信支付

Stone大约 13 分钟

微信支付

准备

身份

小程序接入微信支付,不支持个人接入,需要先申请成为个体工商户或者企业。

申请成为个体工商户,要么有线下经营场所,要么有网店。建议先在拼多多开通网店,然后基于该网店申请个体工商户。

小程序

认证

在小程序官网的【首页/微信认证】中进行认证,注意认证的主体不要选个人,因为个人无法使用支付功能。认证费用 30 元。

如果你之前已经认证成了“个人”,可以如下的相关方法去变更主体:

  1. 想使用微信支付,则小程序的主体不能是“个人”,如果你之前已认证成了“个人”,可以在这里申请变更主体。

  2. 如果你认证的主体已经是“企业”或“个体工商户”则不需要操作此步。

  3. 变更主体需要的资料有:

    • 原主体(即个人)的身份证正反面照片。由于只能上传一个文件,请提前将正反面拼接成一张图片。

    • 新主体(即企业)的营业执照电子档。

    • 在你申请的页面下载《变更申请函》,是一个 Word 文档,下载后先填写内容,再打印出来。在“个人”的地方签名、按手印(记得有两个签名和两个手印),在“企业”的地方盖公章。

    • 填写时需要的“原小程序原始ID”请在【账号设置/账号信息/原始ID】处查看。

备案

小程序备案时主要填写的内容有:

  1. 主办人信息、主体信息、小程序管理员信息、小程序信息等;
  2. 在当页下载《互联网信息服务承诺书》进行打印,签名按手印后上传。

小程序备案完成后得到:

  1. 小程序 ID,也叫 AppId 或应用 ID,相当于小程序的身份证号。
  2. 小程序密钥,也叫 AppSecret。查看后请及时找安全的地方记录一下,如果忘记,只能重置。

微信支付

注册微信支付商户号

接入微信支付需要注册微信支付商户号,在微信支付open in new window主页,点击“接入微信支付”,注册微信支付商户号。

最终得到微信支付商户号。

开通产品

然后开通需要的产品。微信商户平台的产品有很多,在这里开通自己需要的产品即可:

  • JSAPI 支付:小程序内使用较多,用户点支付就进入微信官方统一的支付页面,输出密码或按指纹即可。
  • Native 支付:即生成指定金额的收款二维码,用户扫这个码就是付对应金额的钱。
  • 扫付款码:就像你在街边买早餐时一样,所有客户都是扫同一个二维码,金额自己输入。

在此先开通 JSAPI 支付和 Native 支付。

开发配置

需要在 【产品中心】- 【开发配置】中配置一个服务器域名,这个域名必须是外网能访问到的,因为微信会将支付结果通知到这个域名下的某个接口,例如 /callback

注意:这里只需要写域名即可,不需要写接口名,例如:

这里写域名:https://www.xxxx.com/

回调地址写:https://www.xxxx.com/callback(注意,回调地址是在代码中配置的,详情请看看代码部分)

关联小程序

小程序与商户号, 是两个不同的东西, 需要在这里让商户号对小程序发起关联 (小程序那边也需要对商户号进行关联)。在 【产品中心】- 【AppID账号管理】关联小程序的 AppID。

然后在小程序中的 【微信支付】页面接受。

API安全

验证商户身份

验证商户身份有“商户API证书”和“商户APIv2密钥”两种方式。APIv2是较早的支付方式,现在不建议使用。在此我们使用“商户API证书”。

点击 【验证商户身份】-【商户API证书】-【管理证书】进入API证书管理页面:

点击【申请新证书】:

在【生成API证书】对话框,点击【下载证书工具】,记下商户号和商户名称:

安装证书工具:

启动证书工具,选择证书保存路径:

填写商户号和商户名称:

从证书工具中复制证书请求串,粘贴到【生成API证书】对话框中,然后点击“下一步”得到证书串:

将得到的证书串拷贝到证书工具中获取证书文件:

在证书保存路径解压证书包,得到 apiclient_key.pem 文件。

在商户平台上查看刚才申请的证书的系列号,建议用文档保存在安全的地方,以免每次都要来查。

验证微信支付身份

使用微信支付公钥验证微信支付身份:

点击【管理公钥】进入微信支付公钥页面,保存”公钥ID“并下载公钥:

公钥文件名称为 pub_key.pem

解密回调

使用APIv3密钥进行解密回调:

总结

需要准备以下:

  1. AppId:小程序官网/管理/开发管理/AppId
  2. AppSecret:小程序官网/管理/开发管理/AppSecret
  3. 商户号:商户平台官网/帐户中心/商户信息/商户号
  4. 商户API证书系列号:商户平台官网/帐户中心/API安全/商户API证书/证书系列号
  5. 商户API私钥:在商户API证书文件 apiclient_key.pem 中,有这个文件即可
  6. 微信支付公钥的ID:商户平台官网/帐户中心/API安全/微信支付公钥/微信支付公钥的ID。例:PUB_KEY_ID_xxx
  7. 微信支付公钥的本身:对 pub_key.pem 文件解码及解密,得到 PublicKey 类型的对象作为微信支付公钥
  8. APIv3密钥:商户平台官网/帐户中心/API安全/APIv3密钥

后端

密码学基础

对称加密与非对称加密

  • 对称加密
    • 加密和解密都使用相同的密钥,意味着双方人员都需要知道密钥。
    • 优点:加密和解密速度都很快。
    • 缺点:分发密钥时容易泄露。
    • 常见的算法有:AES(高级加密标准),DES(数据加密标准),3DES(三重数据加密算法)
    • 微信支付中用的是AES-256-GCM进行对称加密数据的。(备注:例如使用api-v3这个秘钥来加密和解密回调时的订单信息)
  • 非对称加密
    • 一个公钥,一个私钥,这二个密钥在数学上相关,但是不能通过一个算出另一个。
    • 公钥可以公共分享,一般用于加密。私钥是保密的,只有持有者才知道,一般用于解密。
    • 需要说明的是:公钥也可以解密私钥加密的内容(例如验证签名时),但是公钥不能解自己加密的内容。
    • 优点:安全性高,因为不需要通过网络传输密钥。缺点:速度比较慢,不适合加密解密大量的数据。
    • 常见的算法有:RSA,ECC。
  • 非对称加密的常见用途
    • 加密信息:使用公钥加密,使用私钥解密。(备注:虽然也可以用私钥加密+公钥解密,但是一般只用到验证签名上)
    • 身份认证:即验证签名。使用私钥生成签名。接收方使用公钥解密签名,验证签名是否正确(具体步骤请见《签名与验签》)

摘要算法

  • 特点

    • 摘要算法也称为散列函数。它可以将任意长度的数据转换成为固定长度的值,这个值称为散列值或摘要。
    • 它具有特定性,也就是只要给定的值相同,则摘要值肯定相同。
    • 它具有唯一性,也就是只要给定的值不同,则摘要值肯定不同。
    • 它具有高效性,也就是计算的速度非常快。
    • 它是不可逆的,也就是只能通过原文得到摘要,不能通过摘要得到原文。
  • 用途

    • 可用于验证数据的完整性,例如我收到你的数据后,我们都行进摘要,如同不同,说明我收到的数据不完整。
    • 可用于安全存放密码,数据库存放的是密码的摘要,登录时,将用户输入的密码进行摘要然后与库中对比。
    • 可用于数字签名,将原文摘要后使用私钥加密,收件方使用公钥解密后,与原文的摘要进行对比。
  • 常见的摘要算法

    • MD5 128位
    • SHA-1 160位
    • SHA-256 256位,微信支付中使用的就是SHA-256

签名与验签

  • 目标
    • 需要传递的正文是“刘景景世界最帅”
  • 签名
    • 密文:私钥持有者将原文“刘景景世界最帅”使用对称加密的Apiv3密钥加密得到“asdfghjkl!@#$%^&*()_+”,这个称为密文。
    • 签名:私钥持有者将原文“刘景景世界最帅”进行摘要得到“景帅”,这个称为签名。
    • 私钥持有者将密文“asdfghjkl!@#$%^&*()_+”和签名“qwertyu1op”发送给公钥持有者。
    • 简洁:【原文】+使用Apiv3加密=【密文】
    • 简洁:【原文】+摘要算法=【摘要】,【摘要】+私钥加密=【签名】
    • 汇总:
      • 原文:刘景景世界最帅
      • 密文:asdfghjkl!@#$%^&*()_+
      • 摘要:景帅
      • 签名:qwertyu1op
  • 验签
    • 收件人收到密文“asdfghjkl!@#$%^&*()_+”和签名“qwertyu1op”
    • 使用秘钥解密正文:使用对称加密的Apiv3密钥对密文“asdfghjkl!@#$%^&*()_+”解密,得到“刘景景世界最帅”或“刘景景世界第二帅”
    • 摘要:使用摘要算法对正文“刘景景世界第二帅”进行摘要,得到“景帅”或“景二”
    • 使用公钥解密签名:使用公钥对签名“qwertyu1op”进行解密,得到摘要“景帅”。公钥谁都有,也就是谁都可以看摘要,不影响安全。
    • 对比:如果c的摘要是“景帅”,即与签名相同,则验签通过;如果c的摘要是“景二”,即与签名不同,则验签不通过。
    • 备注:也就是,对比的是【使用对称密钥解密后的正文的摘要】与【使用公钥解密后的签名(也就是摘要)】
  • 配合
    • 为了保证安全和高效,一般会将对称加密和非对称加密配合使用。
    • 先使用非对称加密完成“对称加密的密钥”的传递,因为它有签名和验签技术,很安全(不需要高效,因为内容不多)
    • 再使用对称加密来完成消息正文的加密和解密,因为它很高效。

JSAPI 支付

支付流程

支付需要分两个请求来完成:

  • 第一个请求:预支付(承诺)
  • 第二个请求:支付(兑现)

实际中,在小程序端编写代码,拿到第一次的响应后再发起第二次请求,用户无感。

第 1 步:用户小程序向商家服务器发起预支付请求

  • 这个请求是从用户小程序发给商家服务器,表达我要向你付款的意思
  • 这个请求会有一些参数,例如商品名称、商品价格、付款人(即付款者的openid) 等
  • 由于这个时候还没有拿到微信服务器返回的必要参数,并没有付款,所以称为“预支付”
  • 请求url是商家自己的服务器,例如:system/pay/createOrder

第 2 步:商家服务器向微信服务器发起预支付请求

  • 商家服务器收到请求后,向微信官方服务器发送请求,表达说:有人准备向我的账户付款了,你管看我的账号,帮我看看点
  • 商家服务器的这个请求会带有一些参数,例如:
    • 账户相关的:商户号、私钥或路径、公钥或路径、公钥ID、商户API证书序列号、APIv3密钥
    • 商品相关的:商品名、价格、商品描述、支付者、商户号、商端的订单号、支付结果通知地址(NotifyUrl)
  • 思考:为什么要配置商户相关的信息?为什么要配置密钥?
    • 如果不能配置商户相关信息,微信官方收到钱,不知道用户是付给哪个商家的
    • 如果不配置密钥相关信息,微信不知道你是不是商家本人发的请求,等下你跟您老王说用户是想付给他。
  • 请求url是微信官方提供的:[ POST ] /v3/pay/transactions/jsapi,如果您用官方SDK则不必在意此项。
  • 商家服务器报告会在此时执行“创建订单”的工作,并将订单状态设置为“未支付”。

第 3 步:微信服务器向商家服务器进行预支付应答

  • 微信服务器收到请求后,回响应给商家一个应答,里面有 4 个重要的参数

    • noncestr -------- 32 位随机字符串 ------ 长这样:"BHIyfeov13pZqw5QUUsx7QunH4s8oR"

    • package -------- 预支付 ID----------- 长这样:"prepay_id=wx159540t6000114d47fde7e091010000"

    • sign ----------- 签名 --------------- 长这样:"kMHFurHsu8eoxqKJ6e8...共344位...AkJja6T2Tj0s6J1P50VUhLj+Ig="

    • timestamp ------ 应答的时间 --------- 长这样:"172039443"

第 4 步:商家服务器向用户小程序进行预支付应答

  • 将这 4 个参数放进一个 map 或一个对象,响应给用户小程序,用户需要这些参数才能真正实现支付。

第 5 步:用户小程序 -> 微信服务器发起支付请求

  • 小程序前台拿到上述 5 个参数后,会直接向微信官方发起支付,注意,这次调用的是微信的工具发起的请求,是 wx.requestPayment();
  • 代码如下:
const callMeChatPay = async (payParams: PayParams) => {
    // 下述参数要求,请查看微信官方文档中的《小程序支付/API列表/小程序调起支付》
    wx.requestPayment({
        timeStamp: payParams.timeStamp,
        nonceStr: payParams.nonceStr,
        package: payParams.package,
        signType: 'RSA', // 非对称加密的一种
        paySign: payParams.sign,

        success(res) {
            wx.showToast({
                title: '支付成功',
                icon: 'success'
            });
            console.log('支付成功:', res);
        },
        fail(err) {
            wx.showToast({
                title: '支付失败',
                icon: 'none'
            });
            console.error('支付失败:', err);
        }
    });
};

第 6 步:微信服务器向商家服务器发送结果通知

  • 微信官方收到一笔钱后,通过支付者所携带的那 5 个参数 (时间戳、随机串、预支付 id、加密方式、签名),可以判断这个钱是谁付的,是付给哪个商家的(因为这些参数之前发起支付时就有了,现在就等于去兑现支付的承诺)。
  • 微信官方需要把这个结果告诉给商家服务器,就相当于有人给你的银行卡里转了钱,银行执行完成钱加的工作后,会给你预留的手机号发送到账通知。同理,在微信支付中,你也需要给官方预留 “支付结果通知的 url”,即NotifyUrl,这个通知也叫 “回调通知”。
  • 小技巧:这个通知是微信官方发给你的,所以你的这个url必须是外网可以访问的(相当于你的手机能打得通),但如果你的商家服务器代码暂时没有部署到阿里云等服务器上,可以考虑使用 “内网穿透” 技术,我也建议你这么用,方便调试。
  • 商家服务器很适合在此时执行 “修改订单状态” 的工作,将订单状态设置为 “已支付”。

预支付

创建一个 Spring Boot 项目,引入微信支付依赖:

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.17</version>
        </dependency>

前端

上次编辑于:
贡献者: stonebox