微信支付
微信支付
准备
身份
小程序接入微信支付,不支持个人接入,需要先申请成为个体工商户或者企业。
申请成为个体工商户,要么有线下经营场所,要么有网店。建议先在拼多多开通网店,然后基于该网店申请个体工商户。
小程序
认证
在小程序官网的【首页/微信认证】中进行认证,注意认证的主体不要选个人,因为个人无法使用支付功能。认证费用 30 元。
如果你之前已经认证成了“个人”,可以如下的相关方法去变更主体:
想使用微信支付,则小程序的主体不能是“个人”,如果你之前已认证成了“个人”,可以在这里申请变更主体。
如果你认证的主体已经是“企业”或“个体工商户”则不需要操作此步。
变更主体需要的资料有:
原主体(即个人)的身份证正反面照片。由于只能上传一个文件,请提前将正反面拼接成一张图片。
新主体(即企业)的营业执照电子档。
在你申请的页面下载《变更申请函》,是一个 Word 文档,下载后先填写内容,再打印出来。在“个人”的地方签名、按手印(记得有两个签名和两个手印),在“企业”的地方盖公章。
填写时需要的“原小程序原始ID”请在【账号设置/账号信息/原始ID】处查看。
备案
小程序备案时主要填写的内容有:
- 主办人信息、主体信息、小程序管理员信息、小程序信息等;
- 在当页下载《互联网信息服务承诺书》进行打印,签名按手印后上传。
小程序备案完成后得到:
- 小程序 ID,也叫 AppId 或应用 ID,相当于小程序的身份证号。
- 小程序密钥,也叫 AppSecret。查看后请及时找安全的地方记录一下,如果忘记,只能重置。
微信支付
注册微信支付商户号
接入微信支付需要注册微信支付商户号,在微信支付主页,点击“接入微信支付”,注册微信支付商户号。
最终得到微信支付商户号。
开通产品
然后开通需要的产品。微信商户平台的产品有很多,在这里开通自己需要的产品即可:
- 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密钥进行解密回调:
总结
需要准备以下:
- AppId:小程序官网/管理/开发管理/AppId
- AppSecret:小程序官网/管理/开发管理/AppSecret
- 商户号:商户平台官网/帐户中心/商户信息/商户号
- 商户API证书系列号:商户平台官网/帐户中心/API安全/商户API证书/证书系列号
- 商户API私钥:在商户API证书文件
apiclient_key.pem中,有这个文件即可 - 微信支付公钥的ID:商户平台官网/帐户中心/API安全/微信支付公钥/微信支付公钥的ID。例:
PUB_KEY_ID_xxx - 微信支付公钥的本身:对
pub_key.pem文件解码及解密,得到 PublicKey 类型的对象作为微信支付公钥 - 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>
