导入微信官方推荐maven jar
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
获取微信证书,并生成前端调起支付需要的参数(示例是合单支付)
/**
* 商户号
* url https://pay.weixin.qq.com/index.php/core/account/info
*/
private static final String merchantId = "xxxxx";
/**
* 商户证书序列号。
* url https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
*/
private static final String merchantSerialNumber = "xxxxx";
/**
* API V3密钥
* url https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
*/
private static final String apiV3Key = "xxxxx";
/**
* 商户API V3私钥证书地址(从微信商户平台下载,放到本地的地址)
* url https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
*/
private static final String privateKeyPath = "xxxxx";
private PrivateKey merchantPrivateKey;
private ScheduledUpdateCertificatesVerifier verifier;
private CloseableHttpClient httpClient;
@PostConstruct
//初始化微信证书
private void initHttpClient() {
log.info("--------微信证书v3初始化开始--------");
try (FileInputStream inputStream = new FileInputStream(privateKeyPath)){
merchantPrivateKey = PemUtil.loadPrivateKey(inputStream);
verifier = new ScheduledUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
log.info("--------微信证书v3初始化成功--------");
}catch (IOException e){
log.error("微信证书v3初始化失败,证书地址:{}",privateKeyPath,e);
}
}
/**
* 微信合单支付
* @return
*/
public Map<String, String> combineTransactionsApp(){
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/combine-transactions/app");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
JSONObject requestBody = new JSONObject();
requestBody.put("combine_appid","xxxxx");
requestBody.put("combine_mchid", merchantId);
requestBody.put("combine_out_trade_no", "xxxxx");
JSONArray subOrders = new JSONArray();
JSONObject subOrderJsonObject = new JSONObject();
subOrderJsonObject.put("mchid",merchantId);
subOrderJsonObject.put("attach","合单支付");
JSONObject amountJsonObject = new JSONObject();
amountJsonObject.put("total_amount",100);
amountJsonObject.put("currency","CNY");
subOrderJsonObject.put("amount",amountJsonObject);
subOrderJsonObject.put("out_trade_no", "xxxxx");
subOrderJsonObject.put("sub_mchid", "xxxxx");
subOrderJsonObject.put("description","商品购买");
subOrders.add(subOrderJsonObject);
requestBody.put("sub_orders",subOrders);
requestBody.put("notify_url", "xxxxx");
String requestJsonString = JSONObject.toJSONString(requestBody);
log.info("请求参数:{}",requestJsonString);
httpPost.setEntity(new StringEntity(requestJsonString, "UTF-8"));
try (CloseableHttpResponse response = httpClient.execute(httpPost)){
String bodyAsString = EntityUtils.toString(response.getEntity());
log.info("response:{}",bodyAsString);
JSONObject responseJson = JSONObject.parseObject(bodyAsString);
String prepayId = Optional.ofNullable(responseJson.getString("prepay_id"))
.orElseThrow(() -> new BestPayException(responseJson.getString("message")));
return this.buildSign(prepayId);
} catch (IOException e) {
log.error("微信合单支付V3接口请求失败",e);
}
return null;
}
/**
* 生成返回给前端的数据
* @param prepayId 预支付id
* @return
*/
public Map<String, String> buildSign(String prepayId) {
Map<String, String> resultMap = new LinkedHashMap<>(8);
resultMap.put("appid", "xxxxx");
resultMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
resultMap.put("noncestr", "xxxxx");
resultMap.put("prepayid", prepayId);
resultMap.put("sign", this.signSHA256(resultMap,merchantPrivateKey));
resultMap.put("partnerid", merchantId);
resultMap.put("package", "Sign=WXPay");
return resultMap;
}
/**
* 微信v3支付签名
*/
public String signSHA256(Map<String, String> params, PrivateKey privateKey) {
StringBuilder content = new StringBuilder();
for (String key : params.keySet()) {
String value = params.get(key);
if (StringUtils.isNotEmpty(value)) {
content.append(value).append("\n");
}
}
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(content.toString().getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
log.error("sha256_HMAC加密失败",e);
}
return null;
}
回调数据校验
@PostMapping(value = "wxMergePayNotify")
public Map<String, String> wxMergePayNotify(HttpServletRequest request) {
//给微信的回应
Map<String, String> result = new HashMap<>(2);
//解密数据
String data = this.payRequestDecryption(request);
if (data == null){
result.put("code","FAILED");
result.put("message","失败");
return result;
}
log.info("微信支付处理后的数据data={}", data);
JSONObject response = JSONObject.parseObject(data);
//TODO .......业务逻辑处理
log.info("微信支付回调成功");
result.put("code","SUCCESS");
result.put("message","成功");
return result;
}
/**
* v3支付回调数据校验
*/
public String payRequestDecryption(HttpServletRequest request){
//校验签名
String data = this.verifySign(request);
if (data == null){
return null;
}
JSONObject jsonObject = JSONObject.parseObject(data);
//重复通知不处理,redis示例
String id = jsonObject.getString("id");
String key = "WXV3PayId:" + id;
if (Objects.equals(redisUtil.get(key),id)){
log.info("重复通知不处理:{}",data);
return null;
}
//10分钟以内的请求不处理
redisUtil.set(key,1,60*10);
//非支付通知不处理
String eventType = jsonObject.getString("event_type");
String resourceType = jsonObject.getString("resource_type");
if (!Objects.equals(eventType,"TRANSACTION.SUCCESS") && Objects.equals(resourceType,"encrypt-resource")){
log.info("不是支付通知不处理:{}",data);
return null;
}
//参数解密
JSONObject resource = jsonObject.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String associatedData = resource.getString("associated_data");
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String result = null;
try {
result = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);
} catch (GeneralSecurityException e) {
log.error("微信v3解密异常",e);
}
return result;
}
public static final String REQUEST_ID = "Request-ID";
public static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial";
public static final String WECHAT_PAY_SIGNATURE = "Wechatpay-Signature";
public static final String WECHAT_PAY_TIMESTAMP = "Wechatpay-Timestamp";
public static final String WECHAT_PAY_NONCE = "Wechatpay-Nonce";
/**
* 签名校验
* url https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter5_1_13.shtml
*/
public String verifySign(HttpServletRequest request){
//检查header
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
for (String headerName : headers) {
if (request.getHeader(headerName) == null) {
log.info("{} is null", headerName);
return null;
}
}
//检查时间
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
log.info("超过应答时间");
return null;
}
//获取微信返回的参数
String data;
try {
data = request.getReader().lines().collect(Collectors.joining());
} catch (IOException e) {
log.error("获取微信V3回调参数失败",e);
return null;
}
//校验签名
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String message = timestamp + "\n" + nonce + "\n" + data + "\n";
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
log.info("签名校验失败");
return null;
}
return data;
}