WxPayManagerImpl.java
16.8 KB
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
package com.hotent.lpg.user.manager.impl;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.hotent.base.exception.BaseException;
import com.hotent.base.util.BeanUtils;
import com.hotent.lpg.common.enums.DdZffsEnum;
import com.hotent.lpg.common.enums.DdlyEnum;
import com.hotent.lpg.common.enums.DdztEnum;
import com.hotent.lpg.common.enums.SysEnum;
import com.hotent.lpg.common.model.WCzzfpz;
import com.hotent.lpg.common.model.WDd;
import com.hotent.lpg.user.dao.CzzfpzDao;
import com.hotent.lpg.user.dao.DdDao;
import com.hotent.lpg.user.manager.WxPayManager;
import com.hotent.lpg.user.util.LocalDateTimeUtils;
import com.hotent.lpg.user.util.WxPayConfiguration;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.rmi.ServerError;
import java.time.LocalDateTime;
import java.util.HashMap;
@Service
@Slf4j
public class WxPayManagerImpl implements WxPayManager {
@Value("${payment.hostUrl}")
private String hostUrl;
@Resource
private DdDao ddDao;
@Resource
private CzzfpzDao czzfpzDao;
/**
* 发起退款请求
*
* @param ddid
* @return
*/
@Override
public String initiateRefundOrder(String ddid) {
// 1. 查询订单信息
WDd wDd = ddDao.selectById(ddid);
if (!DdztEnum.PAID.getCode().equals(wDd.getFDdzt())) {
throw new RuntimeException("订单状态不正确");
}
if (!(wDd.getFDdly().equals(DdlyEnum.xsxd.getCode()) && wDd.getFZffs().equals(DdZffsEnum.WX_PAY.getCode()) && wDd.getFSfzf().equals(SysEnum.Y.getCode()))) {
throw new RuntimeException("订单状态不正确");
}
// 2. 获取支付配置
WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery().eq(WCzzfpz::getFCzid, wDd.getFCzid()).eq(WCzzfpz::getFZflx, wDd.getFZffs()));
if (wCzzfpz == null) {
throw new RuntimeException("支付配置不存在");
}
// 3. 初始化微信支付服务
WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
try {
// 4. 调用微信退款接口
// 这里需要构造退款请求对象,具体参数根据实际情况填写
// 注意:这里需要根据实际的微信支付SDK版本和退款接口文档来调整
// 示例代码可能需要根据实际情况修改
String tkdh = RandomUtil.randomString(32);
WxPayRefundRequest refundRequest = new WxPayRefundRequest();
refundRequest.setTransactionId(wDd.getFZfdh());
refundRequest.setOutTradeNo(wDd.getFDddh()); // 订单号
refundRequest.setOutRefundNo(RandomUtil.randomString(32)); // 退款单号
refundRequest.setTotalFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue()); // 订单总金额
refundRequest.setRefundFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue()); // 退款金额
refundRequest.setNotifyUrl(hostUrl + "/user/wxPay/handleRefundSuccessCallback");
// 发起退款请求
Object payment = wxPayService.refund(refundRequest);
// 返回结果
return tkdh;
} catch (WxPayException e) {
log.error("退款失败: {}", e.getErrCodeDes(), e);
throw new RuntimeException("退款失败: " + e.getErrCodeDes());
}
}
/**
* 处理退款成功回调
*
* @param xmlData
* @return
*/
@Override
public String handleRefundSuccessCallback(String xmlData) {
// 将XML格式的回调数据转换为WxPayOrderNotifyResult对象
WxPayOrderNotifyResult rs = WxPayOrderNotifyResult.fromXML(xmlData);
// 获取回调中的返回码
String returnCode = rs.getReturnCode();
// 如果返回码不是"SUCCESS",则生成失败响应并返回
if (!returnCode.equals("SUCCESS")) {
return generateXmlResponse("FAIL", "退款失败", returnCode);
}
// 获取回调中的appid和mchId
String appid = rs.getAppid();
String mchId = rs.getMchId();
// 从数据库中查询对应的应用配置信息
WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery()
.eq(WCzzfpz::getFAppid, appid)
.eq(WCzzfpz::getFMchid, mchId)
.last("LIMIT 1"));
// 如果没有找到对应的配置信息,则抛出异常
if (wCzzfpz == null) {
throw new BaseException("退款失败,请联系管理员。");
}
// 根据配置信息创建WxPayService对象
WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
try {
// 解析退款通知结果
WxPayRefundNotifyResult notifyResult = wxPayService.parseRefundNotifyResult(xmlData);
// 获取退款请求信息
WxPayRefundNotifyResult.ReqInfo reqInfo = notifyResult.getReqInfo();
// 提取退款相关信息
String refundId = reqInfo.getRefundId();
String successTime = reqInfo.getSuccessTime();
String transactionId = reqInfo.getTransactionId();
String outTradeNo = reqInfo.getOutTradeNo();
// 从数据库中查询订单信息
WDd wDd = ddDao.selectOne(Wrappers.<WDd>lambdaQuery()
.eq(WDd::getFZfdh, transactionId)
.eq(WDd::getFDddh, outTradeNo));
// 如果没有查询到订单,则生成失败响应并返回
if (wDd == null) {
return generateXmlResponse("FAIL", "未查询到订单", outTradeNo);
}
// 如果订单已经退款,则生成失败响应并返回
if (wDd.getFSftk().equals(SysEnum.Y.getCode())) {
return generateXmlResponse("FAIL", "订单已退款", outTradeNo);
}
// 更新订单状态为已退款,并记录相关信息
wDd.setFDdzt(DdztEnum.REFUNDED.getCode());
wDd.setFSftk(SysEnum.Y.getCode());
wDd.setFTkdh(refundId);
wDd.setFTksj(LocalDateTimeUtils.parse(successTime));
// 保存更新后的订单信息
ddDao.updateById(wDd);
// 记录日志信息
log.info("微信退款成功: {}", wDd.getFDddh());
// 生成成功响应并返回
return generateXmlResponse("SUCCESS", "成功", outTradeNo);
} catch (WxPayException e) {
// 记录微信支付异常信息
log.error("微信退款回调处理失败: {}", e.getErrCodeDes(), e);
// 生成失败响应并返回
return generateXmlResponse("FAIL", e.getErrCodeDes(), "");
} catch (Exception e) {
// 记录其他异常信息
log.error("处理退款回调时发生未知错误: ", e);
// 生成失败响应并返回
return generateXmlResponse("FAIL", "处理退款回调时发生未知错误", "");
}
}
/**
* 处理支付成功回调
*
* @param xmlData
* @return
*/
@Override
public String handlePaySuccessCallback(String xmlData) {
// 从 XML 数据中提取支付单号
String outTradeNo = extractValueFromXML(xmlData, "out_trade_no");
//先用订单号找到订单
WDd wDd = ddDao.selectOne(new LambdaQueryWrapper<WDd>().eq(WDd::getFDddh, outTradeNo));
//在用 场站信息 拿到场站对应得支付配置
WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery().eq(WCzzfpz::getFCzid, wDd.getFCzid()).eq(WCzzfpz::getFZflx, wDd.getFZffs()));
WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
//下面依旧是走微信sdk,查询订单和验证订单,为了保证数据得安全性,还是重复验证一次
String dddh = null;
try {
WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData);
// 获取订单编号
dddh = notifyResult.getOutTradeNo();
// 获取微信支付单号
String transactionId = notifyResult.getTransactionId();
wDd = ddDao.selectOne(new LambdaQueryWrapper<WDd>().eq(WDd::getFDddh, dddh));
if (wDd == null) {
log.warn("订单不存在: {}", dddh);
return generateXmlResponse("FAIL", "订单不存在", dddh);
}
// 检查订单状态是否已支付
if (SysEnum.Y.getCode().equals(wDd.getFDdzt())) {
log.warn("订单 {} 已经处理过,忽略本次通知", dddh);
return generateXmlResponse("SUCCESS", "已处理过", dddh);
}
BigDecimal payPrice = wDd.getFDdje();
if (payPrice.multiply(new BigDecimal(100)).intValue() != notifyResult.getTotalFee()) {
log.warn("付款金额与订单金额不等: {}", dddh);
return generateXmlResponse("FAIL", "付款金额与订单金额不等", dddh);
}
String timeEnd = notifyResult.getTimeEnd();
LocalDateTime paymentTime = LocalDateTimeUtils.parse(timeEnd);
wDd.setFFksj(paymentTime);
wDd.setFSfzf(SysEnum.Y.getCode());
wDd.setFDdzt(DdztEnum.PAID.getCode());
wDd.setFZfdh(transactionId);
ddDao.updateById(wDd);
log.info("订单支付成功: {}", dddh);
return generateXmlResponse("SUCCESS", "成功", dddh);
} catch (WxPayException e) {
log.error("支付回调处理失败: {}", e.getErrCodeDes(), e);
return generateXmlResponse("FAIL", e.getErrCodeDes(), dddh);
}
}
/**
* 生成预支付订单
*
* @param wDd
* @param openId
* @return
*/
@Override
public HashMap<String, Object> generatePrepayOrder(WDd wDd, String openId) {
try {
WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery().eq(WCzzfpz::getFCzid, wDd.getFCzid()).eq(WCzzfpz::getFZflx, wDd.getFZffs()));
if (BeanUtils.isEmpty(wCzzfpz) || wCzzfpz == null) {
throw new RuntimeException("支付配置不存在");
}
String tradeType = wDd.getFZflx();
if (StrUtil.isBlank(tradeType)) {
throw new RuntimeException("支付方式错误");
}
WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest();
wxPayUnifiedOrderRequest.setAppid(wCzzfpz.getFAppid());
wxPayUnifiedOrderRequest.setOpenid(null);
wxPayUnifiedOrderRequest.setBody(wDd.getFDddh());
wxPayUnifiedOrderRequest.setOutTradeNo(wDd.getFDddh());
wxPayUnifiedOrderRequest.setTotalFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue());
wxPayUnifiedOrderRequest.setTradeType(tradeType);
JSONObject scene_info = new JSONObject();
scene_info.put("id", "LPG");
scene_info.put("name", "燃气");
wxPayUnifiedOrderRequest.setSceneInfo(scene_info.toString());
wxPayUnifiedOrderRequest.setNotifyUrl(hostUrl + "/user/wxPay/handlePaySuccessCallback"); // 支付回调地址,开放不用登录
wxPayUnifiedOrderRequest.setSpbillCreateIp("127.0.0.1");
// trade_type=APP时 移动应用内的支付场景
if ("APP".equals(wxPayUnifiedOrderRequest.getTradeType())) {
wxPayUnifiedOrderRequest.setAppid(wCzzfpz.getFAppid());
}
// trade_type=NATIVE时 线下消费场景
if ("NATIVE".equals(wxPayUnifiedOrderRequest.getTradeType())) {
wxPayUnifiedOrderRequest.setProductId(wxPayUnifiedOrderRequest.getOutTradeNo());
}
// 公众号内或者微信内的网页支付
if ("JSAPI".equals(wxPayUnifiedOrderRequest.getTradeType())) {
wxPayUnifiedOrderRequest.setOpenid(openId);
}
// 手机浏览器中的支付场景
if ("MWEB".equals(wxPayUnifiedOrderRequest.getTradeType())) {
wxPayUnifiedOrderRequest.setSceneInfo(null);
}
WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
Object payment = wxPayService.createOrder(wxPayUnifiedOrderRequest);
HashMap<String, Object> resultData = new HashMap<String, Object>();
resultData.put("payment", payment);
resultData.put("ddid", wDd.getId());
resultData.put("dddh", wDd.getFDddh());
return resultData;
} catch (WxPayException e) {
if ("INVALID_REQUEST".equals(e.getErrCode())) {
throw new RuntimeException("订单号重复,请重新下单");
}
throw new RuntimeException(e.getMessage());
}
}
/**
* 生成微信回调响应
*
* @param returnCode
* @param returnMsg
* @param outTradeNo
* @return
*/
private String generateXmlResponse(String returnCode, String returnMsg, String outTradeNo) {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder;
try {
dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.newDocument();
Element rootElement = doc.createElement("xml");
doc.appendChild(rootElement);
Element returnCodeElement = doc.createElement("return_code");
returnCodeElement.appendChild(doc.createTextNode(returnCode));
rootElement.appendChild(returnCodeElement);
Element returnMsgElement = doc.createElement("return_msg");
returnMsgElement.appendChild(doc.createTextNode(returnMsg));
rootElement.appendChild(returnMsgElement);
Element outTradeNoElement = doc.createElement("out_trade_no");
outTradeNoElement.appendChild(doc.createTextNode(outTradeNo));
rootElement.appendChild(outTradeNoElement);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
return writer.toString();
} catch (Exception e) {
log.error("构建 XML 响应失败", e);
return "<xml><return_code>FAIL</return_code><return_msg>处理失败</return_msg></xml>";
}
}
/**
* 从 XML 数据中提取指定参数的值
*
* @param xmlData
* @param paramName
* @return
*/
private String extractValueFromXML(String xmlData, String paramName) {
// 解析 XML 数据以提取指定参数的值
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(xmlData));
Document doc = dBuilder.parse(is);
Element element = doc.getDocumentElement();
Node paramNode = element.getElementsByTagName(paramName).item(0).getFirstChild();
return paramNode != null ? paramNode.getNodeValue() : null;
} catch (Exception e) {
log.error("解析 XML 数据失败: {}", e.getMessage(), e);
return null;
}
}
}