一、导入maven
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
二、添加配置文件
package org.jeecg.config.wxpay;
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.ScheduledUpdateCertificatesVerifier;
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;
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.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
@Configuration
@PropertySource("classpath:application-dev.yml") //读取配置文件
@ConfigurationProperties(prefix="wxjzg") //读取wx.pay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
// APIv2密钥
private String partnerKey;
/**
* 获取商户的私钥文件
* @description:获取商户的私钥文件
* @author: Chris
* @date: 2021/12/29 20:50
* @Param filename:
* @return: java.security.PrivateKey
*/
private PrivateKey getPrivateKey(String filename){
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream(filename.getBytes("utf-8")));
return merchantPrivateKey;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取签名验证器
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
/**
* 获取http请求对象
* @description:获取http请求对象
* @author: Chris
* @date: 2021/12/29 20:56
* @Param verifier:
* @return: org.apache.http.impl.client.CloseableHttpClient
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
// //获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
// //获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, mchSerialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
}
}
三、统一支付接口
controller层
package org.jeecg.modules.orderinfo.controller;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
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.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.orderadmin.orderinfo.entity.OrderInfo;
import org.jeecg.modules.orderadmin.orderinfo.util.HttpUtils;
import org.jeecg.modules.orderadmin.orderinfo.util.WechatPay2ValidatorForRequest;
import org.jeecg.modules.orderinfo.entity.HyOrderInfo;
import org.jeecg.modules.orderinfo.service.IHyOrderInfoService;
import org.jeecg.modules.orderinfo.service.IPaymentService;
import org.jeecg.modules.orderinfo.xcxutil.AesUtil;
import org.jeecg.modules.orderinfo.xcxutil.PayUtil;
import org.jeecg.modules.orderinfo.xcxutil.QRCodeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.util.*;
import org.apache.commons.codec.binary.Base64;
/**
* @Description: 订单
* @Author: jeecg-boot
* @Date: 2021-12-15
* @Version: V1.0
*/
@Api(tags="微信支付")
@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {
@Autowired
private CloseableHttpClient wxPayClient;
/**
* url
*/
@Value(value = "${wxjzg.url}")
private String url;
/**
* appid
*/
@Value(value = "${wxjzg.appid}")
private String appId;
/**
* mchid
*/
@Value(value = "${wxjzg.mchid}")
private String mchId;
/**
* key
*/
@Value(value = "${wxjzg.apiV3Key}")
private String key;
/**
* privateKeyPath
*/
@Value(value = "${wxjzg.privateKeyPath}")
private String privateKeyPath;
/**
* 生成链接地址配置
*/
@Value(value = "${wxjzg.secret}")
private String secret;
@Autowired
private IPaymentService paymentService;
@Autowired
private IHyOrderInfoService hyOrderInfoService;
@Resource
private Verifier verifier;
private static String ENCODING = "UTF-8";
public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* <p>统一下单入口</p>
*
* @param
* @param
* @throws Exception
*/
@AutoLog(value = "统一下单入口")
@ApiOperation(value="统一下单入口", notes="统一下单入口")
@PostMapping(value = "/toPay")
public Result<?> toPay(@RequestBody Map<String,String> map) throws Exception {
String orderId = map.get("orderId");
// 获取openid
String openId = map.get("openId");
// 获取订单下信息
HyOrderInfo orderInfo = hyOrderInfoService.getById(orderId);
if (orderInfo == null) {
return Result.error("订单不存在");
}
if (orderInfo.getOrderTotalSum() == null || new BigDecimal(orderInfo.getOrderTotalSum()).floatValue() < 0) {
return Result.error("订单有误,请确认!");
}
if (Integer.valueOf(orderInfo.getOrderState()) != 1 && Integer.valueOf(orderInfo.getOrderState()) != 7) {//1待付款
String msg = Integer.valueOf(orderInfo.getOrderState()) > 1 ? "此订单已支付!" : "订单未提交,请确认!";
return Result.error(msg);
}
//发起预支付
// String prepayId = paymentService.xcxPayment(orderInfo.getOrderCode(), orderInfo.getOrderTotalSum(), openId);
String prepayId = paymentService.xcxPayment(orderInfo, openId);
// 封装要签名的数据
Map<String,String> parameters = new LinkedHashMap<>();
// appId
parameters.put("appId",appId);
// 时间戳
Long timeStamp = PayUtil.getCurrentTimestampMs();
parameters.put("timeStamp", timeStamp.toString());
// 随机数
String nonceStr = PayUtil.generateNonceStr();
parameters.put("nonceStr",nonceStr);
// 订单详情扩展字符串
parameters.put("package","prepay_id=" + prepayId);
// 签名串
String signString = appId + "\n" + timeStamp.toString() + "\n" + nonceStr + "\n" + "prepay_id=" + prepayId + "\n" ;
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
// 使用商户私钥对待签名串进行SHA256 with RSA签名
// 进行签名服务
byte[] sign = sign(signString,privateKey);
// Base64计算签名值
String paySign = encodeBase64(sign);
// String paySign = PayUtil.generateSignature(parameters,key);
parameters.put("paySign", paySign);
return Result.OK(parameters);
}
/**
* 二进制数据编码为BASE64字符串
* @param
* @return
*/
public static String encodeBase64(byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
}
/**
* SHA256WithRSA签名
* @param data
* @param privateKey
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws InvalidKeyException
* @throws SignatureException
* @throws UnsupportedEncodingException
*/
public static byte[] sign(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException,
SignatureException, UnsupportedEncodingException {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data.getBytes(ENCODING));
return signature.sign();
}
/**
* 获取商户的私钥文件
* @description:获取商户的私钥文件
* @author: Chris
* @date: 2021/12/29 20:50
* @Param filename:
* @return: java.security.PrivateKey
*/
private PrivateKey getPrivateKey(String filename){
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream(filename.getBytes("utf-8")));
return merchantPrivateKey;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取openid
*/
public String getOpenId(@RequestParam("code")String code) throws Exception{
String urlPath = url;
urlPath += "?appid=" + appId;
urlPath += "&secret=" + secret;
urlPath += "&js_code=" + code;
urlPath += "&grant_type=authorization_code";
urlPath += "&connect_redirect=1";
String res = null;
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// get请求
HttpGet httpGet = new HttpGet(urlPath);
CloseableHttpResponse response = null;
// 配置信息
RequestConfig requestConfig = RequestConfig.custom()
// 设置请求超时时间(毫秒)
.setConnectTimeout(5000)
// socket读写超时时间(单位毫秒)
.setConnectionRequestTimeout(5000)
// 设置是否允许重定向(默认为true)
.setSocketTimeout(5000)
// 将上面的配置信息 运用到这个Get请求里
.setRedirectsEnabled(false).build();
// 由客户端执行(发送)Get请求
httpGet.setConfig(requestConfig);
// 从响应模型中获取响应实体
response = httpClient.execute(httpGet);
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null){
res = EntityUtils.toString(responseEntity);
}
// 释放资源
if (httpClient != null){
httpClient.close();
}
if (response != null){
response.close();
}
JSONObject jsonObject = JSON.parseObject(res);
String openid = jsonObject.getString("openid");
return jsonObject.getString("openid");
}
/**
* 查询订单
*/
@AutoLog(value = "查询订单")
@ApiOperation(value="查询订单", notes="查询订单")
@GetMapping(value = "/queryOrder")
public Result<?> queryOrder (@RequestParam(name = "orderId") String orderId) throws IOException {
HyOrderInfo hyOrderInfo = hyOrderInfoService.getById(orderId);
String transactionId = hyOrderInfo.getTransactionId();
String mchid = mchId;
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + transactionId + "?" + "mchid=" + mchid;
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept","application/json");
// 完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200 && statusCode != 204){
throw new RuntimeException("查询订单异常,响应码 = " + statusCode + ",查询订单返回结果 = "+ bodyAsString);
}
JSONObject json = JSONObject.parseObject(bodyAsString);
// 交易状态
String tradeState = json.getString("trade_state");
if ("SUCCESS".equals(tradeState)){
if (!"2".equals(hyOrderInfo.getOrderState())){
// 生成6位随机数
String captcha = RandomUtil.randomNumbers(6);
// 封装二维码内容
String contents = "hyzj6666&" + orderId + captcha;
// 生成base64加密二维码
hyOrderInfo.setQrCodeAddress(QRCodeUtils.creatRrCode(contents, 200,200));
hyOrderInfo.setOrderState("2");
hyOrderInfoService.updateById(hyOrderInfo);
}
return Result.OK(hyOrderInfo);
}
return Result.OK("");
} finally {
response.close();
}
}
/**
* 关闭订单
*/
@AutoLog(value = "关闭订单")
@ApiOperation(value="关闭订单", notes="关闭订单")
@PostMapping(value = "/closeOrder")
public Result<?> closeOrder (@RequestBody Map<String,String> map) throws IOException {
String orderId = map.get("orderId");
String mchid = mchId;
// 请求的url
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + orderId + "/close");
// 请求参数
JSONObject body = new JSONObject();
body.put("mchid", mchid);
StringEntity entity = new StringEntity(String.valueOf(body),"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//创建httpclient对象
CloseableHttpClient client = HttpClients.createDefault();
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
} else if (statusCode == 204) {
// System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
//TODO:更改订单状态。status=6
int i = hyOrderInfoService.updateRefundStateByOrderCode(orderId, "2");
return Result.OK("取消成功");
}
@ApiOperation("支付通知")
@PostMapping("/notify")
public String notify(HttpServletRequest request, HttpServletResponse response){
Gson gson = new Gson();
// 应答对象
HashMap<String, Object> map = new HashMap<>();
try {
// 处理通知参数
String body = HttpUtils.readData(request);
Map<String,Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String)bodyMap.get("id");
// 签名验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if (!wechatPay2ValidatorForRequest.validate(request)){
// 失败应答
response.setStatus(500);
map.put("code","ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
// 处理订单
hyOrderInfoService.processOrder(bodyMap);
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
}
serviceimpl层
package org.jeecg.modules.orderinfo.service.impl;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.orderinfo.entity.HyOrderInfo;
import org.jeecg.modules.orderinfo.service.IPaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
@Service
public class PaymentServiceImpl implements IPaymentService {
@Autowired
private CloseableHttpClient wxPayClient;
@Autowired
private RedisUtil redisUtil;
/**
* appid
*/
@Value(value = "${wxjzg.appid}")
private String appid;
/**
* mchid
*/
@Value(value = "${wxjzg.mchId}")
private String mchid;
/**
* key
*/
@Value(value = "${wxjzg.key}")
private String key;
/**
* 发起微信支付获取预支付交易会话标识prepayId
*/
@Override
public String xcxPayment(HyOrderInfo orderInfo, String openId) throws IOException {
// 请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// 请求body参数
JSONObject jsonObject = new JSONObject();
// 直连商户号
jsonObject.put("mchid",mchid);
// 商户订单号
jsonObject.put("out_trade_no", orderInfo.getOrderCode());
// 应用ID
jsonObject.put("appid", appid);
// 商品描述
jsonObject.put("description","航院之家");
// 通知地址
jsonObject.put("notify_url", "http://hyzj.rzocean.com/hyzj/payment/notify");
// 订单金额
JSONObject amount = new JSONObject();
amount.put("total", new BigDecimal(orderInfo.getOrderTotalSum()).multiply(BigDecimal.valueOf(100)).intValue());
amount.put("currency", "CNY");
jsonObject.put("amount",amount);
// 用户标识
JSONObject payer = new JSONObject();
payer.put("openid",openId);
jsonObject.put("payer", payer);
StringEntity entity = new StringEntity(String.valueOf(jsonObject),"utf-8");
entity.setContentType("application/json");
// httpPost.setHeader("Content-type", "application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//创建httpclient对象
CloseableHttpClient client = HttpClients.createDefault();
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
// System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
// System.out.println("success");
} else {
// System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
JSONObject json = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
String message = (String) json.get("message");
if (message != null){
throw new IOException(message);
} else {
throw new IOException("支付失败");
}
}
} finally {
response.close();
}
//获取结果实体
HttpEntity httpEntity = response.getEntity();
String result = EntityUtils.toString(httpEntity, "UTF-8");
JSONObject json = JSONObject.parseObject(result);
String prepayId = json.get("prepay_id").toString();
// redisUtil.del(CommonConstant.PREFIX_FACULTY_ORDER+orderInfo.getId());
return prepayId;
}
}
四、退款
controller
@Autowired
private Verifier verifier;
/**
* 申请退款
*
* @description:微信申请退款API
* @author: Chris
* @date: 2021/12/29 9:14
* @Param null:
* @return: null
*/
@ApiOperation("申请退款")
@PostMapping("/refunds")
public Result<?> orderRefund(@RequestBody RefundRecord refundRecord) throws Exception {
orderInfoService.orderRefund(refundRecord);
return Result.OK();
}
/**
* 查询退款
*
* @description:微信查询退款API
* @author: Chris
* @date: 2021/12/29 14:52
* @Param refundNo:
* @return: org.jeecg.common.api.vo.Result<?>
*/
@ApiOperation("查询退款")
@GetMapping("/queryRefund")
public Result<?> queryRefund(@RequestParam("refundNo") String refundNo) throws Exception {
String result = orderInfoService.queryRefund(refundNo);
return Result.OK("查询成功", result);
}
/**
* 退款结果通知
*
* @description:退款状态改变后,微信会把相关退款结果发送给商户。
* @author: Chris
* @date: 2021/12/29 16:00
* @Param request:
* @Param response:
* @return: java.lang.String
*/
@PostMapping("/refunds/notify")
public String refundsNotify(HttpServletRequest request, HttpServletResponse response) {
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();
try {
// 处理通知参数
String body = HttpUtils.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
// 签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if (!wechatPay2ValidatorForRequest.validate(request)) {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
//处理退款单
orderInfoService.processRefund(bodyMap);
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
serviceimpl
/**
* 微信申请退款接口
*
* @description:微信申请退款API
* @author: Chris
* @date: 2021/12/29 10:04
* @Param orderRefundBO:
* @return: int
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void orderRefund(RefundRecord refundRecord) throws Exception {
// 根据单号查询订单状态
OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().lambda()
.eq(OrderInfo::getDelFlag, "1")
.eq(OrderInfo::getOrderCode, refundRecord.getOutTradeNo()));
if ("3".equals(orderInfo.getOrderState()) && "1".equals(orderInfo.getRefundState())) {
// 根据单号查询记录是否已存在
RefundRecord refundRecord1 = refundRecordService.getOne(new QueryWrapper<RefundRecord>().lambda()
.eq(RefundRecord::getOutTradeNo, refundRecord.getOutTradeNo()));
if (refundRecord1 != null) {// 根据单号查询记录是否已存在,返回
return;
}
RefundRecord Record = new RefundRecord();
BeanUtils.copyProperties(refundRecord, Record);
BigDecimal refund = new BigDecimal(Record.getRefund());
BigDecimal divide = refund.divide(BigDecimal.valueOf(100));
Record.setTotal(divide.toString());
Record.setRefund(divide.toString());
refundRecordService.save(Record);
} else {
return;
}
// 1.获取返回参数存入退款表
// refundRecord.setId(IdWorker.getIdStr());
// 2.调用微信申请退款API
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
HttpPost httpPost = new HttpPost(url);
// 请求body参数
Gson gson = new Gson();
Map paramsMap = new HashMap();
// 订单编号
paramsMap.put("out_trade_no", refundRecord.getOutTradeNo());
// 退款单编号
paramsMap.put("out_refund_no", refundRecord.getOutTradeNo());
// 通知地址
paramsMap.put("notify_url", "http://hyzj.rzocean.com/hyzj/orderinfo/orderInfo/refunds/notify");
Map amountMap = new HashMap();
// 退款金额
amountMap.put("refund", new BigDecimal(refundRecord.getRefund()));
// 原订单金额
amountMap.put("total", new BigDecimal(refundRecord.getRefund()));
// 退款币种
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
// 参数转换为json
String jsonParams = gson.toJson(paramsMap);
StringEntity entity = new StringEntity(jsonParams, "utf-8");
// 设置请求报文格式
entity.setContentType("application/json");
// 请求报文放入请求对象
httpPost.setEntity(entity);
// 响应报文格式
httpPost.setHeader("Accept", "application/json");
// 完成签名并执行请求,且完成验签
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
// 解析响应结果
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200 && statusCode != 204) {
throw new RuntimeException("退款异常,响应码 = " + statusCode + ",退款返回结果 = " + bodyAsString);
}
// 3.修改退款记录信息
refundRecordService.updateRefund(bodyAsString);
orderInfoMapper.updateRefundStateByOrderCode(refundRecord.getOutTradeNo(), "2");//2退款申请通过
} finally {
response.close();
}
}
/**
* @description:微信查询退款API
* @author: Chris
* @date: 2021/12/29 14:53
* @Param refundNo:
* @return: java.lang.String
*/
@Override
public String queryRefund(String refundNo) throws Exception {
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + refundNo;
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
// 完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200 && statusCode != 204) {
throw new RuntimeException("查询退款异常,响应码 = " + statusCode + ",查询退款返回结果 = " + bodyAsString);
}
JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
// 查询退款信息
List<RefundRecord> refundRecordList = refundRecordService.list(new QueryWrapper<RefundRecord>().lambda().eq(RefundRecord::getOutTradeNo, refundNo));
refundRecordList.forEach(refundRecord -> {
if (!refundRecord.getStatus().equals(jsonObject.get("status"))) {
refundRecord.setStatus((String) jsonObject.get("status"));
refundRecord.setSuccessTime(new Date());
refundRecordService.updateById(refundRecord);
}
});
return bodyAsString;
} finally {
response.close();
}
}
/**
* @description:对称解密
* @author: Chris
* @date: 2021/12/29 21:17
* @Param bodyMap:
* @return: java.lang.String
*/
private String decryptFromResource(Map<String, Object> bodyMap) 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(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}
/**
* @description:处理退款
* @author: Chris
* @date: 2021/12/29 21:11
* @Param bodyMap:
* @return: void
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processRefund(Map<String, Object> bodyMap) throws Exception {
// 解密报文
String plainText = decryptFromResource(bodyMap);
// 转map
Gson gson = new Gson();
HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
String orderNo = (String) plainTextMap.get("out_trade_no");
// if(lock.tryLock()){
// try {
// 订单状态是否处于正在退款中
String orderStatus = refundRecordService.getOrderStatus(orderNo);
if (!orderStatus.equals("PROCESSING")) {
return;
}
// 修改退款记录状态
refundRecordService.updateRefund(plainText);
// } finally {
// 主动释放锁
// lock.unlock();
// }
// }
}
五、相关工具类
AesUtil
package org.jeecg.modules.orderinfo.xcxutil;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AesUtil {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
public AesUtil(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
this.aesKey = key;
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
PayUtil
package org.jeecg.modules.orderinfo.xcxutil;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class PayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
PayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.HMACSHA256);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
QRCodeUtils
package org.jeecg.modules.orderinfo.xcxutil;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import static com.google.zxing.client.j2se.MatrixToImageWriter.toBufferedImage;
public class QRCodeUtils {
private static final String CHARSET = "utf-8";
public static String creatRrCode(String contents, int width, int height) {
String binary = null;
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(
contents, BarcodeFormat.QR_CODE, width, height, hints);
// 1、读取文件转换为字节数组
ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedImage image = toBufferedImage(bitMatrix);
//转换成png格式的IO流
ImageIO.write(image, "png", out);
byte[] bytes = out.toByteArray();
// 2、将字节数组转为二进制
BASE64Encoder encoder = new BASE64Encoder();
binary = encoder.encodeBuffer(bytes).trim();
} catch (WriterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "data:image/png;base64,"+binary;
}
/**
* image流数据处理
*
* @author ianly
*/
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
return image;
}
/**
* 解析二维码解析,此方法是解析Base64格式二维码图片
* baseStr:base64字符串,data:image/png;base64开头的
*/
public static String deEncodeByBase64(String baseStr) {
String content = null;
BufferedImage image;
BASE64Decoder decoder = new BASE64Decoder();
byte[] b=null;
try {
int i = baseStr.indexOf("data:image/png;base64,");
baseStr = baseStr.substring(i+"data:image/png;base64,".length());//去掉base64图片的data:image/png;base64,部分才能转换为byte[]
b = decoder.decodeBuffer(baseStr);//baseStr转byte[]
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(b);//byte[] 转BufferedImage
image = ImageIO.read(byteArrayInputStream);
LuminanceSource source = new BufferedImageLuminanceSource(image);
Binarizer binarizer = new HybridBinarizer(source);
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解码
// System.out.println("图片中内容: ");
// System.out.println("content: " + result.getText());
content = result.getText();
} catch (IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
return content;
}
/**
* 解析二维码,此方法解析一个路径的二维码图片
* path:图片路径
*/
public static String deEncodeByPath(String path) {
String content = null;
BufferedImage image;
try {
image = ImageIO.read(new File(path));
LuminanceSource source = new BufferedImageLuminanceSource(image);
Binarizer binarizer = new HybridBinarizer(source);
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解码
// System.out.println("图片中内容: ");
// System.out.println("content: " + result.getText());
content = result.getText();
} catch (IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
return content;
}
/* public static void main(String[] args) {
String binary = QRCodeUtils.creatRrCode("https://blog.csdn.net/ianly123", 200,200);
System.out.println(binary);
}*/
}
WXPayXmlUtil
package org.jeecg.modules.orderinfo.xcxutil;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* 2018/7/3
*/
public final class WXPayXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}