回调处理

This commit is contained in:
gong
2026-01-14 14:41:30 +08:00
parent e341630e82
commit 946fe4de4c
7 changed files with 285 additions and 11 deletions

View File

@@ -4,8 +4,12 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.CzgPayUtils;
import com.czg.PayCst;
import com.czg.constants.PayTypeConstants;
import com.czg.dto.req.WechatNotifyReqDto;
import com.czg.dto.req.WechatPayNotifyDataDto;
import com.czg.entity.CzgBaseRespParams;
import com.czg.exception.CzgException;
import com.czg.market.entity.MkShopConsumeDiscountRecord;
import com.czg.market.service.MkDistributionUserService;
import com.czg.market.service.MkShopConsumeDiscountRecordService;
@@ -15,15 +19,13 @@ import com.czg.order.entity.OrderPayment;
import com.czg.order.service.OrderInfoCustomService;
import com.czg.order.service.OrderPaymentService;
import com.czg.service.market.service.impl.AppWxServiceImpl;
import com.czg.third.wechat.WechatReqUtils;
import com.czg.utils.AssertUtil;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@@ -59,6 +61,48 @@ public class NotifyController {
return "success";
}
/**
* 原生支付回调
*/
@RequestMapping("/native/pay/{platform}")
public String pay(@PathVariable String platform, @RequestBody JSONObject json) {
if (PayCst.Platform.WECHAT.equalsIgnoreCase(platform)) {
// 微信
WechatNotifyReqDto reqDto = JSONObject.parseObject(json.toJSONString(), WechatNotifyReqDto.class);
log.info("【微信支付回调】收到微信支付回调 data: {}", JSONObject.toJSONString(reqDto));
String decrypted = WechatReqUtils.decryptRespParam(null, reqDto);
log.info("【微信支付回调】解密数据 {}", decrypted);
WechatPayNotifyDataDto dataDto = JSONObject.parseObject(decrypted, WechatPayNotifyDataDto.class);
return "success";
} else if (PayCst.Platform.ALIPAY.equalsIgnoreCase(platform)) {
// 支付宝
return "success";
}
throw new CzgException("不支持的支付平台");
}
/**
* 原生退款回调
*/
@RequestMapping("/native/refund/{platform}")
public String refund(@PathVariable String platform, @RequestBody JSONObject json) {
if (PayCst.Platform.WECHAT.equalsIgnoreCase(platform)) {
// 微信
WechatNotifyReqDto reqDto = JSONObject.parseObject(json.toJSONString(), WechatNotifyReqDto.class);
log.info("【微信退款回调】收到微信退款回调 data: {}", JSONObject.toJSONString(reqDto));
String decrypted = WechatReqUtils.decryptRespParam(null, reqDto);
log.info("【微信退款回调】解密数据 {}", decrypted);
return "success";
} else if (PayCst.Platform.ALIPAY.equalsIgnoreCase(platform)) {
// 支付宝
return "success";
}
throw new CzgException("不支持的支付平台");
}
@RequestMapping("/payCallBack")
public String notifyCallBack(@RequestBody CzgBaseRespParams respParams) {

View File

@@ -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;
}
}

View File

@@ -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公众号支付、小程序支付
* NATIVENative支付
* APPAPP支付
* MICROPAY付款码支付
* MWEBH5支付
* 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());
}
}

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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());

View File

@@ -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("解密失败");
}
}
/**
* 获取随机串
*/