微信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<String, Object> 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<String, Object> amountMap = new HashMap<>(4);
// 金额单位为分
amountMap.put("total", 1);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 场景信息
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>(){});
Map<String, Object> signMap = paySignMsg(resMap, weChatPayVO.getType());
resMap.put("type",weChatPayVO.getType());
resMap.put("signMap",signMap);
return new ResultUtils(resMap);
}
private Map<String, Object> paySignMsg(Map<String, Object> 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<String, Object> 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<String, Object> params){
try {
Map<String, Object> treeMap = new TreeMap<>(params);
List<String> signList = new ArrayList<>(5);
for (Map.Entry<String, Object> 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<String, X509Certificate> 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生成的,都不行,一直提示支付签名验证失败,这是怎么回事