wlzboy
2025-12-02 d294abb765e4ed349907c92ce313689c6299ba7d
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
178
179
package com.ruoyi.payment.infrastructure.channel.wechat;
 
import com.ruoyi.payment.domain.model.PaymentOrder;
import com.ruoyi.payment.infrastructure.config.WechatPayConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 微信支付v2客户端
 * 
 * @author ruoyi
 */
@Slf4j
@Component
public class WxPayV2Client {
 
    private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private static final String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
    private static final String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
 
    @Autowired
    private WechatPayConfig wechatPayConfig;
 
    @Autowired
    private WxPayUtil wxPayUtil;
 
    /**
     * 统一下单 - Native支付
     */
    public String unifiedOrder(PaymentOrder order) throws Exception {
        log.info("调用微信统一下单接口,订单ID: {}", order.getId());
 
        // 构造请求参数
        Map<String, String> params = new HashMap<>();
        params.put("appid", wechatPayConfig.getAppId());
        params.put("mch_id", wechatPayConfig.getMchId());
        params.put("nonce_str", generateNonceStr());
        params.put("body", order.getSubject());
        params.put("out_trade_no", String.valueOf(order.getId())); // 使用订单ID作为商户订单号
        params.put("total_fee", String.valueOf(order.getAmount())); // 单位:分
        params.put("spbill_create_ip", "127.0.0.1");
        params.put("notify_url", wechatPayConfig.getNotifyUrl());
        params.put("trade_type", "NATIVE");
        
        if (order.getDescription() != null && !order.getDescription().isEmpty()) {
            params.put("detail", order.getDescription());
        }
 
        // 生成签名
        String sign = wxPayUtil.generateSign(params);
        params.put("sign", sign);
 
        // 转换为XML
        String requestXml = wxPayUtil.mapToXml(params);
        log.info("微信统一下单请求: {}", requestXml);
 
        // 发送HTTP请求
        String responseXml = doPost(UNIFIED_ORDER_URL, requestXml);
        log.info("微信统一下单响应: {}", responseXml);
 
        // 解析响应
        Map<String, String> response = wxPayUtil.xmlToMap(responseXml);
 
        // 验证响应签名
        if (!wxPayUtil.verifySign(response)) {
            throw new RuntimeException("微信返回签名验证失败");
        }
 
        // 检查返回状态
        if (!"SUCCESS".equals(response.get("return_code"))) {
            throw new RuntimeException("微信统一下单失败: " + response.get("return_msg"));
        }
 
        if (!"SUCCESS".equals(response.get("result_code"))) {
            throw new RuntimeException("微信统一下单业务失败: " + response.get("err_code_des"));
        }
 
        // 返回code_url
        String codeUrl = response.get("code_url");
        if (codeUrl == null || codeUrl.isEmpty()) {
            throw new RuntimeException("微信未返回code_url");
        }
 
        log.info("微信统一下单成功,code_url: {}", codeUrl);
        return codeUrl;
    }
 
    /**
     * 查询订单
     */
    public Map<String, String> queryOrder(String outTradeNo) throws Exception {
        log.info("查询微信订单,商户订单号: {}", outTradeNo);
 
        Map<String, String> params = new HashMap<>();
        params.put("appid", wechatPayConfig.getAppId());
        params.put("mch_id", wechatPayConfig.getMchId());
        params.put("out_trade_no", outTradeNo);
        params.put("nonce_str", generateNonceStr());
 
        String sign = wxPayUtil.generateSign(params);
        params.put("sign", sign);
 
        String requestXml = wxPayUtil.mapToXml(params);
        String responseXml = doPost(ORDER_QUERY_URL, requestXml);
 
        Map<String, String> response = wxPayUtil.xmlToMap(responseXml);
 
        if (!wxPayUtil.verifySign(response)) {
            throw new RuntimeException("查询订单响应签名验证失败");
        }
 
        if (!"SUCCESS".equals(response.get("return_code"))) {
            throw new RuntimeException("查询订单失败: " + response.get("return_msg"));
        }
 
        return response;
    }
 
    /**
     * 关闭订单
     */
    public void closeOrder(String outTradeNo) throws Exception {
        log.info("关闭微信订单,商户订单号: {}", outTradeNo);
 
        Map<String, String> params = new HashMap<>();
        params.put("appid", wechatPayConfig.getAppId());
        params.put("mch_id", wechatPayConfig.getMchId());
        params.put("out_trade_no", outTradeNo);
        params.put("nonce_str", generateNonceStr());
 
        String sign = wxPayUtil.generateSign(params);
        params.put("sign", sign);
 
        String requestXml = wxPayUtil.mapToXml(params);
        String responseXml = doPost(CLOSE_ORDER_URL, requestXml);
 
        Map<String, String> response = wxPayUtil.xmlToMap(responseXml);
 
        if (!"SUCCESS".equals(response.get("return_code"))) {
            throw new RuntimeException("关闭订单失败: " + response.get("return_msg"));
        }
 
        log.info("关闭微信订单成功");
    }
 
    /**
     * 发送POST请求
     */
    private String doPost(String url, String xmlData) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "text/xml;charset=UTF-8");
            httpPost.setEntity(new StringEntity(xmlData, "UTF-8"));
 
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity, "UTF-8");
            }
        }
    }
 
    /**
     * 生成随机字符串
     */
    private String generateNonceStr() {
        return java.util.UUID.randomUUID().toString().replace("-", "");
    }
}