diff --git a/src/main/java/com/sczx/pay/controller/PaymentController.java b/src/main/java/com/sczx/pay/controller/PaymentController.java index b66131a..1382774 100644 --- a/src/main/java/com/sczx/pay/controller/PaymentController.java +++ b/src/main/java/com/sczx/pay/controller/PaymentController.java @@ -3,6 +3,7 @@ package com.sczx.pay.controller; import com.sczx.pay.dto.PaymentRequest; import com.sczx.pay.dto.PaymentResponse; import com.sczx.pay.dto.RefundRequest; +import com.sczx.pay.entity.CompanyWechatConfig; import com.sczx.pay.mapper.CompanyWechatConfigMapper; import com.sczx.pay.service.WechatPayService; import com.sczx.pay.utils.WXPayUtil; @@ -186,18 +187,27 @@ public class PaymentController { // 解析XML数据 Map notifyMap = WXPayUtil.xmlToMap(xmlData); + //WechatRefundNotify notify = WXPayUtil.xmlToEntity(xmlData, WechatRefundNotify.class); - Long companyId = companyWechatConfigMapper.getCompanyIdByMchId(notifyMap.get("mch_id")); + CompanyWechatConfig wechatConfig = companyWechatConfigMapper.getCompanyIdByMchId(notifyMap.get("mch_id")); + //CompanyWechatConfig wechatConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(companyId); + //Long companyId = companyWechatConfigMapper.getCompanyIdByMchId(notifyMap.getMchid()); + + Boolean isRefundSuccess; // 验证签名 - if (!wechatPayService.verifyNotifySign(companyId, notifyMap)) { - logger.warn("微信退款通知签名验证失败,公司ID: {}", companyId); - return buildResponse("FAIL", "签名失败"); + try { + Map decryptMap = wechatPayService.decryptRefundNotify(xmlData, wechatConfig.getApikey()); + isRefundSuccess = wechatPayService.processRefundNotify(decryptMap); + } catch (Exception e){ + logger.warn("微信退款通知签名验证失败,公司ID: {}", wechatConfig.getId()); + return buildResponse("FAIL", "解密失败"); } + String returnCode = notifyMap.get("return_code"); if (!"SUCCESS".equals(returnCode)) { - logger.warn("微信退款通知返回失败,公司ID: {}: {}", companyId, notifyMap.get("return_msg")); + logger.warn("微信退款通知返回失败,公司ID: {}: {}", wechatConfig.getId(), notifyMap.get("return_msg")); return buildResponse("FAIL", "返回失败"); } @@ -207,13 +217,12 @@ public class PaymentController { String refundStatus = notifyMap.get("refund_status"); // 更新数据库中的退款状态 - boolean success = wechatPayService.processRefundNotify(companyId, notifyMap); - if (success) { + if (isRefundSuccess) { logger.info("退款处理完成,公司ID: {}, 退款单号: {}, 微信退款单号: {}, 状态: {}", - companyId, outRefundNo, refundId, refundStatus); + wechatConfig.getId(), outRefundNo, refundId, refundStatus); return buildResponse("SUCCESS", "OK"); } else { - logger.error("更新退款状态失败,公司ID: {}, 退款单号: {}", companyId, outRefundNo); + logger.error("更新退款状态失败,公司ID: {}, 退款单号: {}", wechatConfig.getId(), outRefundNo); return buildResponse("FAIL", "更新退款状态失败"); } diff --git a/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java b/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java index c9f44cf..b57ad6b 100644 --- a/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java +++ b/src/main/java/com/sczx/pay/mapper/CompanyWechatConfigMapper.java @@ -17,6 +17,6 @@ public interface CompanyWechatConfigMapper { CompanyWechatConfig getWechatConfigByCompanyId(@Param("companyId") Long companyId); - @Select("SELECT id FROM zc_company WHERE wechat_receiving_account = #{mchId}") - Long getCompanyIdByMchId(@Param("mchId") String mchId); + @Select("SELECT id,wechat_receiving_account AS mchId, wechat_key AS apikey FROM zc_company WHERE wechat_receiving_account = #{mchId} limit 1") + CompanyWechatConfig getCompanyIdByMchId(@Param("mchId") String mchId); } diff --git a/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java b/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java index 756593c..b8e9fce 100644 --- a/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java +++ b/src/main/java/com/sczx/pay/mapper/OrderPayMapper.java @@ -5,6 +5,7 @@ import com.sczx.pay.entity.PayStatus; import com.sczx.pay.entity.PaymentRecord; import org.apache.ibatis.annotations.*; +import java.math.BigDecimal; import java.util.Date; @Mapper @@ -46,6 +47,14 @@ public interface OrderPayMapper { "where payment_id = #{paymentId} and del_flag = '0')") OrderMain getOrderStatusByOrderNo(@Param("paymentId") String paymentId); + @Update("update zc_order_main as om,zc_order_sub as os set os.transaction_id = #{refundId},os.pay_status = #{payStatus} os.amount = #{refundFee},os.update_time = #{updateTime}" + + " where om.order_id = os.order_id and os.payment_id = #{outTradeNo} and os.suborder_type = 'FD_DEPOSIT'" ) + int updateSubOrderRefundStatus(@Param("outTradeNo") String outTradeNo, + @Param("payStatus") String payStatus, + @Param("refundFee") BigDecimal refundFee, + @Param("updateTime") Date updateTime, + @Param("refundId") String refundId); + } diff --git a/src/main/java/com/sczx/pay/service/WechatPayService.java b/src/main/java/com/sczx/pay/service/WechatPayService.java index 4ddb027..b49eb17 100644 --- a/src/main/java/com/sczx/pay/service/WechatPayService.java +++ b/src/main/java/com/sczx/pay/service/WechatPayService.java @@ -240,6 +240,7 @@ public class WechatPayService { public Map refund(RefundRequest request) throws Exception { // 根据companyId获取微信支付配置 CompanyWechatConfig companyConfig = companyWechatConfigMapper.getWechatConfigByCompanyId(request.getCompanyId()); + PaymentRecord paymentRecord = paymentRecordMapper.getPaymentRecordByOutTradeNo(request.getOutTradeNo()); if (companyConfig == null) { Map errorResult = new HashMap<>(); errorResult.put("return_code", "FAIL"); @@ -260,7 +261,7 @@ public class WechatPayService { Map reqData = new HashMap<>(); reqData.put("out_trade_no", request.getOutTradeNo()); reqData.put("out_refund_no", request.getOutRefundNo()); - reqData.put("total_fee", String.valueOf(request.getTotalFee())); + reqData.put("total_fee", String.valueOf(paymentRecord.getTotalFee())); reqData.put("refund_fee", String.valueOf(request.getRefundFee())); reqData.put("notify_url", refundNotifyUrl); @@ -417,88 +418,44 @@ public class WechatPayService { } } - /** - * 处理退款成功通知并更新退款状态 - */ - @Transactional - public boolean processRefundSuccessNotify(Long companyId, Map notifyData) { - try { - String outRefundNo = notifyData.get("out_refund_no"); - String refundId = notifyData.get("refund_id"); - String refundStatus = notifyData.get("refund_status"); - - // 根据退款状态更新退款记录 - String statusDesc = ""; - switch (refundStatus) { - case "SUCCESS": - statusDesc = "退款成功"; - break; - case "REFUNDCLOSE": - statusDesc = "退款关闭"; - break; - case "PROCESSING": - statusDesc = "退款处理中"; - break; - case "CHANGE": - statusDesc = "退款异常"; - break; - default: - statusDesc = "未知状态"; - } - - int updated = refundRecordMapper.updateRefundStatus( - outRefundNo, - refundStatus, - statusDesc, - refundId, - "SUCCESS".equals(refundStatus) ? new Date() : null, // 退款成功时间 - new Date() // 更新时间 - ); - - if (updated > 0) { - logger.info("退款记录状态已更新,退款单号: {}, 微信退款单号: {}, 状态: {}", outRefundNo, refundId, refundStatus); - // TODO: 在这里调用其他业务服务更新实际订单退款状态 - return true; - } else { - logger.warn("未找到对应的退款记录,退款单号: {}", outRefundNo); - return false; - } - } catch (Exception e) { - logger.error("处理退款成功通知异常,退款单号: {}", notifyData.get("out_refund_no"), e); - return false; - } - } - /** * 处理退款通知并更新退款状态 */ @Transactional - public boolean processRefundNotify(Long companyId, Map notifyData) { + public boolean processRefundNotify(Map notifyData) { + try { String outRefundNo = notifyData.get("out_refund_no"); String refundId = notifyData.get("refund_id"); String refundStatus = notifyData.get("refund_status"); String outTradeNo = notifyData.get("out_trade_no"); + BigDecimal refundFee = new BigDecimal(notifyData.get("refund_fee")); // 根据退款状态更新退款记录 String statusDesc = ""; + String payStatus = ""; switch (refundStatus) { case "SUCCESS": statusDesc = "退款成功"; + payStatus = "REFUND_SUCCESS"; break; case "REFUNDCLOSE": statusDesc = "退款关闭"; + payStatus = "REFUND_SUCCESS"; break; case "PROCESSING": statusDesc = "退款处理中"; + payStatus = "REFUNDING"; break; case "CHANGE": statusDesc = "退款异常"; + payStatus = "REFUND_ERROR"; break; default: statusDesc = "未知状态"; + payStatus = "REFUND_ERROR"; } - + orderPayMapper.updateSubOrderRefundStatus(outTradeNo,payStatus,refundFee,new Date(),refundId); int updated = refundRecordMapper.updateRefundStatus( outRefundNo, refundStatus, @@ -510,7 +467,6 @@ public class WechatPayService { if (updated > 0) { logger.info("微信退款记录状态已更新,退款单号: {}, 微信退款单号: {}, 状态: {}", outRefundNo, refundId, refundStatus); - orderPayMapper.updateRefundOrderStatus(outTradeNo,"REFUND_SUCCESS",outRefundNo); return true; } else { logger.warn("未找到对应的微信退款记录,退款单号: {}", outRefundNo); @@ -522,4 +478,35 @@ public class WechatPayService { } } + + public Map decryptRefundNotify(String xmlData, String apiKey) throws Exception { + try { + // 解析XML数据 + Map notifyData = WXPayUtil.xmlToMap(xmlData); + + // 检查是否包含req_info字段 + if (!notifyData.containsKey("req_info")) { + logger.warn("微信退款通知中不包含req_info字段"); + return notifyData; + } + + // 获取加密的req_info + String reqInfo = notifyData.get("req_info"); + + // 解密req_info + String decryptedReqInfo = WXPayUtil.decryptReqInfo(reqInfo, apiKey); + + // 解析解密后的XML数据 + Map decryptedData = WXPayUtil.xmlToMap(decryptedReqInfo); + + // 将解密后的数据合并到原始通知数据中 + notifyData.putAll(decryptedData); + + return notifyData; + } catch (Exception e) { + logger.error("处理微信退款通知失败", e); + throw new Exception("处理微信退款通知失败: " + e.getMessage(), e); + } + } + } diff --git a/src/main/java/com/sczx/pay/utils/WXPayUtil.java b/src/main/java/com/sczx/pay/utils/WXPayUtil.java index 0ca32d0..6434741 100644 --- a/src/main/java/com/sczx/pay/utils/WXPayUtil.java +++ b/src/main/java/com/sczx/pay/utils/WXPayUtil.java @@ -22,6 +22,26 @@ import java.io.StringWriter; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.*; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; +import java.security.Security; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.InvalidAlgorithmParameterException; public class WXPayUtil { @@ -164,4 +184,60 @@ public class WXPayUtil { Map data = xmlToMap(xmlString); return isSignatureValid(data, key); } + + + /** + * 将XML字符串转换为指定类型的实体对象 + * + * @param xmlString XML字符串 + * @param clazz 实体类Class + * @param 实体类型 + * @return 实体对象 + * @throws Exception 转换异常 + */ + public static T xmlToEntity(String xmlString, Class clazz) throws Exception { + JAXBContext context = JAXBContext.newInstance(clazz); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return clazz.cast(unmarshaller.unmarshal(new StringReader(xmlString))); + } + + + public static String decryptReqInfo(String reqInfo, String apiKey) throws Exception { + try { + // 步骤1: 对加密串A做base64解码,得到加密串B + byte[] encryptedData = Base64.decodeBase64(reqInfo); + + // 步骤2: 对商户key做md5,得到32位小写key* + String key = DigestUtils.md5Hex(apiKey).toLowerCase(); + + // 步骤3: 用key*对加密串B做AES-256-ECB解密(PKCS7Padding) + byte[] decryptedData = decrypt(encryptedData, key); + + return new String(decryptedData, "UTF-8"); + } catch (Exception e) { + logger.error("解密微信退款通知req_info失败", e); + throw new Exception("解密失败: " + e.getMessage(), e); + } + } + + /** + * AES解密方法 + * + * @param encryptedData 加密数据 + * @param key 解密密钥 + * @return 解密后的字节数组 + * @throws Exception 解密异常 + */ + private static byte[] decrypt(byte[] encryptedData, String key) throws Exception { + try { + SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + return cipher.doFinal(encryptedData); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | + IllegalBlockSizeException | BadPaddingException e) { + logger.error("AES解密失败", e); + throw new Exception("AES解密失败: " + e.getMessage(), e); + } + } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..e594345 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,53 @@ + + + + + + + + + ${PATTERN} + + + + + + ${LOG_FILE_PATH}/${hostname}/info.%d{yyyy-MM-dd}.log + 31 + + + + ${PATTERN} + + + + WARN + DENY + NEUTRAL + + + ERROR + DENY + NEUTRAL + + + + + + WARN + + ${LOG_FILE_PATH}/${hostname}/warn.log + true + + ${PATTERN} + + + + + + + + + \ No newline at end of file