无关风月
2025-04-30 4adb656ffd2c3660e07d224dd483e7479d48b46e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package com.dsh.activity.util.wx;
 
import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil;
import com.dsh.activity.util.wx.WxPayAesUtil;
import com.dsh.activity.util.wx.WxV3PayConfig;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
 
@RestController
@RequestMapping("/api/appPayment") // 路径前缀,与常量中配置的回调URL匹配
public class WxPayNotifyController {
 
    private static final Logger log = LoggerFactory.getLogger(WxPayNotifyController.class);
 
    private final ObjectMapper objectMapper;
 
    @Autowired
    public WxPayNotifyController(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
 
    /**
     * 接收微信支付结果通知
     * URL需要与 WXPayConstants.WECHAT_PAY_NOTIFY_URL 匹配
     */
    @PostMapping("/weChatPayNotify")
    public ResponseEntity<Map<String, String>> handleWeChatPayNotify(HttpServletRequest request, @RequestBody String requestBody) {
 
        Map<String, String> responseMap = new HashMap<>();
        try {
            // 1. 验证签名 (使用工具类,它会处理证书)
            boolean verifyResult = WXPaySignatureCertificateUtil.verifyNotify(request, requestBody);
            if (!verifyResult) {
                log.error("微信支付通知验签失败!");
                responseMap.put("code", "FAIL");
                responseMap.put("message", "验签失败");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST); // 返回错误状态码
            }
            log.info("微信支付通知验签成功。");
 
            // 2. 解析通知体
            JsonNode notifyData = objectMapper.readTree(requestBody);
            String eventType = notifyData.get("event_type").asText();
 
            // 只处理支付成功的通知类型
            if ("TRANSACTION.SUCCESS".equals(eventType)) {
                log.info("处理支付成功通知...");
                JsonNode resourceNode = notifyData.get("resource");
                String associatedData = resourceNode.get("associated_data").asText();
                String nonce = resourceNode.get("nonce").asText();
                String ciphertext = resourceNode.get("ciphertext").asText();
 
                // 3. 解密关键信息
                String decryptedDataJson = WxPayAesUtil.decrypt(WxV3PayConfig.apiV3Key, associatedData, nonce, ciphertext);
                log.info("解密后的支付通知信息: {}", decryptedDataJson);
                JsonNode decryptedData = objectMapper.readTree(decryptedDataJson);
 
                // 4. 处理业务逻辑
                String outTradeNo = decryptedData.get("out_trade_no").asText();
                String transactionId = decryptedData.get("transaction_id").asText();
                String tradeState = decryptedData.get("trade_state").asText();
                // ... 获取其他需要的信息,例如支付金额、用户openid等
 
                // TODO: 在这里实现你的业务逻辑:
                // 1. 根据 outTradeNo 查询你的数据库订单状态。
                // 2. 判断订单是否已经处理过(防止重复处理通知)。
                // 3. 如果订单未处理且 tradeState 为 SUCCESS,则更新订单状态为支付成功。
                // 4. 记录 transactionId (微信支付订单号)。
                // 5. 执行后续业务流程(如发货、增加积分等)。
                // 6. 如果处理失败,可以考虑记录日志并后续重试,但仍需返回成功给微信,避免微信重复通知。
 
                log.info("业务逻辑处理完成,商户订单号: {}, 微信订单号: {}", outTradeNo, transactionId);
 
            } else {
                log.warn("收到非支付成功类型的通知: {}", eventType);
                // 其他类型的通知,根据需要处理或忽略
            }
 
            // 5. 返回成功响应给微信平台
            responseMap.put("code", "SUCCESS");
            responseMap.put("message", "成功");
            return new ResponseEntity<>(responseMap, HttpStatus.OK);
 
        } catch (Exception e) {
            log.error("处理微信支付通知异常", e);
            responseMap.put("code", "FAIL");
            responseMap.put("message", "处理失败");
            // 即使处理失败,也尽量返回成功给微信,避免重复通知轰炸,然后在后台处理异常。
            // 但如果验签失败,可以返回错误状态码。
            return new ResponseEntity<>(responseMap, HttpStatus.INTERNAL_SERVER_ERROR); // 或者返回OK,根据策略定
        }
    }
 
 
    /**
     * 接收微信退款结果通知
     * URL需要与 WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL 匹配
     */
    @PostMapping("/weChatPayRefundsNotify")
    public ResponseEntity<Map<String, String>> handleWeChatRefundsNotify(HttpServletRequest request, @RequestBody String requestBody) {
 
        Map<String, String> responseMap = new HashMap<>();
        try {
            // 1. 验证签名
            boolean verifyResult = WXPaySignatureCertificateUtil.verifyNotify(request, requestBody);
             if (!verifyResult) {
                log.error("微信退款通知验签失败!");
                responseMap.put("code", "FAIL");
                responseMap.put("message", "验签失败");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            }
            log.info("微信退款通知验签成功。");
 
            // 2. 解析通知体
            JsonNode notifyData = objectMapper.readTree(requestBody);
            String eventType = notifyData.get("event_type").asText();
 
             // 处理退款成功或异常的通知
            if ("REFUND.SUCCESS".equals(eventType) || "REFUND.ABNORMAL".equals(eventType) || "REFUND.CLOSED".equals(eventType)) {
                 log.info("处理退款通知,类型: {}", eventType);
                JsonNode resourceNode = notifyData.get("resource");
                String associatedData = resourceNode.get("associated_data").asText();
                String nonce = resourceNode.get("nonce").asText();
                String ciphertext = resourceNode.get("ciphertext").asText();
 
                // 3. 解密关键信息
                String decryptedDataJson = WxPayAesUtil.decrypt(WxV3PayConfig.apiV3Key, associatedData, nonce, ciphertext);
                log.info("解密后的退款通知信息: {}", decryptedDataJson);
                JsonNode decryptedData = objectMapper.readTree(decryptedDataJson);
 
                // 4. 处理业务逻辑
                String outTradeNo = decryptedData.get("out_trade_no").asText();
                String outRefundNo = decryptedData.get("out_refund_no").asText();
                String refundStatus = decryptedData.get("refund_status").asText(); // SUCCESS, CLOSED, ABNORMAL
                // ... 获取其他需要的信息,例如退款金额、微信退款单号 refund_id 等
 
                 // TODO: 在这里实现你的退款业务逻辑:
                // 1. 根据 outRefundNo 查询你的数据库退款单状态。
                // 2. 判断退款单是否已经处理过。
                // 3. 根据 refundStatus 更新退款单状态。
                // 4. 如果退款成功 (SUCCESS),可能需要执行一些操作,如返还库存、通知用户等。
                // 5. 如果退款关闭或异常,也需要记录状态。
 
                log.info("退款业务逻辑处理完成,商户订单号: {}, 商户退款单号: {}, 退款状态: {}", outTradeNo, outRefundNo, refundStatus);
 
            } else {
                log.warn("收到非退款类型的通知: {}", eventType);
            }
 
            // 5. 返回成功响应给微信平台
            responseMap.put("code", "SUCCESS");
            responseMap.put("message", "成功");
            return new ResponseEntity<>(responseMap, HttpStatus.OK);
 
        } catch (Exception e) {
            log.error("处理微信退款通知异常", e);
            responseMap.put("code", "FAIL");
            responseMap.put("message", "处理失败");
            return new ResponseEntity<>(responseMap, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
 
}