From 423b7d1934e5bf4c28aa818d95350e6ed5f18696 Mon Sep 17 00:00:00 2001 From: quyixiao <2621048238@qq.com> Date: Sat, 8 Nov 2025 14:31:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/data/dao/vv/VvReverseOrderDao.java | 7 + .../java/com/heyu/api/jsapi/JsapiPrepay.java | 3 +- .../vv/AppWeiXinRefundNotifyController.java | 125 ++++++++++++++++++ 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppWeiXinRefundNotifyController.java diff --git a/api-mapper/src/main/java/com/heyu/api/data/dao/vv/VvReverseOrderDao.java b/api-mapper/src/main/java/com/heyu/api/data/dao/vv/VvReverseOrderDao.java index a13e7c2..b51cf28 100644 --- a/api-mapper/src/main/java/com/heyu/api/data/dao/vv/VvReverseOrderDao.java +++ b/api-mapper/src/main/java/com/heyu/api/data/dao/vv/VvReverseOrderDao.java @@ -12,6 +12,7 @@ import com.heyu.api.data.entity.vv.VvReverseOrderEntity; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lz.mybatis.plugin.annotations.Column; import com.lz.mybatis.plugin.annotations.IF; +import com.lz.mybatis.plugin.annotations.LIMIT; import com.lz.mybatis.plugin.annotations.OrderBy; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -51,4 +52,10 @@ public interface VvReverseOrderDao extends BaseMapper { @IF @Column(VvReverseOrderEntity.create_timestamp) Long maxCreateTimestamp, @IF @OrderBy(VvReverseOrderEntity.create_timestamp) String createTimestampSort ); + + + + @OrderBy(VvReverseOrderEntity.id_) + @LIMIT + VvReverseOrderEntity selectVvReverseOrderByTradeOrderId(Long tradeOrderId); } \ No newline at end of file diff --git a/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java b/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java index c231f05..afc61d9 100644 --- a/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java +++ b/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java @@ -246,8 +246,7 @@ public class JsapiPrepay { request.amount.currency = "CNY"; // 必填 【退款币种】 符合ISO 4217标准的三位字母代码,固定传:CNY,代表人民币。 String HOST = refundNotifyUrl; String METHOD = "POST"; - String PATH = "/v3/refund/domestic/refunds"; - String uri = PATH; + String uri = "/v3/refund/domestic/refunds"; String reqBody = WXPayUtility.toJson(request); Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppWeiXinRefundNotifyController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppWeiXinRefundNotifyController.java new file mode 100644 index 0000000..b6ec677 --- /dev/null +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppWeiXinRefundNotifyController.java @@ -0,0 +1,125 @@ +package com.heyu.api.controller.vv; + + +import com.alibaba.fastjson.JSONObject; +import com.heyu.api.alibaba.request.mm.enums.ReverseStatusEnums; +import com.heyu.api.data.dao.vv.VvReverseOrderDao; +import com.heyu.api.data.dao.vv.VvTradeOrderDao; +import com.heyu.api.data.dao.vv.VvTradeOrderLineDao; +import com.heyu.api.data.entity.vv.VvReverseOrderEntity; +import com.heyu.api.data.entity.vv.VvTradeOrderLineEntity; +import com.heyu.api.data.utils.NumberUtil; +import com.heyu.api.data.utils.StringUtils; +import com.heyu.api.jsapi.JsapiPrepay; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +@Slf4j +@RestController +@RequestMapping("/app/weixin") +public class AppWeiXinRefundNotifyController { + + + @Autowired + private VvTradeOrderLineDao vvTradeOrderLineDao; + + + // 微信支付APIv3密钥 + @Value("${eb.config.weixin.pay.apiv3key}") + private String apiV3key; + + @Autowired + private VvTradeOrderDao tradeOrderDao; + + @Value("${eb.config.weixin.pay.mchid}") + private String mchid; + + + @Autowired + private JsapiPrepay jsapiPrepay; + + + @Autowired + private VvReverseOrderDao reverseOrderDao; + + + //退款回调 + @PostMapping("/refundNotifyUrl") + @Transactional + public Object refundNotifyUrl(@RequestBody String jsonData) throws Exception { + //转为map格式 + Map jsonMap = JSONObject.parseObject(jsonData, Map.class); + + //退款成功后返回一个加密字段resource,以下为解密 + /** + * 解密需要从resource参数中,获取到ciphertext,nonce,associated_data这三个参数进行解密 + */ + String resource = JSONObject.toJSONString(jsonMap.get("resource")); + JSONObject object = JSONObject.parseObject(resource); + + String ciphertext = String.valueOf(object.get("ciphertext")); + String nonce = String.valueOf(object.get("nonce")); + String associated_data = String.valueOf(object.get("associated_data")); + + String resultStr = decryptToString(associated_data.getBytes("UTF-8"), nonce.getBytes("UTF-8"), ciphertext); + log.info("AppWeiXinRefundNotifyController refundNotifyUrl resultStr = " + resultStr); + Map reqInfo = JSONObject.parseObject(resultStr, Map.class); + String refund_status = reqInfo.get("refund_status");//退款状态 + String out_trade_no = reqInfo.get("out_trade_no"); //订单号 + + Map parm = new HashMap<>(); + if (!StringUtils.isEmpty(refund_status) && "SUCCESS".equals(refund_status)) { + VvReverseOrderEntity vvReverseOrderEntity = reverseOrderDao.selectVvReverseOrderByTradeOrderId(NumberUtil.objToLong(out_trade_no)); + vvReverseOrderEntity.setGmtRefunded(new Date()); + vvReverseOrderEntity.setStatus(ReverseStatusEnums.refunded.getStatus()); + reverseOrderDao.updateVvReverseOrderById(vvReverseOrderEntity); + List vvTradeOrderLineEntityList = vvTradeOrderLineDao.selectVvTradeOrderLineByTradeOrderId(NumberUtil.objToLong(out_trade_no)); + for (VvTradeOrderLineEntity vvTradeOrderLineEntity : vvTradeOrderLineEntityList) { + vvTradeOrderLineEntity.setReverseStatus(ReverseStatusEnums.refunded.getStatus()); + vvTradeOrderLineDao.updateVvTradeOrderLineById(vvTradeOrderLineEntity); + } + //你自己的业务 + parm.put("code", "SUCCESS"); + parm.put("message", "成功"); + } else { + parm.put("code", "FAIL"); + parm.put("message", "失败"); + throw new RuntimeException("退款失败"); + } + return parm; //返回给前端的参数 + } + + //退款回调 解密数据 + public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + SecretKeySpec key = new SecretKeySpec(apiV3key.getBytes(), "AES");//todo 这里的apiV3key是你的商户APIV3密钥 + GCMParameterSpec spec = new GCMParameterSpec(128, nonce);//规定为128 + + 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 (Exception e) { + throw new IllegalArgumentException(e); + } + } + + +}