前期准备
- Maven依赖
由于具体是哪个已经分不清了,有些是微信支付要用到的,有些是退款要用到的,有些是解码要用到的。
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-common</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.5.ALL</version>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 微信支付sdk -->
<!-- Apache Commons Codec库 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
- 微信支付工具类
这个类是配置微信支付的基本信息的如appId,mchId这些,建议用我的,因为后面要用到这个配置类。
注意:除了一般用到的appid这些东西外,还得去官网申请证书,如apiclient_cert.p12,pem这些。路径配置可以配置本地的地址或者服务器下的地址,按我下面那样子配置就可以。
public class PayConfig implements WXPayConfig{
private byte[] certData;
public PayConfig() throws Exception {
//服务器的路径,按照你自己的来
File file = new File("/usr/local/cert/apiclient_cert.p12");
// 本地测试的路径,按照你自己的来
// File file = new File("D:\\*****\\*****\\*****\\apiclient_cert.p12");
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
public String getAppID() {
return "****************";
}
public String getMchID() {
return "********";
}
public String getKey() {
return "************";
}
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
}
- 解码类
拿来用就可以 ,是用于解密退款回调返回的信息req_info的。第一个decrypt方法是解密手机号的,这里用不到,只是顺便贴一下,要用可以拿去用。
public class AesUtil {
public static String decrypt(String encryptedData, String sessionKey, String iv) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// 初始化加密库
Security.addProvider(new BouncyCastleProvider());
byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData);
byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
byte[] ivBytes = Base64.getDecoder().decode(iv);
// 构造解密器
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(ivBytes));
cipher.init(Cipher.DECRYPT_MODE, keySpec, params);
byte[] decryptedDataBytes = cipher.doFinal(encryptedDataBytes);
return new String(decryptedDataBytes, StandardCharsets.UTF_8);
}
public static String aesDecrypt(String secretInfo, String rawKey) throws Exception {
try {
SecretKeySpec key = new SecretKeySpec(DigestUtils.md5Hex(getContentBytes(rawKey, "utf-8")).toLowerCase().getBytes(), "AES");
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.getDecoder().decode(secretInfo)),"UTF-8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误" + charset);
}
}
}
退款接口
@注解按需添加
transactionId为微信支付订单号,money表示你要退款的钱,reason表示原因。
@Data
@Accessors(chain = true)
public class ConfirmCancel{
private Long transactionId;
private String money;
private String reason;
}
微信退款以分为单位,不论是总额还是要退款的钱。 (有些字段和微信官方提供的不同,是因为版本不一样,用就行了)
@Log
@ApiOperation(value = "退款")
@PostMapping("confirmCancel")
public Object confirmCancel(@RequestBody ConfirmCancel confirmCancel) throws Exception {//serviceOrderId
if (poDto != null){
String transactionId = confirmCancel.getTransactionId();
Double stringMonry = Double.valueOf(confirmCancel.getMoney());
//退款以分为单位,且为int类型,不能有小数点。前端输入的money也得按这个来。如0.01元就得是1分
//totalMoney为总额,你可以从之前支付订单存的数据库里面取,也可以直接从前端传,我这里取的是数据库里的,按你自己的来
Double totalMoney = *****Dto.get******Vo().getTotalMoney()*100;
Integer intToalMoney = totalMoney.intValue();
Double refundMoney = stringMonry*100;
Integer intRefundMoney = refundMoney.intValue();
String url = "https://(填写域名)/(填写回调接口url)"; //如果你需要回调的话
if (stringMonry <= totalMoney) {
PayConfig config = new PayConfig();
WXPay wxpay = new WXPay(config);
String refundNo = String.valueOf(UUID.randomUUID());
Map<String, String> map = new HashMap<>();
map.put("appid", config.getAppID());
map.put("mch_id", config.getMchID());
map.put("refund_desc", confirmCancel.getReason()); //退款原因
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("transaction_id", transactionId);
map.put("out_refund_no", refundNo);
map.put("total_fee", intToalMoney + ""); //总价格
map.put("refund_fee", intRefundMoney + ""); //退款价格
map.put("notify_url",url); //回调地址
String sign = WXPayUtil.generateSignature(map, config.getKey());
map.put("sign", sign);
Map<String, String> refund = wxpay.refund(map);
System.out.println(refund); //返回的详情,可以根据这个来看成没成功
String return_code = refund.get("return_code");
String return_msg = refund.get("return_msg");
} else {
return buildFailure("超出原有金额");
}
}
return buildFailure("失败");
}
实现效果
请求参数
回调接口
注意:如果使用回调,得先在退款的接口中,设置你的notify_url,这个url为你的回调接口地址
再次提醒,@注解按需求添加。
@Log
@PostMapping("refundNotifyResult")
public Object refundNotifyResult(@RequestBody String xmlData) throws Exception {
System.out.println("进入回调");
//TODO 具体业务处理
logger.info(xmlData); //回调接收到的是xml格式的数据
PayConfig config = new PayConfig();
Map<String, String> xmlMap = WXPayUtil.xmlToMap(xmlData); //转为map格式
System.out.println("xmlMap==>" + xmlMap);
//退款成功后返回一个加密字段req_info,以下为解密
//解密的方法在上述准备工作的AesUtil类里
String req_info = xmlMap.get("req_info");
String resultStr = AesUtil.aesDecrypt(req_info,config.getKey());
Map<String, String> reqInfo = WXPayUtil.xmlToMap(resultStr);
System.out.println("reqInfo===>" + reqInfo);
String out_trade_no = reqInfo.get("out_trade_no");
String return_code = xmlMap.get("return_code");
Map<String,Object> parm = new HashMap<>();
if (StringUtils.isNotBlank(return_code) && StringUtils.equals(return_code, SUCCESS)) {
//你自己的业务操作
*********************************
parm.put("return_code",SUCCESS);
parm.put("req_info",reqInfo);
}
return parm; //返回给前端的参数
}
以下为打印的数据,因为涉及隐私,所以大部分都打码了。