写在前面:
1. 后端使用微信官方sdk会简单很多,在第五点有我写的java代码示例。
2. 以下所有问题均基于uniapp开发微信小程序。
3. 所使用的工具版本为:
1、微信开发者工具:稳定版 Stable Build (1.06.2402040);
2、uniapp工具:HBuilder X 3.99;
3、后端程序语言:java;
4. 相关文档汇总:
1、WxJava - MiniApp Java SDK 4.6.0 API
2、wechatpay-java/README.md at main · wechatpay-apiv3/wechatpay-java · GitHub
5. 官方支付流程:
一、请求路径不在以下 request 合法域名列表中
1. 报错详细信息:
http://localhost:30013 不在以下 request 合法域名列表中,请参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html
2. 解决思路:
2.1 将域名配置在小程序中
根据报错信息查看微信相关文档,网络 | 微信开放文档
2.2 如果只是临时修改或有其他需求可以修改微信开发工具中的配置
启用设置中的“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”选项
二、获取用户openid出现40029响应码
1. 请求url:
https://api.weixin.qq.com/sns/jscode2session
2. 微信响应原文:
Response Body: {"errcode":40029,"errmsg":"invalid code, rid: 660a3033-68f26499-0b8d2eb4"}
3. 解决思路:
3.1 查看代码code是否被使用了两次;
3.2 检查小程序配置的appid是否与代码中一致、appSecret是否正确
检查小程序配置的appid:看appid是否与微信开发工具中配置的一致。
三、uni.requestPayment()下单时产生参数校验错误parameter error
1. 报错具体信息:
requestPayment:fail parameter error: parameter.timeStamp should be String instead of Undefined;parameter.nonceStr should be String instead of Undefined;parameter.package should be String instead of Undefined;parameter.signType should be String instead of Undefined;parameter.paySign should be String instead of Undefined;
2. 解决思路:
可以将所有参数都打印出来查看。下面出现错误可能原因:
2.1 请求数据字段数据类型必须为String
后台可能返回的不是String而是int类型,需要调整为String;
2.2 响应数据取用层级不对。
比如后台返回的数据是{data:{data:{}}},只取到数据所在层级前几层;
2.3 请求数据所放层级不对。
正确的应该是paySign等参数与success平行,结果参照了其他请求示例,将这些参数放在orderInfo下面了,正确的官方请求示例为:
四、解密退款通知时产生Tag mismatch错误
官方文档相关问题链接:微信支付-QA文档
1. 报错具体信息:
javax.crypto.AEADBadTagException: Tag mismatch!
2. 相关代码:
//原始代码
JSONObject resource = jsonObject.getJSONObject("resource");
AesUtil aesUtil = new AesUtil("v3密钥".getBytes());
String s = aesUtil.decryptToString(
resource.getString("associated_data").getBytes(),
resource.getString("nonce").getBytes(),
resource.getString("ciphertext")
);
3. 解决思路:
在网上查询后发现的可能原因:
3.1 v3秘钥有问题。
检查使用的v3秘钥是否有问题
3.2 解密入参需要以utf-8转换。
相关文章链接:分享微信支付回调解密提示:Tag mismatch | 微信开放社区
//更改后
JSONObject resource = jsonObject.getJSONObject("resource");
AesUtil aesUtil = new AesUtil("你的密钥".getBytes(StandardCharsets.UTF_8));
String s = aesUtil.decryptToString(
resource.getString("associated_data").getBytes(StandardCharsets.UTF_8),
resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
resource.getString("ciphertext")
);
3.3 解密时接口遗漏传入附加数据(associated_data)。
我忘了我为了美观规范写成了小驼峰的形式,导致 接收数据类的字段名 和 微信传过来的 不一致,所以associated_data没有接收到 (最好使用微信官方sdk,各种实体类和方法就不用再次创建了):
//微信原始发送的json
{"id":"xxx","create_time":"2024-04-02T11:53:30+08:00","resource_type":"encrypt-{
"id": "xxx",
"create_time": "2024-04-02T11:53:30+08:00",
"resource_type": "encrypt-resource",
"event_type": "REFUND.SUCCESS",
"summary": "退款成功",
"resource": {
"original_type": "refund",
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "xxx",
"associated_data": "refund",
"nonce": "xxx"
}
}
//错误接收的json
{
"id": "xxx",
"resource": {
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "xxx",
"nonce": "xxx"
},
"summary": "退款成功"
}
五、代码示例:
后端使用微信官方sdk示例。
1. pom:
<!-- 微信sdk依赖 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
2. 具体代码:
/**
* refundApply 退款申请
*
* @param request
* @return Result<java.lang.Object>
*/
@PostMapping("refund-apply")
public Result<Object> refundApply(@RequestBody CreateRequest request) {
// 获取.p12文件并获取其中的信息
P12InfoVo vo = getP12Info();
// 初始化商户配置
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(readWechatProfile.getMchId())
.privateKey(vo.getPrivateKey())
.merchantSerialNumber(vo.getSerialNo())
.apiV3Key(readWechatProfile.getV3())
.build();
// 初始化服务,使用微信sdk对应功能的service
refundService = new RefundService.Builder().config(config).build();
// 调用对应service中的接口
Refund response = refundService.create(request);
log.info("退款申请响应:{}", response);
return Result.success();
}
其中privateKey方法是可以替换成读取私钥的.pem文件地址,具体可查看类中api。另外读取p12文件api:
public P12InfoVo getP12Info2() {
String p12Path = "xx/xx/xxx.p12";// p12证书路径
char[] mchid = "xxx" .toCharArray();//商户号
try {
// 获取文件流
FileInputStream fileInputStream = new FileInputStream(p12Path);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(fileInputStream, mchid);
String keyAlias = null;
//解析证书,必须有别名
Enumeration<String> aliases = keyStore.aliases();
if (aliases.hasMoreElements()) {
keyAlias = aliases.nextElement();
}
//解析私钥
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, mchid);
Certificate cert = keyStore.getCertificate(keyAlias);
BigInteger serialNumber = ((X509CertImpl) cert).getSerialNumber();
//证书一般都使用16进制表示
String certSn = serialNumber.toString(16).toUpperCase();
//设置证书公钥、私钥、序列号
return P12InfoVo.builder()
.publicKey(cert.getPublicKey())
.privateKey(privateKey)
.serialNo(certSn)
.build();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}