微信V3支付时总是提示支付签名验证失败,后端是java 前端是uniapp开发小程序
前端代码
toRecharge() {
let list = {
openid: this.openid,
userId: this.userInfo.id,
rechargeAmount: 0.1,
type: 'sub_jsapi'
}
transactions(list).then((res1) => {
//成功结果
console.log(res1)
let params = res1.data.signMap
console.log(params)
uni.requestPayment({
provider: 'wxpay',
timeStamp: params.timeStamp,
nonceStr: params.nonceStr,
package: params.package,
orderInfo:"充值",
signType: params.signType,
paySign: params.paySign,
sign: params.paySign,
success: (res) => {
uni.showToast({
title: '支付成功'
})
},
fail: (err) => {
console.log(err)
},
complete: (err) => {
console.log(err)
//this.getOrderStatus()
this.btnDisabled = false
}
});
}).catch(err => {
//失败结果
console.log(32323232)
console.log(err)
});
},
后端代码
package com.xjc.vueapi.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.xjc.common.config.WechatPayConfig;
import com.xjc.common.config.WechatPayUrlEnum;
import com.xjc.common.request.WechatPayRequest;
import com.xjc.common.untils.ResultUtils;
import com.xjc.entity.RechargeRecord;
import com.xjc.service.RechargeRecordService;
import com.xjc.service.WeChatUserService;
import com.xjc.vo.WeChatPayVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @ClassName WeChatPayController
* @Description
* @Author bill
* @Date 2022-07-20 16:19
**/
@RestController
@RequestMapping("wx/pay")
@RequiredArgsConstructor
@Slf4j
public class WeChatPayController {
final WechatPayConfig wechatPayConfig;
final WechatPayRequest wechatPayRequest;
final WeChatUserService weChatUserService;
final RechargeRecordService rechargeRecordService;
/**
* 无需应答签名
*/
final CloseableHttpClient wxPayNoSignClient;
/**
* 交易
* @param type h5、jsapi、app、native、sub_jsapi
* @return
*/
@RequestMapping("transactions")
public ResultUtils transactions(@RequestBody WeChatPayVO weChatPayVO){
if (weChatPayVO.getUserId()== null){
return new ResultUtils("203","请先登录");
}
if(StringUtils.isBlank(weChatPayVO.getOpenid())){
return new ResultUtils("203","请先登录");
}
//先判断是否存在
Long userId = weChatUserService.selectUserIdByOpenid(weChatPayVO.getOpenid());
if(!weChatPayVO.getUserId().equals(userId)){
return new ResultUtils("203","请先登录");
}
if(weChatPayVO.getRechargeAmount()<=0){
return new ResultUtils("203","充值金额不能小于0");
}
//添加记录
RechargeRecord rechargeRecord = new RechargeRecord();
rechargeRecord.setUserId(weChatPayVO.getUserId());
rechargeRecord.setRechargeAmount(weChatPayVO.getRechargeAmount());
rechargeRecord.setRechargeMethod(weChatPayVO.getType());
rechargeRecordService.insertRecords(rechargeRecord);
// 统一参数封装
Map params = new HashMap<>(8);
params.put("appid", wechatPayConfig.getAppId());
params.put("mchid", wechatPayConfig.getMerchantId());
params.put("description", "充值信息");
int outTradeNo = new Random().nextInt(999999999);
params.put("out_trade_no", outTradeNo+"");
params.put("notify_url", wechatPayConfig.getNotifyOrderUrl());
Map amountMap = new HashMap<>(4);
// 金额单位为分
amountMap.put("total", 1);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 场景信息
Map sceneInfoMap = new HashMap<>(4);
// 客户端IP
sceneInfoMap.put("payer_client_ip", "127.0.0.1");
// 商户端设备号(门店号或收银设备ID)
sceneInfoMap.put("device_id", "127.0.0.1");
// 除H5与JSAPI有特殊参数外,其他的支付方式都一样
if (weChatPayVO.getType().equals(WechatPayUrlEnum.H5.getType())) {
Map h5InfoMap = new HashMap<>(4);
// 场景类型:iOS, Android, Wap
h5InfoMap.put("type", "IOS");
sceneInfoMap.put("h5_info", h5InfoMap);
} else if (weChatPayVO.getType().equals(WechatPayUrlEnum.JSAPI.getType()) || weChatPayVO.getType().equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
Map payerMap = new HashMap<>(4);
payerMap.put("openid", weChatPayVO.getOpenid());
params.put("payer", payerMap);
}
params.put("scene_info", sceneInfoMap);
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
// 重写type值,因为小程序会多一个下划线(sub_type)
String[] split = weChatPayVO.getType().split("_");
String newType = split[split.length - 1];
String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType));
log.info(url);
String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType)), paramsStr);
Map resMap = JSONObject.parseObject(resStr, new TypeReference{});
Map signMap = paySignMsg(resMap, weChatPayVO.getType());
resMap.put("type",weChatPayVO.getType());
resMap.put("signMap",signMap);
return new ResultUtils(resMap);
}
private Map paySignMsg(Map map,String type) {
// 设置签名信息,Native与H5不需要
if(type.equals(WechatPayUrlEnum.H5.getType()) || type.equals(WechatPayUrlEnum.NATIVE.getType()) ){
return null;
}
long timeMillis = System.currentTimeMillis();
String appId = wechatPayConfig.getAppId();
String timeStamp = timeMillis/1000+"";
String nonceStr = timeMillis+"";
String prepayId = map.get("prepay_id").toString();
String packageStr = "prepay_id="+prepayId;
// 公共参数
Map resMap = new HashMap<>();
resMap.put("nonceStr",nonceStr);
resMap.put("timeStamp",timeStamp);
// JSAPI、SUB_JSAPI(小程序)
if(type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType()) ) {
resMap.put("appId",appId);
resMap.put("package", packageStr);
// 使用字段appId、timeStamp、nonceStr、package进行签名
String paySign = createSign(resMap);
resMap.put("paySign", paySign);
resMap.put("signType", "MD5");
}
// APP
if(type.equals(WechatPayUrlEnum.APP.getType())) {
resMap.put("appid",appId);
resMap.put("prepayid", prepayId);
// 使用字段appId、timeStamp、nonceStr、prepayId进行签名
String sign = createSign(resMap);
resMap.put("package", "Sign=WXPay");
resMap.put("partnerid", wechatPayConfig.getMerchantId());
resMap.put("sign", sign);
resMap.put("signType", "HMAC-SHA256");
}
return resMap;
}
/**
* 获取加密数据
*/
private String createSign(Map params) {
try {
Map treeMap = new TreeMap<>(params);
List signList = new ArrayList<>(5);
for (Map.Entry entry : treeMap.entrySet())
{
signList.add(entry.getKey() + "=" + entry.getValue());
}
String signStr = String.join("&", signList);
signStr = signStr+"&key="+wechatPayConfig.getV3Key();
System.out.println(signStr);
Mac sha = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(wechatPayConfig.getV3Key().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha.init(secretKey);
byte[] array = sha.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
signStr = sb.toString().toUpperCase();
System.out.println(signStr);
return signStr;
}catch (Exception e){
throw new RuntimeException("加密失败!");
}
}
}
WechatPayConfig如下
package com.xjc.common.config;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName WechatPayConfig
* @Description
* @Author bill
* @Date 2022-07-20 15:08
**/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wx")
public class WechatPayConfig {
/**
* 小程序ID
*/
private String appId;
/**
* 小程序密钥
*/
private String appSecret;
/**
* 商户id
*/
private String merchantId;
/**
* 证书序列号
*/
private String merchantSerialNumber;
/**
* v3密钥
*/
private String v3Key;
/**
* 下单成功后回调
*/
private String notifyOrderUrl;
/**
* 退款回调url
*/
private String notifyRefoundUrl;
private String keyPath;
private String certPath;
/**
* 微信支付V3-url前缀
*/
private String baseUrl;
/**
* // 定义全局容器 保存微信平台证书公钥
*/
public Map certificateMap = new ConcurrentHashMap<>();
/**
* 获取商户的私钥文件
* @param keyPemPath
* @return
*/
public PrivateKey getPrivateKey(String keyPemPath){
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if(inputStream==null){
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取证书管理器实例
* @return
*/
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(merchantSerialNumber, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(merchantId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(merchantId,wechatPay2Credentials,v3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(merchantId);
}
/**
* 获取支付http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(merchantId, merchantSerialNumber, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
}
WechatPayRequest 如下
package com.xjc.common.request;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
/**
* @ClassName WechatPayRequest
* @Description 封装统一请求处理
* @Author bill
* @Date 2022-07-20 16:03
**/
@Component
@Slf4j
public class WechatPayRequest {
@Resource
private CloseableHttpClient wxPayClient;
public String wechatHttpGet(String url) {
try {
// 拼接请求参数
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);
return getResponseBody(response);
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
}
public String wechatHttpPost(String url,String paramsStr) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(paramsStr, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
return getResponseBody(response);
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
}
private String getResponseBody(CloseableHttpResponse response) throws IOException {
//响应体
HttpEntity entity = response.getEntity();
String body = entity==null?"": EntityUtils.toString(entity);
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//处理成功,204是,关闭订单时微信返回的正常状态码
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
log.info("成功, 返回结果 = " + body);
} else {
String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
log.error(msg);
throw new RuntimeException(msg);
}
return body;
}
}
这两个都试过,一个是和apiclient_key一起下载的,另一个是通过Certificate Downloader生成的,都不行,一直提示支付签名验证失败,这是怎么回事
签名算法是有规则的。
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 注意:密钥的长度为32个字节。
https://www.bilibili.com/video/BV1og411F7Uz/
APP下载地址
https://api.mch.weixin.qq.com/v3/pay/transactions/app
小程序下载地址
https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
微信支付的V3版本使用的是RSA加密,从前的V2版本使用的是MD5加密。今天在调试微信小程序的时候,始终无法调起微信支付,提示“支付签名验证失败”
问题排查思路:
统一下单接、关单、查单接口可以参考【Java教程】微信支付V3版本_哔哩哔哩_bilibili
同一商户的微信小程序的appId和APP应用的appId不同,但商户号和证书密钥等参数都相同
2.签名串需要严格区分大小写,注意字符串最后还要拼接“\n”
不同API的下单地址不同,参数有少量差别
APP下单地址:https://api.mch.weixin.qq.com/v3/pay/transactions/app
小程序下单地址:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
两种API的最后一级不同。
另外小程序获取预支付id需要用户登录的openId,且下单付款的用户必须和登录的用户是一个
APP签名串
wx8888888888888888
1414561699
5K8264ILTKCH16CQ2502SI8ZNMTM67VS
WX1217752501201407033233368018
JSAPI签名串
wx8888888888888888
1414561699
5K8264ILTKCH16CQ2502SI8ZNMTM67VS
prepay_id=wx201410272009395522657a690389285100
两种API的前三个串都是appId、时间戳、随机字符串
不同的是,app的第四个串是预支付id,而小程序的签名串需要在预支付id前写一个prepay_id=
注意能获取到预支付信息prepay_id不代表能调起应用成功
3.签名方法不同
我在调起app的时候一次就成功付款了,以至于没有怀疑过签名算法有问题,卡了四个多小时就是不知道为什么,最后终于让我发现了这个坑
在微信的官方demo里
com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil.encryptOAEP(String message, X509Certificate certificate)
第一次就是用这个方法给app做签名串的,没有任何问题,后来迁移到小程序就不行了,
小程序不能用这个方法,要用
String sign(byte[] message) {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(你的私钥privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
提示签名失败,一般就是参数少了,或者参数加密的顺序不对,
文章:微信支付v3 签名与验签 中也许有你想要的答案,请看下吧参考这个:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
另外微信支付问题,最好从微信社区中提问,你说的这个应该很多人遇见过,看看社区中是否有帖子.
签名验证失败一方面可能是认证的信息不对,另一方面可能是文件已失效