一.说明和前期准备(小程序的V3版本)
特别说明:遇到 java.security.InvalidKeyException: Illegal key size ******* getValidator的错误
参考添加链接描述
JDK7的下载地址
JDK8的下载地址:
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。
1.场景:小程序项目需要对接微信小程序的支付接口,这里使用的是V3版本
官方文档链接:微信小程支付申请文档链接
1.1按照上面的文档申请支付
拿到相关的参数(小程序的Appid,小程序的appSecret ,商户秘钥 PrivateKey,商户号 Mchid,证书序列号MchSerialNo,V3秘钥))本人读取方式为从配置文件中读取
package com.wxapplet.model;
import java.io.IOException;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import com.foc.alipay.config.AlipayServiceEnvConstants;
import com.foc.common.util.Constants;
public class WxV3payConfig {
static {
/**
* 静态代码块初始化类变量
*/
Properties proper = new Properties();
try {
proper.clear();
proper.load(WxV3payConfig.class.getResourceAsStream("/paykey.properties"));
appletAppid=proper.getProperty("appletAppid");
appletMchid=proper.getProperty("appletMchid");
appletPrivateKeyPath=proper.getProperty("appletPrivateKeyPath");
appletMchSerialNo=proper.getProperty("appletMchSerialNo");
appletSecret=proper.getProperty("appletSecret");
appletApiV3Key=proper.getProperty("appletApiV3Key");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String appletAppid;// 由微信生成的应用ID
public static String appletMchid;// 直连商户的商户号,由微信支付生成并下发。
public static String appletPrivateKeyPath;// 小程序商户秘钥
public static String appletMchSerialNo;// 商户证书序列号
public static String appletSecret;// 小程序 appSecret
public static String appletApiV3Key;// V3密钥
@Value("${appletAppid}")
public static void setAppletAppid(String appletAppid) {
WxV3payConfig.appletAppid = appletAppid;
}
@Value("${appletMchid}")
public static void setAppletMchid(String appletMchid) {
WxV3payConfig.appletMchid = appletMchid;
}
@Value("${appletPrivateKeyPath}")
public static void setAppletPrivateKey(String appletPrivateKeyPath) {
WxV3payConfig.appletPrivateKeyPath = appletPrivateKeyPath;
}
@Value("${appletMchSerialNo}")
public static void setAppletMchSerialNo(String appletMchSerialNo) {
WxV3payConfig.appletMchSerialNo = appletMchSerialNo;
}
@Value("${appletSecret}")
public static void setAppletSecret(String appletSecret) {
WxV3payConfig.appletSecret = appletSecret;
}
@Value("${appletApiV3Key}")
public static void setAppletApiV3Key(String appletApiV3Key) {
WxV3payConfig.appletApiV3Key = appletApiV3Key;
}
}
对应的paykey.properties文件
#v3
#ying yong id
appletAppid=
#shang hu hao
appletMchid=
appletPrivateKeyPath=appletPrivateKey.pem
#zheng shu xu lie hao
appletMchSerialNo=
#appsectet
appletSecret=
#v3 mi yao
appletApiV3Key=
2.引入相关的maven包
2.1参考的微信官方文档
微信开发指引
微信java给出的示例
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
3.相关工具类
3.1发送http请求工具类
package com.wxapplet.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
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 com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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.util.PemUtil;
@Component
public class AppletHttpClient {
/**
* 将通知参数转化为字符串
*
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null;) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取商户的私钥文件
*
* @param filename
* @return
*/
public PrivateKey getPrivateKey(String filename) {
InputStream insss = AppletHttpClient.class.getClassLoader().getResourceAsStream(filename);
// InputStream insss = ClassLoader.getSystemResourceAsStream(filename);
return PemUtil.loadPrivateKey(insss);
}
/**
* 获取http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(AutoUpdateCertificatesVerifier verifier) {
// 获取商户私钥
PrivateKey privateKey = getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(WxV3payConfig.appletMchid, WxV3payConfig.appletMchSerialNo, privateKey).withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
// 获取商户私钥
PrivateKey privateKey = getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
// 用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
// 设置商户信息
.withMerchant(WxV3payConfig.appletMchid, WxV3payConfig.appletMchSerialNo, privateKey)
// 无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* V3 SHA256withRSA 签名.
* @param appid
* @param timeStamp
* @param nonceStr
* @param prepayId
* @param privateKey
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
* @throws FileNotFoundException
*/
public String getSign1(String appid ,String timeStamp,String nonceStr ,String prepayId , String privateKeyPath) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, FileNotFoundException{
try {
String signatureStr = Stream.of(appid, timeStamp, nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
InputStream insss = PayCommonUtil.class.getClassLoader().getResourceAsStream(privateKeyPath);
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(insss);
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取签名验证器
*
* @return
*/
@Bean
public AutoUpdateCertificatesVerifier getVerifier() {
// 获取商户私钥
PrivateKey privateKey = getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
// 私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(WxV3payConfig.appletMchSerialNo, privateKey);
// 身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(WxV3payConfig.appletMchid, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(wechatPay2Credentials,
WxV3payConfig.appletApiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
}
3.2进行下单操作
这里使用JSAPI下单,微信小程序JSAPI下单参考
String appId =WxV3payConfig.appletAppid; //
String mchId =WxV3payConfig.appletMchid; // 商户号
String privateKeyPath =WxV3payConfig.appletPrivateKeyPath;// 你的商户私钥路径
String nonceStr = RandomUtil.randomString(32);//随机字符串
// 构造订单信息
JSONObject jsonObject = new JSONObject();
jsonObject.put("appid",appId); // 小程序的应用appid
jsonObject.put("mchid", mchId); // 直连商户的商户号,由微信支付生成并下发。
String body = "订单消费";
Project project = projectService.findOne(tempPersonalRecord.getProjectId());
if (project != null && StringUtils.isNotBlank(project.getName())) {
body = project.getName();
}
jsonObject.put("description", body); // 商品描述
jsonObject.put("out_trade_no", outTradeNo); // 商户订单号
logger.info("total_fee=" + Math.round(tempPersonalRecord.getMoney() * 100) + "");
JSONObject amoutJson = new JSONObject();
amoutJson.put("total", tempPersonalRecord.getMoney() * 100); // 总金额,订单总金额,单位为分
amoutJson.put("currency", "CNY");// 货币类型CNY:人民币,境内商户号仅支持人民币。
jsonObject.put("amount", amoutJson); // 订单金额信息
jsonObject.put("notify_url", notifyUrl); // 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
JSONObject payUser = new JSONObject();
payUser.put("openid", mfUser.getOpenId());
jsonObject.put("payer", payUser); // 支付者用户在商户appid下的唯一标识。
// 发送请求进行JSAPI下单
HttpPost httpPost = new HttpPost(APPLET_JSAPI_URL);
StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpPost);
String reString = EntityUtils.toString(response.getEntity());//响应体
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode==200) {//处理成功
logger.info("微信小程序支付响应成功,返回的结果为="+reString);
}else if (statusCode==204) {//处理成功无返回的boy
logger.info("微信小程序支付响应成功");
}else {
logger.info("微信小程序响应失败,响应码为="+statusCode+",返回的结果为="+reString);
return new ResultVO(ResultCode.ORDERERROR);
}
SortedMap<String, String> params = new TreeMap<String, String>();
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);// 时间戳
String preId = "prepay_id=" + JSON.parseObject(reString).getString("prepay_id");
String paySign = appletHttpClient.getSign1(appId, timeStamp, nonceStr, preId,
privateKeyPath);// 签名
rr.put("appId", appId);// appid
rr.put("timeStamp", timeStamp);// 时间戳
rr.put("nonceStr", nonceStr);// 随机字符串
rr.put("package", "prepay_id=" + JSON.parseObject(reString).getString("prepay_id"));// 订单详情扩展字符串
rr.put("signType", "RSA");// 签名类型,默认为RSA,仅支持RSA
rr.put("paySign", paySign);// 签名
rr.put("outTradeNo", tempPersonalRecord.getOutTradeNo());//订单编号
return new ResultVO(ResultCode.SUCCESS, rr);
3.3获取返回的prepay_id稍后返回给前端
4.向前端传递调用wx.requestPayment(OBJECT)发起微信支付的参数
Api地址
特别说明:签名使用的是SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。
4.1生成签名的工具类
/**
* V3 SHA256withRSA 签名.
* @param appid
* @param timeStamp
* @param nonceStr
* @param prepayId
* @param privateKey
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
*/
public static String getSign(String appid ,String timeStamp,String nonceStr ,String prepayId , String privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException{
try {
String signatureStr = Stream.of(appid, timeStamp, nonceStr, prepayId)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
官方签名参考地址
4.2签名的生成
String nonceStr = RandomUtil.randomString(32);// 随机字符串
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);// 时间戳
String preId = "prepay_id=" + JSON.parseObject(reString).getString("prepay_id");
String paySign = PayCommonUtil.getSign(weChatPub.getAppletAppid(), timeStamp, nonceStr, preId,
weChatPub.getAppletPrivateKey());// 签名
rr.put("appId", weChatPub.getAppletAppid());// appid
rr.put("timeStamp", timeStamp);// 时间戳
rr.put("nonceStr", nonceStr);// 随机字符串
rr.put("package", "prepay_id=" + JSON.parseObject(reString).getString("prepay_id"));// 订单详情扩展字符串
rr.put("signType", "RSA");// 签名类型,默认为RSA,仅支持RSA
rr.put("paySign", paySign);// 签名
return new ResultVO(ResultCode.SUCCESS, rr);
4.3使用对生成的签名使用官方工具验签
工具下载地址
5.支付成功后微信的回调
官方的验签和解密
// 构建request,传入必要参数
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(request);
// 从notification中获取解密报文
System.out.println(notification.getDecryptData());
相关示例代码(包含验签操作)自己实现的代码文章来源:https://www.toymoban.com/news/detail-483327.html
logger.info("支付成功进入回调方法--" + DateUtil.formatDate(new Date()));
Map<String, String> map1 = new HashMap<>();// 应答对象
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();// 应答对象
String outTradeNo = null;// 商户订单号
String transactionId = null;// 微信支付订单号
String timeEnd = null;//
String openid = null;// 用户标识
String tradeState = null;// 交易状态
// 处理通知参数
String body = appletHttpClient.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
// 获取验签器
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
PrivateKey privateKey =appletHttpClient.getPrivateKey(WxV3payConfig.appletPrivateKeyPath);
AutoUpdateCertificatesVerifier verifier = appletHttpClient.getVerifier();
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(
verifier, requestId, body);
// 进行验签操作
if (!wechatPay2ValidatorForRequest.validate(request)) {// 验签成功
logger.error("支付通知验签失败");
// 失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
logger.info("支付通知验证成功");
// 获取明文
String plainText = wxAPIV3AesUtil.decryptFromResource(bodyMap, WxV3payConfig.appletApiV3Key);
// 将明文转换成map
Map<String, String> resultMap = JSON.parseObject(plainText, HashMap.class);
outTradeNo = resultMap.get("out_trade_no");// 商户订单号
transactionId = resultMap.get("transaction_id");// 微信支付订单号
timeEnd = resultMap.get("time_end");//
openid = resultMap.get("openid");// 用户标识
tradeState = resultMap.get("trade_state");// 交易状态
5.1验签的工具类
package com.wxapplet.util;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
public class WechatPay2ValidatorForRequest {
protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
/**
* 应答超时时间,单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
//·验签
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}
5.2 解密回调通知的数据相关工具类
@Component
public class WxAPIV3AesUtil {
/**
* 对称解密
*
* @param bodyMap
* @return
*/
public static String decryptFromResource(Map<String, Object> bodyMap, String apiV3Key)
throws GeneralSecurityException {
// 通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
// 数据密文
String ciphertext = resourceMap.get("ciphertext");
// 随机串
String nonce = resourceMap.get("nonce");
// 附加数据
String associatedData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
return plainText;
}
}
参考链接
官方微信V3排错指南
参考1
参考2文章来源地址https://www.toymoban.com/news/detail-483327.html
到了这里,关于微信小程序支付V3版本接口实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!