回调处理
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package com.czg.dto.req;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author yjjie
|
||||
* @date 2025/12/23 21:47
|
||||
*/
|
||||
@Data
|
||||
public class WechatNotifyReqDto {
|
||||
|
||||
private String id;
|
||||
@JSONField(name = "create_time")
|
||||
private String createTime;
|
||||
@JSONField(name = "resource_type")
|
||||
private String resourceType;
|
||||
@JSONField(name = "event_type")
|
||||
private String eventType;
|
||||
private String summary;
|
||||
private Resource resource;
|
||||
|
||||
@Data
|
||||
public static class Resource {
|
||||
@JSONField(name = "resource_type")
|
||||
private String originalType;
|
||||
private String algorithm;
|
||||
private String ciphertext;
|
||||
@JSONField(name = "associated_data")
|
||||
private String associatedData;
|
||||
private String nonce;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.czg.dto.req;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author yjjie
|
||||
* @date 2025/12/23 22:21
|
||||
*/
|
||||
@Data
|
||||
public class WechatPayNotifyDataDto {
|
||||
|
||||
/**
|
||||
* 公众账号ID
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String mchid;
|
||||
|
||||
/**
|
||||
* 商户订单号
|
||||
*/
|
||||
@JSONField(name = "out_trade_no")
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
@JSONField(name = "transaction_id")
|
||||
private String transactionId;
|
||||
|
||||
/**
|
||||
* 交易类型
|
||||
* JSAPI:公众号支付、小程序支付
|
||||
* NATIVE:Native支付
|
||||
* APP:APP支付
|
||||
* MICROPAY:付款码支付
|
||||
* MWEB:H5支付
|
||||
* FACEPAY:刷脸支付
|
||||
*/
|
||||
@JSONField(name = "trade_type")
|
||||
private String tradeType;
|
||||
|
||||
/**
|
||||
* 交易状态
|
||||
* SUCCESS—支付成功
|
||||
* REFUND—转入退款
|
||||
* NOTPAY—未支付
|
||||
* CLOSED—已关闭
|
||||
* REVOKED—已撤销(刷卡支付)
|
||||
* USERPAYING--用户支付中
|
||||
* PAYERROR--支付失败(其他原因,如银行返回失败)
|
||||
*/
|
||||
@JSONField(name = "trade_state")
|
||||
private String tradeState;
|
||||
|
||||
/**
|
||||
* 交易状态描述
|
||||
*/
|
||||
@JSONField(name = "trade_state_desc")
|
||||
private String tradeStateDesc;
|
||||
|
||||
/**
|
||||
* 付款银行
|
||||
*/
|
||||
@JSONField(name = "bank_type")
|
||||
private String bankType;
|
||||
|
||||
/**
|
||||
* 商户数据包
|
||||
*/
|
||||
private String attach;
|
||||
|
||||
/**
|
||||
* 支付完成时间
|
||||
*/
|
||||
@JSONField(name = "success_time")
|
||||
private String successTime;
|
||||
|
||||
/**
|
||||
* 金额
|
||||
*/
|
||||
private Amount amount;
|
||||
|
||||
@Data
|
||||
public static class Amount {
|
||||
/**
|
||||
* 用户支付金额
|
||||
*/
|
||||
@JSONField(name = "payer_total")
|
||||
private Integer payerTotal;
|
||||
|
||||
/**
|
||||
* 货币类型
|
||||
*/
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 总金额
|
||||
*/
|
||||
private Integer total;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return "SUCCESS".equals(tradeState);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return "CLOSED".equals(tradeState);
|
||||
}
|
||||
|
||||
public Long getPayAmount() {
|
||||
return Long.valueOf(amount.getTotal());
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class AlipayIsvPayManager {
|
||||
model.setTotalAmount(getYuanAmountByFen(paramsDto.getAmount()));
|
||||
model.setSubject(paramsDto.getTitle());
|
||||
model.setBody(paramsDto.getBody());
|
||||
model.setNotifyUrl(paramsDto.getNotifyUrl());
|
||||
model.setNotifyUrl(paramsDto.getNotifyUrl() + "/" + PayCst.Platform.ALIPAY);
|
||||
|
||||
model.setExtendParams(new ExtendParams());
|
||||
CustomizedParams customizedParams = new CustomizedParams();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.czg.third.wechat;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 微信参数 AES 解密
|
||||
* @author GYJoker
|
||||
*/
|
||||
public class WechatAesUtil {
|
||||
static final int KEY_LENGTH_BYTE = 32;
|
||||
static final int TAG_LENGTH_BIT = 128;
|
||||
private final byte[] aesKey;
|
||||
public WechatAesUtil(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 {
|
||||
try {
|
||||
|
||||
// 调试信息:打印参数信息
|
||||
System.out.println("associatedData长度: " + (associatedData != null ? associatedData.length : 0));
|
||||
System.out.println("nonce长度: " + (nonce != null ? nonce.length : 0));
|
||||
System.out.println("ciphertext长度: " + ciphertext.length());
|
||||
|
||||
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);
|
||||
|
||||
// 处理 associatedData,确保使用空数组而非 null
|
||||
cipher.updateAAD(associatedData != null ? associatedData : new byte[0]);
|
||||
|
||||
byte[] decodedCiphertext = Base64.getDecoder().decode(ciphertext);
|
||||
System.out.println("解码后密文长度: " + decodedCiphertext.length);
|
||||
|
||||
byte[] plaintext = cipher.doFinal(decodedCiphertext);
|
||||
return new String(plaintext, StandardCharsets.UTF_8);
|
||||
} catch (AEADBadTagException e) {
|
||||
// 捕获标签不匹配异常,提供更详细的错误信息
|
||||
throw new GeneralSecurityException("解密失败: 标签不匹配,可能是密钥错误、数据被篡改或参数不匹配", e);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
throw new IllegalArgumentException("无效的密钥或参数", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.cipher.Signer;
|
||||
import com.wechat.pay.java.core.util.NonceUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.apache.bcel.generic.RET;
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.DocumentHelper;
|
||||
import org.dom4j.Element;
|
||||
@@ -58,7 +57,7 @@ public class WechatPayManager {
|
||||
reqData.put("sub_mchid", paramsDto.getMerchantId());
|
||||
reqData.put("description", paramsDto.getTitle());
|
||||
reqData.put("out_trade_no", paramsDto.getOrderNo());
|
||||
reqData.put("notify_url", paramsDto.getNotifyUrl());
|
||||
reqData.put("notify_url", paramsDto.getNotifyUrl() + "/" + PayCst.Platform.WECHAT);
|
||||
reqData.put("attach", paramsDto.getExtData());
|
||||
|
||||
JSONObject amount = new JSONObject();
|
||||
@@ -268,7 +267,7 @@ public class WechatPayManager {
|
||||
refundParam.put("out_trade_no", paramsDto.getOrderNo());
|
||||
refundParam.put("out_refund_no", paramsDto.getRefundOrderNo());
|
||||
refundParam.put("reason", paramsDto.getRefundReason());
|
||||
refundParam.put("notify_url", paramsDto.getRefundNotifyUrl());
|
||||
refundParam.put("notify_url", paramsDto.getRefundNotifyUrl() + "/" + PayCst.Platform.WECHAT);
|
||||
|
||||
JSONObject amount = new JSONObject();
|
||||
amount.put("total", paramsDto.getOrderAmount());
|
||||
|
||||
@@ -3,12 +3,11 @@ package com.czg.third.wechat;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.czg.dto.req.WechatNotifyReqDto;
|
||||
import com.czg.exception.CzgException;
|
||||
import com.czg.third.wechat.dto.config.WechatPayConfigDto;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.cipher.Signer;
|
||||
import com.wechat.pay.java.core.http.HttpClient;
|
||||
import com.wechat.pay.java.core.http.HttpMethod;
|
||||
import com.wechat.pay.java.core.util.NonceUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -16,7 +15,6 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -102,6 +100,28 @@ public class WechatReqUtils {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密微信回调报文
|
||||
*
|
||||
* @param configDto 配置
|
||||
* @param reqDto 请求报文
|
||||
* @return 解密后的报文
|
||||
*/
|
||||
public static String decryptRespParam(WechatPayConfigDto configDto, WechatNotifyReqDto reqDto) {
|
||||
if (configDto == null) {
|
||||
configDto = WechatPayConfigDto.getDefaultConfig();
|
||||
}
|
||||
WechatAesUtil aesUtil = new WechatAesUtil(configDto.getApiV3Key().getBytes(StandardCharsets.UTF_8));
|
||||
try {
|
||||
return aesUtil.decryptToString(
|
||||
reqDto.getResource().getAssociatedData() == null ? new byte[0] : reqDto.getResource().getAssociatedData().getBytes(StandardCharsets.UTF_8),
|
||||
reqDto.getResource().getNonce().getBytes(StandardCharsets.UTF_8), reqDto.getResource().getCiphertext());
|
||||
} catch (Exception e) {
|
||||
log.error("【微信支付回调】解密失败 {}", reqDto);
|
||||
throw new CzgException("解密失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机串
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user