202506170908
This commit is contained in:
parent
4d768ea54e
commit
d729c52fcf
|
|
@ -12,7 +12,12 @@ ruoyi:
|
|||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
captchaType: math
|
||||
|
||||
wechat:
|
||||
pay:
|
||||
app-id: wx73d0202b3c8a6d68
|
||||
mch-id: 1672571923
|
||||
api-key: sssssssssssssssssssssssssssssssS
|
||||
cert-path: wechat/apiclient_cert.p12
|
||||
# 七牛云配置
|
||||
qiniu:
|
||||
# 是否启用七牛云上传 true-启用七牛云 false-使用本地上传
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIELjCCAxagAwIBAgIUSSFh2nX1a5drX17lBR7WCwwoi7kwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjQwNDAzMDEzNTM5WhcNMjkwNDAyMDEzNTM5WjCBhzETMBEGA1UEAwwK
|
||||
MTY3MjU3MTkyMzEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQL
|
||||
DCropb/lronljY7lupzkurrlrrboo4XppbDlt6XnqIvmnInpmZDlhazlj7gxCzAJ
|
||||
BgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBANvnZX1T6o0xR1yOkQy17upOeNsGGE8pn5istcvc5/UTtcg8
|
||||
5aH39oAZ0wXruE41wR9bEM6c3FB27w5FcnQonUYgBZvU/jHlIrbogZK1UhvoQMqX
|
||||
SnKbiQx2oqLxiFXzZjWBz5g8E9Mss4xErRT8XmuE5alWnIc5ljGGYAUNt9qi5bpc
|
||||
wawSqoBYR1nJM1WtGwHWNUTCi2XYL2H7olPnJw/vv0dpQC3kNF/0bNALzM9ulyf3
|
||||
sESbFu6wOJhYqZRseiUQHyXRFGQOQ665rho7YJ4j6y51kfbUAIUp2U4yMf8009pQ
|
||||
du9XYId9TxjRt/tNxwkRVEIVukAgv90mZwALvyMCAwEAAaOBuTCBtjAJBgNVHRME
|
||||
AjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDov
|
||||
L2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUw
|
||||
REJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJF
|
||||
MTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IB
|
||||
AQASunlG7V6cH05d3z4pthRixzU4Rn5xSW8d09gshq1XA6BBCu6I7glqiEKotrai
|
||||
oLWrwHDKddCRSRuV3KKC0//fAwS/8j0pkFDI/Aad5TKS361uA38HaHda0bJQnKoB
|
||||
m1WinL+GsfsLr+SL17ZGZAJqPBeP/Vz/c41cUX2gMAXjkem7DGbtytQiVkyXxocL
|
||||
/qhlF3Hy3eiMI186ki5ctKkp6WISCRKHWjc1du8ri/T6fvnNv/Rls/GAtpxVBm7V
|
||||
uFem8oe5heDDe4/RlqPv7sY2Oybkq/9iUfFwYvUNv/yQfb7TiGBQkasAt+PVgI5g
|
||||
djd8S7m1zDTFWl4LeNBIYaS2
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDb52V9U+qNMUdc
|
||||
jpEMte7qTnjbBhhPKZ+YrLXL3Of1E7XIPOWh9/aAGdMF67hONcEfWxDOnNxQdu8O
|
||||
RXJ0KJ1GIAWb1P4x5SK26IGStVIb6EDKl0pym4kMdqKi8YhV82Y1gc+YPBPTLLOM
|
||||
RK0U/F5rhOWpVpyHOZYxhmAFDbfaouW6XMGsEqqAWEdZyTNVrRsB1jVEwotl2C9h
|
||||
+6JT5ycP779HaUAt5DRf9GzQC8zPbpcn97BEmxbusDiYWKmUbHolEB8l0RRkDkOu
|
||||
ua4aO2CeI+sudZH21ACFKdlOMjH/NNPaUHbvV2CHfU8Y0bf7TccJEVRCFbpAIL/d
|
||||
JmcAC78jAgMBAAECggEAQBF82jTWw2g2GFtZG1DuWMmgIH/4GShRXVQEa27jDd4+
|
||||
Oiaz6Kqr4MqlVrZErlSu/Ym0rd25up/MKmZFYF5s4/90YuB9ZuARayT2i13UnFjg
|
||||
d6X4hMeX+E+gNsUUKaaOgsxdRUdLp10Aur9Phiu4/q5rkxiu5h77CVbTJuXGSSZe
|
||||
VEtlUfXsyl5nLqnViqOam0cbD3vM0tKnAtHHootIcz3Bn5a9QGfBA7JKHqWpQIZp
|
||||
zkd0XcfbZdOLwk+I+HAenFQYK8mOpg999iNVOFOI0egsj9tES9cxkDMezUrYjr/W
|
||||
+I5KkggwigF0tjgvqcklqNdGIIlLak+jBDfWA2vwgQKBgQD3Sx6C/YafiEv30Phw
|
||||
K+kmWMT5X+bTJBF8uie62Rlzz6PQs9p7K7iHcWLPtAO93JR6O25/tWIeYWGVyWOF
|
||||
PyJLNoch+3aDw2e7K9gn14xMb9/rlAMEtKsM1g+W78TJfzGaeH4gGTkzBQYxaoV1
|
||||
Zi9LXGEXurpgIRgCYA+2mxIeAwKBgQDjpWmcAXEA6NQBfUWCxlI3xAv5R0G4Adtf
|
||||
ziKnJZnTxTbe7OZBMugAwxWtuNjJESg4RAPwUlmkaWO+pN4yu6dZsfphsgNUs6kG
|
||||
1x2LXB72E0bVo0xyMANkeYtgh6Vz9JIfm54CNHEbpf6udTKFAL4QmoW43v68FKSK
|
||||
6wR6j3ogYQKBgFBwqP4nBdGC/mMgIJAsWGzW10ve/DCWPPH7g8ztra/UTQ8oUdta
|
||||
/21fXqr9fXIW/F0+U9VK5R3Iw3t7xNmlDby1ggN1zrlRqL5jpq5fGQ4MHiR9QvYW
|
||||
5sUIJI4OdE6vBy2eIDjEu6xy5+7PHZZwpNW4uQSFAvceDLdJFBNsIdqNAoGAPRmK
|
||||
jPq+D9ZuALBU1dLRAnK2HAQKGY82CDLVjjNDZFmMbqz66Aj3yCyURvpUBTN7rg5j
|
||||
WD9iHATFZsftc4R2WFays0IKLtPBzDo0jdD95mIqIvEfdY55eqA8zn3Z5JOCHMWx
|
||||
1TmqGhelkcGW+6fFW8N9nK8NhAGuYDLQEu0rhEECgYAdsN/WQcG+IcHv/90AajQi
|
||||
a4gnybw1tuC7PlBDTtrSYbd3YuEZ3ocLTKINvADqh9/rgTaOHs6n4wS8av02XWw+
|
||||
AOUF2N2Qvb1nk5FDV87wBjeWDWR1wy8o5njBU45+mnMdelubpKHemkrqkMd0sd7B
|
||||
Pcd6BtcwZjvzB4UisErFqA==
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEFDCCAvygAwIBAgIUSfMroWK5C2gDN/vbxA/Igoszko0wDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjQwNDAzMDEzNTM4WhcNMjkwNDAyMDEzNTM4WjBuMRgwFgYDVQQDDA9U
|
||||
ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
|
||||
bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGEwJDTjERMA8GA1UEBwwIU2hlblpo
|
||||
ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCu+HNPatzFYBeTBflC
|
||||
fsbpLAz/5RgxMriwVxUsy7i2IdlcCX60ZM9W0a4ZpaR0o61njR0s67dTGDW17Kd5
|
||||
bQ8rJBParLSVh4sBn7kRcJFyBzcnjGWECDR5kUpuvV2MW7HzlogGZxWqD55/5ZHz
|
||||
n4QPi35axPRv/ZCx9KDz2iK86FBad9+CZeYBJ0KvDZDDDq4Y3N9b4e0LNNw5Pq4l
|
||||
IpuHU1Q8LPytf84UhRdyLh7ofgQmulIwISeqPy+IXB01brIYwh76affri90w8UyS
|
||||
cDraOC8cgjgWsMc5ZJiRjiPmmWTa2jFbUeh2mHoC4ik3CBGxHNOXtg7fEbYtxVCn
|
||||
qdQLAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
|
||||
kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
|
||||
aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
|
||||
RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
|
||||
MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAB1Dw7Gr+bmGtvL++lNY7KEeIywSTuHz2
|
||||
264LhBxFEX5fKaKdq/cy7Kw6n9c7Jbcu1aIbNb08eZ0h1veJGp8oJzDxpIE8m/g2
|
||||
AQHRfkOJidHEed+UvLb6dKD20nh/vVfdOKoV1NEvQZw6n3YuRjH+YzaZAeKA6T2E
|
||||
CxBF7apLC/FQ06hBZruZDr8GgaxzubkAZu8CHdbCDk4K3OuEOJjJT9fxgbiVhih5
|
||||
wtlupVbpnGKpXm+xqeti90EKUGh/rFv/3pz+mD4n5xioYcQrvOxNUYEGEAcNSEmK
|
||||
mEc2D13rJf7pBRm+nhjVee3qckW5cdQfj1hxgB2CQOY05Xt5Zba8KA==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -6212,4 +6212,63 @@ public class AppletController extends BaseController {
|
|||
return AppletControllerUtil.appletError("操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 云信交互式语音通知呼叫结果推送回调接口
|
||||
* 用于接收云信平台推送的交互式语音通知呼叫结果。
|
||||
* @param requestBody 云信平台推送的JSON字符串
|
||||
* @return 必须返回{"resultCode":"200"},否则云信认为推送失败
|
||||
*/
|
||||
@PostMapping("/api/voice/interactNotify/resultCallback")
|
||||
public Map<String, Object> voiceInteractNotifyResultCallback(@RequestBody String requestBody) {
|
||||
// 解析请求体(可根据业务需要保存或处理字段)
|
||||
try {
|
||||
JSONObject json = JSONObject.parseObject(requestBody);
|
||||
// 可提取字段:accountId、callId、calleeNumber、startCallTime、endTime、duration等
|
||||
// String accountId = json.getString("accountId");
|
||||
// String callId = json.getString("callId");
|
||||
// ... 其他字段
|
||||
// 这里可以根据业务需求进行日志记录、数据库保存等操作
|
||||
} catch (Exception e) {
|
||||
// 解析异常可记录日志
|
||||
}
|
||||
// 必须返回{"resultCode":"200"},否则云信会认为推送失败
|
||||
Map<String, Object> resp = new java.util.HashMap<>();
|
||||
resp.put("resultCode", "200");
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 云信交互式小号呼叫结果推送结果推送回调接口
|
||||
* 用于接收云信平台推送的交互式语音通知呼叫结果。
|
||||
* @param requestBody 云信平台推送的JSON字符串
|
||||
* @return 必须返回{"resultCode":"200"},否则云信认为推送失败
|
||||
*/
|
||||
@PostMapping("/api/voice/middleNumberAXB/resultCallback")
|
||||
public Map<String, Object> voiceInteractmiddleNumberAXBCallback(@RequestBody String requestBody) {
|
||||
// 解析请求体(可根据业务需要保存或处理字段)
|
||||
try {
|
||||
JSONObject json = JSONObject.parseObject(requestBody);
|
||||
// 可提取字段:accountId、callId、calleeNumber、startCallTime、endTime、duration等
|
||||
// String accountId = json.getString("accountId");
|
||||
// String callId = json.getString("callId");
|
||||
// ... 其他字段
|
||||
// 这里可以根据业务需求进行日志记录、数据库保存等操作
|
||||
} catch (Exception e) {
|
||||
// 解析异常可记录日志
|
||||
}
|
||||
// 必须返回{"resultCode":"200"},否则云信会认为推送失败
|
||||
Map<String, Object> resp = new java.util.HashMap<>();
|
||||
resp.put("resultCode", "200");
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2696,4 +2696,38 @@ public class AppletControllerUtil {
|
|||
dataPage.put("total", pageInfo.getTotal());
|
||||
return dataPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 云信交互式语音通知回调接口
|
||||
* <p>
|
||||
* 用于接收云信平台推送的语音通知结果,解析callId、result、message等字段。
|
||||
* 该方法可直接作为Controller层的回调接口方法使用。
|
||||
* </p>
|
||||
* @param requestBody 云信平台推送的JSON字符串
|
||||
* @return 标准响应结果
|
||||
*/
|
||||
public static Map<String, Object> voiceInteractNotifyCallback(String requestBody) {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
try {
|
||||
// 解析JSON请求体
|
||||
com.alibaba.fastjson2.JSONObject json = com.alibaba.fastjson2.JSONObject.parseObject(requestBody);
|
||||
String callId = json.getString("callId");
|
||||
String result = json.getString("result");
|
||||
String message = json.getString("message");
|
||||
|
||||
// 业务处理:可根据callId、result、message进行自定义逻辑
|
||||
// 例如:记录日志、更新数据库、通知业务系统等
|
||||
|
||||
// 返回标准响应
|
||||
resultMap.put("code", 200);
|
||||
resultMap.put("msg", "回调接收成功");
|
||||
resultMap.put("callId", callId);
|
||||
resultMap.put("result", result);
|
||||
resultMap.put("message", message);
|
||||
} catch (Exception e) {
|
||||
resultMap.put("code", 500);
|
||||
resultMap.put("msg", "回调处理异常: " + e.getMessage());
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
|
@ -35,22 +37,35 @@ import java.util.*;
|
|||
* @date 2025-01-03
|
||||
* @version 1.0
|
||||
*/
|
||||
@Component
|
||||
public class WechatPayUtil {
|
||||
|
||||
/**
|
||||
* 微信支付配置常量
|
||||
* 注意:实际使用时需要在配置文件中配置这些值
|
||||
* 微信支付配置(通过Spring配置注入)
|
||||
* 仅支持小程序支付(JSAPI)
|
||||
* 建议在application.yml或application.properties中配置如下:
|
||||
* wechat.pay.app-id=xxx
|
||||
* wechat.pay.mch-id=xxx
|
||||
* wechat.pay.api-key=xxx
|
||||
* wechat.pay.cert-path=xxx
|
||||
*/
|
||||
private static final String WECHAT_APP_ID = "your_wechat_appid";
|
||||
private static final String WECHAT_MCH_ID = "your_merchant_id";
|
||||
private static final String WECHAT_API_KEY = "your_api_key";
|
||||
private static final String WECHAT_CERT_PATH = "/path/to/cert.p12"; // 微信支付证书路径
|
||||
@Value("${wechat.pay.app-id}")
|
||||
private String wechatAppId;
|
||||
|
||||
@Value("${wechat.pay.mch-id}")
|
||||
private String wechatMchId;
|
||||
|
||||
@Value("${wechat.pay.api-key}")
|
||||
private String wechatApiKey;
|
||||
|
||||
@Value("${wechat.pay.cert-path}")
|
||||
private String wechatCertPath;
|
||||
|
||||
/**
|
||||
* 微信支付API地址
|
||||
* 微信支付API地址(仅保留小程序相关接口)
|
||||
*/
|
||||
private static final String WECHAT_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
|
||||
private static final String WECHAT_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
|
||||
private static final String WECHAT_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 统一下单
|
||||
private static final String WECHAT_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"; // 订单查询
|
||||
private static final String WECHAT_REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
|
||||
private static final String WECHAT_TRANSFER_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; // 企业付款
|
||||
|
||||
|
|
@ -68,109 +83,101 @@ public class WechatPayUtil {
|
|||
private static final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
/**
|
||||
* 统一下单 - 微信小程序支付
|
||||
*
|
||||
* @param orderInfo 订单信息
|
||||
* @return 支付参数Map
|
||||
* 小程序统一下单(JSAPI)
|
||||
* @param openid 用户openid
|
||||
* @param orderNo 商户订单号(唯一)
|
||||
* @param totalFee 支付金额(单位:分,整型)
|
||||
* @param body 商品描述
|
||||
* @param notifyUrl 支付回调通知地址
|
||||
* @param attach 附加数据(可选)
|
||||
* @return 统一下单结果,包含小程序端调起支付的参数
|
||||
*/
|
||||
public static Map<String, Object> unifiedOrder(Map<String, Object> orderInfo) {
|
||||
public Map<String, Object> unifiedOrder(String openid, String orderNo, int totalFee, String body, String notifyUrl, String attach) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 1. 参数验证
|
||||
String validationError = validateOrderParams(orderInfo);
|
||||
if (validationError != null) {
|
||||
result.put("success", false);
|
||||
result.put("message", validationError);
|
||||
return result;
|
||||
// 1. 参数校验
|
||||
if (openid == null || openid.trim().isEmpty()) {
|
||||
return failResult("用户openid不能为空");
|
||||
}
|
||||
if (orderNo == null || orderNo.trim().isEmpty()) {
|
||||
return failResult("订单号不能为空");
|
||||
}
|
||||
if (totalFee <= 0) {
|
||||
return failResult("支付金额必须大于0");
|
||||
}
|
||||
if (body == null || body.trim().isEmpty()) {
|
||||
return failResult("商品描述不能为空");
|
||||
}
|
||||
if (notifyUrl == null || notifyUrl.trim().isEmpty()) {
|
||||
return failResult("回调通知地址不能为空");
|
||||
}
|
||||
// 2. 构建参数
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", wechatAppId);
|
||||
params.put("mch_id", wechatMchId);
|
||||
params.put("nonce_str", generateNonceStr());
|
||||
params.put("body", body);
|
||||
params.put("out_trade_no", orderNo);
|
||||
params.put("total_fee", String.valueOf(totalFee)); // 单位:分
|
||||
params.put("spbill_create_ip", "127.0.0.1");
|
||||
params.put("notify_url", notifyUrl);
|
||||
params.put("trade_type", TRADE_TYPE_JSAPI);
|
||||
params.put("openid", openid);
|
||||
if (attach != null && !attach.trim().isEmpty()) {
|
||||
params.put("attach", attach);
|
||||
}
|
||||
|
||||
// 2. 构建统一下单参数
|
||||
Map<String, String> params = buildUnifiedOrderParams(orderInfo);
|
||||
|
||||
// 3. 生成签名
|
||||
String sign = generateSign(params, WECHAT_API_KEY);
|
||||
String sign = generateSign(params, wechatApiKey);
|
||||
params.put("sign", sign);
|
||||
|
||||
// 4. 发送请求
|
||||
String xmlRequest = mapToXml(params);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(WECHAT_PAY_URL, xmlRequest, String.class);
|
||||
|
||||
// 5. 解析响应
|
||||
Map<String, String> responseMap = xmlToMap(response.getBody());
|
||||
|
||||
if (SUCCESS_CODE.equals(responseMap.get("return_code")) &&
|
||||
SUCCESS_CODE.equals(responseMap.get("result_code"))) {
|
||||
|
||||
// 6. 构建小程序支付参数
|
||||
// 5. 处理响应
|
||||
if (SUCCESS_CODE.equals(responseMap.get("return_code")) && SUCCESS_CODE.equals(responseMap.get("result_code"))) {
|
||||
// 6. 构建小程序端调起支付参数
|
||||
Map<String, Object> payParams = buildMiniProgramPayParams(responseMap.get("prepay_id"));
|
||||
|
||||
result.put("success", true);
|
||||
result.put("payParams", payParams);
|
||||
result.put("prepayId", responseMap.get("prepay_id"));
|
||||
result.put("message", "统一下单成功");
|
||||
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "统一下单失败:" +
|
||||
(responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") :
|
||||
responseMap.get("return_msg")));
|
||||
result = failResult("统一下单失败:" + (responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") : responseMap.get("return_msg")));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "统一下单异常:" + e.getMessage());
|
||||
result = failResult("统一下单异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param transactionId 微信订单号(可选,与orderNo二选一)
|
||||
* @return 查询结果
|
||||
* 查询订单状态(小程序支付)
|
||||
* @param orderNo 商户订单号
|
||||
* @param transactionId 微信订单号(可选)
|
||||
* @return 查询结果,包含订单状态等信息
|
||||
*/
|
||||
public static Map<String, Object> queryOrder(String orderNo, String transactionId) {
|
||||
public Map<String, Object> queryOrder(String orderNo, String transactionId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 1. 参数验证
|
||||
if ((orderNo == null || orderNo.trim().isEmpty()) &&
|
||||
(transactionId == null || transactionId.trim().isEmpty())) {
|
||||
result.put("success", false);
|
||||
result.put("message", "订单号和微信订单号不能同时为空");
|
||||
return result;
|
||||
if ((orderNo == null || orderNo.trim().isEmpty()) && (transactionId == null || transactionId.trim().isEmpty())) {
|
||||
return failResult("订单号和微信订单号不能同时为空");
|
||||
}
|
||||
|
||||
// 2. 构建查询参数
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", WECHAT_APP_ID);
|
||||
params.put("mch_id", WECHAT_MCH_ID);
|
||||
params.put("appid", wechatAppId);
|
||||
params.put("mch_id", wechatMchId);
|
||||
params.put("nonce_str", generateNonceStr());
|
||||
|
||||
if (orderNo != null && !orderNo.trim().isEmpty()) {
|
||||
params.put("out_trade_no", orderNo.trim());
|
||||
}
|
||||
if (transactionId != null && !transactionId.trim().isEmpty()) {
|
||||
params.put("transaction_id", transactionId.trim());
|
||||
}
|
||||
|
||||
// 3. 生成签名
|
||||
String sign = generateSign(params, WECHAT_API_KEY);
|
||||
String sign = generateSign(params, wechatApiKey);
|
||||
params.put("sign", sign);
|
||||
|
||||
// 4. 发送请求
|
||||
String xmlRequest = mapToXml(params);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(WECHAT_QUERY_URL, xmlRequest, String.class);
|
||||
|
||||
// 5. 解析响应
|
||||
Map<String, String> responseMap = xmlToMap(response.getBody());
|
||||
|
||||
if (SUCCESS_CODE.equals(responseMap.get("return_code")) &&
|
||||
SUCCESS_CODE.equals(responseMap.get("result_code"))) {
|
||||
|
||||
if (SUCCESS_CODE.equals(responseMap.get("return_code")) && SUCCESS_CODE.equals(responseMap.get("result_code"))) {
|
||||
Map<String, Object> orderInfo = new HashMap<>();
|
||||
orderInfo.put("tradeState", responseMap.get("trade_state"));
|
||||
orderInfo.put("tradeStateDesc", responseMap.get("trade_state_desc"));
|
||||
|
|
@ -179,125 +186,44 @@ public class WechatPayUtil {
|
|||
orderInfo.put("totalFee", responseMap.get("total_fee"));
|
||||
orderInfo.put("cashFee", responseMap.get("cash_fee"));
|
||||
orderInfo.put("timeEnd", responseMap.get("time_end"));
|
||||
|
||||
result.put("success", true);
|
||||
result.put("orderInfo", orderInfo);
|
||||
result.put("message", "查询订单成功");
|
||||
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "查询订单失败:" +
|
||||
(responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") :
|
||||
responseMap.get("return_msg")));
|
||||
result = failResult("查询订单失败:" + (responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") : responseMap.get("return_msg")));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "查询订单异常:" + e.getMessage());
|
||||
result = failResult("查询订单异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 找朋友代付功能 - 生成代付订单
|
||||
*
|
||||
* @param payForInfo 代付信息
|
||||
* @return 代付订单结果
|
||||
* 支付回调通知处理(小程序支付)
|
||||
* @param request HttpServletRequest
|
||||
* @return 处理结果,包含是否成功、支付信息等
|
||||
*/
|
||||
public static Map<String, Object> createPayForOrder(Map<String, Object> payForInfo) {
|
||||
public Map<String, Object> handlePayNotify(HttpServletRequest request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 1. 参数验证
|
||||
String validationError = validatePayForParams(payForInfo);
|
||||
if (validationError != null) {
|
||||
result.put("success", false);
|
||||
result.put("message", validationError);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 2. 生成代付订单号
|
||||
String payForOrderNo = generatePayForOrderNo((String) payForInfo.get("orderNo"));
|
||||
|
||||
// 3. 构建代付订单信息
|
||||
Map<String, Object> payForOrderInfo = new HashMap<>();
|
||||
payForOrderInfo.put("orderNo", payForOrderNo);
|
||||
payForOrderInfo.put("openid", payForInfo.get("payerOpenid"));
|
||||
payForOrderInfo.put("totalFee", payForInfo.get("totalFee"));
|
||||
payForOrderInfo.put("body", "代付-" + payForInfo.get("body"));
|
||||
payForOrderInfo.put("notifyUrl", payForInfo.get("notifyUrl"));
|
||||
payForOrderInfo.put("attach", buildPayForAttach(payForInfo));
|
||||
|
||||
// 4. 设置30分钟过期时间
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.MINUTE, 30);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
payForOrderInfo.put("timeExpire", sdf.format(calendar.getTime()));
|
||||
|
||||
// 5. 调用统一下单
|
||||
Map<String, Object> unifiedResult = unifiedOrder(payForOrderInfo);
|
||||
|
||||
if ((Boolean) unifiedResult.get("success")) {
|
||||
result.put("success", true);
|
||||
result.put("payForOrderNo", payForOrderNo);
|
||||
result.put("payParams", unifiedResult.get("payParams"));
|
||||
result.put("prepayId", unifiedResult.get("prepayId"));
|
||||
result.put("expireTime", payForOrderInfo.get("timeExpire"));
|
||||
result.put("message", "代付订单创建成功");
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "代付订单创建失败:" + unifiedResult.get("message"));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "创建代付订单异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付结果通知回调处理
|
||||
*
|
||||
* @param request HTTP请求对象
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Map<String, Object> handlePayNotify(HttpServletRequest request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 1. 读取请求数据
|
||||
StringBuilder xmlData = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
xmlData.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
// 2. 解析XML数据
|
||||
Map<String, String> notifyData = xmlToMap(xmlData.toString());
|
||||
|
||||
// 3. 验证签名
|
||||
if (!verifySign(notifyData, WECHAT_API_KEY)) {
|
||||
result.put("success", false);
|
||||
result.put("responseXml", buildNotifyResponse(FAIL_CODE, "签名验证失败"));
|
||||
result.put("message", "签名验证失败");
|
||||
return result;
|
||||
if (!verifySign(notifyData, wechatApiKey)) {
|
||||
return failResult("签名验证失败");
|
||||
}
|
||||
|
||||
// 4. 检查支付结果
|
||||
if (!SUCCESS_CODE.equals(notifyData.get("return_code")) ||
|
||||
!SUCCESS_CODE.equals(notifyData.get("result_code"))) {
|
||||
result.put("success", false);
|
||||
result.put("responseXml", buildNotifyResponse(FAIL_CODE, "支付失败"));
|
||||
result.put("message", "支付失败:" + notifyData.get("err_code_des"));
|
||||
return result;
|
||||
if (!SUCCESS_CODE.equals(notifyData.get("return_code")) || !SUCCESS_CODE.equals(notifyData.get("result_code"))) {
|
||||
return failResult("支付失败:" + notifyData.get("err_code_des"));
|
||||
}
|
||||
|
||||
// 5. 构建支付信息
|
||||
Map<String, Object> paymentInfo = new HashMap<>();
|
||||
paymentInfo.put("transactionId", notifyData.get("transaction_id"));
|
||||
|
|
@ -307,23 +233,12 @@ public class WechatPayUtil {
|
|||
paymentInfo.put("timeEnd", notifyData.get("time_end"));
|
||||
paymentInfo.put("attach", notifyData.get("attach"));
|
||||
paymentInfo.put("openid", notifyData.get("openid"));
|
||||
|
||||
// 6. 判断是否为代付订单
|
||||
String orderNo = notifyData.get("out_trade_no");
|
||||
boolean isPayFor = isPayForOrder(orderNo);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("responseXml", buildNotifyResponse(SUCCESS_CODE, "OK"));
|
||||
result.put("paymentInfo", paymentInfo);
|
||||
result.put("isPayFor", isPayFor);
|
||||
result.put("message", "支付通知处理成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("responseXml", buildNotifyResponse(FAIL_CODE, "处理异常"));
|
||||
result.put("message", "处理支付通知异常:" + e.getMessage());
|
||||
result = failResult("处理支付通知异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -333,7 +248,7 @@ public class WechatPayUtil {
|
|||
* @param refundInfo 退款信息
|
||||
* @return 退款结果
|
||||
*/
|
||||
public static Map<String, Object> refund(Map<String, Object> refundInfo) {
|
||||
public Map<String, Object> refund(Map<String, Object> refundInfo) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
|
|
@ -349,7 +264,7 @@ public class WechatPayUtil {
|
|||
Map<String, String> params = buildRefundParams(refundInfo);
|
||||
|
||||
// 3. 生成签名
|
||||
String sign = generateSign(params, WECHAT_API_KEY);
|
||||
String sign = generateSign(params, wechatApiKey);
|
||||
params.put("sign", sign);
|
||||
|
||||
// 4. 发送请求(需要证书)
|
||||
|
|
@ -394,7 +309,7 @@ public class WechatPayUtil {
|
|||
* @param transferInfo 付款信息
|
||||
* @return 付款结果
|
||||
*/
|
||||
public static Map<String, Object> transferToUser(Map<String, Object> transferInfo) {
|
||||
public Map<String, Object> transferToUser(Map<String, Object> transferInfo) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
|
|
@ -410,7 +325,7 @@ public class WechatPayUtil {
|
|||
Map<String, String> params = buildTransferParams(transferInfo);
|
||||
|
||||
// 3. 生成签名
|
||||
String sign = generateSign(params, WECHAT_API_KEY);
|
||||
String sign = generateSign(params, wechatApiKey);
|
||||
params.put("sign", sign);
|
||||
|
||||
// 4. 发送请求(需要证书)
|
||||
|
|
@ -448,35 +363,149 @@ public class WechatPayUtil {
|
|||
return result;
|
||||
}
|
||||
|
||||
// ========== 私有工具方法 ==========
|
||||
// ===================== 工具方法和签名相关 =====================
|
||||
|
||||
/**
|
||||
* 验证统一下单参数
|
||||
* 构建小程序端调起支付参数
|
||||
* @param prepayId 预支付交易会话标识
|
||||
* @return 小程序端调起支付参数Map
|
||||
*/
|
||||
private static String validateOrderParams(Map<String, Object> orderInfo) {
|
||||
if (orderInfo == null) return "订单信息不能为空";
|
||||
if (orderInfo.get("orderNo") == null || orderInfo.get("orderNo").toString().trim().isEmpty()) {
|
||||
return "订单号不能为空";
|
||||
private Map<String, Object> buildMiniProgramPayParams(String prepayId) {
|
||||
Map<String, Object> payParams = new HashMap<>();
|
||||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String nonceStr = generateNonceStr();
|
||||
String packageStr = "prepay_id=" + prepayId;
|
||||
String signType = "MD5";
|
||||
// 构建签名参数
|
||||
Map<String, String> signParams = new HashMap<>();
|
||||
signParams.put("appId", wechatAppId);
|
||||
signParams.put("timeStamp", timeStamp);
|
||||
signParams.put("nonceStr", nonceStr);
|
||||
signParams.put("package", packageStr);
|
||||
signParams.put("signType", signType);
|
||||
String paySign = generateSign(signParams, wechatApiKey);
|
||||
payParams.put("timeStamp", timeStamp);
|
||||
payParams.put("nonceStr", nonceStr);
|
||||
payParams.put("package", packageStr);
|
||||
payParams.put("signType", signType);
|
||||
payParams.put("paySign", paySign);
|
||||
return payParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* @param params 参数Map
|
||||
* @param key API密钥
|
||||
* @return 签名字符串
|
||||
*/
|
||||
private String generateSign(Map<String, String> params, String key) {
|
||||
try {
|
||||
Map<String, String> sortedParams = new TreeMap<>(params);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
|
||||
if (entry.getValue() != null && !entry.getValue().isEmpty() && !"sign".equals(entry.getKey())) {
|
||||
stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
||||
}
|
||||
}
|
||||
stringBuilder.append("key=").append(key);
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md5.digest(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
result.append(String.format("%02X", b));
|
||||
}
|
||||
return result.toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("生成签名失败", e);
|
||||
}
|
||||
if (orderInfo.get("openid") == null || orderInfo.get("openid").toString().trim().isEmpty()) {
|
||||
return "用户openid不能为空";
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
* @param params 参数Map
|
||||
* @param key API密钥
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
private boolean verifySign(Map<String, String> params, String key) {
|
||||
String sign = params.get("sign");
|
||||
if (sign == null || sign.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (orderInfo.get("totalFee") == null) {
|
||||
return "支付金额不能为空";
|
||||
Map<String, String> paramsWithoutSign = new HashMap<>(params);
|
||||
paramsWithoutSign.remove("sign");
|
||||
String generatedSign = generateSign(paramsWithoutSign, key);
|
||||
return sign.equals(generatedSign);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map转XML
|
||||
* @param params 参数Map
|
||||
* @return XML字符串
|
||||
*/
|
||||
private String mapToXml(Map<String, String> params) {
|
||||
StringBuilder xml = new StringBuilder();
|
||||
xml.append("<xml>");
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
xml.append("<").append(entry.getKey()).append("><![CDATA[")
|
||||
.append(entry.getValue()).append("]]></").append(entry.getKey()).append(">");
|
||||
}
|
||||
if (orderInfo.get("body") == null || orderInfo.get("body").toString().trim().isEmpty()) {
|
||||
return "商品描述不能为空";
|
||||
xml.append("</xml>");
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* XML转Map(简化实现)
|
||||
* @param xml XML字符串
|
||||
* @return Map
|
||||
*/
|
||||
private Map<String, String> xmlToMap(String xml) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
try {
|
||||
xml = xml.replaceAll("<xml>", "").replaceAll("</xml>", "");
|
||||
String[] elements = xml.split("</\\w+>");
|
||||
for (String element : elements) {
|
||||
if (element.trim().isEmpty()) continue;
|
||||
int startTag = element.indexOf("<");
|
||||
int endTag = element.indexOf(">");
|
||||
if (startTag >= 0 && endTag > startTag) {
|
||||
String key = element.substring(startTag + 1, endTag);
|
||||
String value = element.substring(endTag + 1);
|
||||
if (value.startsWith("<![CDATA[") && value.endsWith("]]>")) {
|
||||
value = value.substring(9, value.length() - 3);
|
||||
}
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("XML解析失败", e);
|
||||
}
|
||||
if (orderInfo.get("notifyUrl") == null || orderInfo.get("notifyUrl").toString().trim().isEmpty()) {
|
||||
return "回调地址不能为空";
|
||||
}
|
||||
return null;
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @return 随机字符串
|
||||
*/
|
||||
private String generateNonceStr() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一失败返回结构
|
||||
* @param msg 失败信息
|
||||
* @return 标准失败结构
|
||||
*/
|
||||
private Map<String, Object> failResult(String msg) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", false);
|
||||
result.put("message", msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证代付参数
|
||||
*/
|
||||
private static String validatePayForParams(Map<String, Object> payForInfo) {
|
||||
private String validatePayForParams(Map<String, Object> payForInfo) {
|
||||
if (payForInfo == null) return "代付信息不能为空";
|
||||
if (payForInfo.get("orderNo") == null || payForInfo.get("orderNo").toString().trim().isEmpty()) {
|
||||
return "原订单号不能为空";
|
||||
|
|
@ -502,7 +531,7 @@ public class WechatPayUtil {
|
|||
/**
|
||||
* 验证退款参数
|
||||
*/
|
||||
private static String validateRefundParams(Map<String, Object> refundInfo) {
|
||||
private String validateRefundParams(Map<String, Object> refundInfo) {
|
||||
if (refundInfo == null) return "退款信息不能为空";
|
||||
if (refundInfo.get("orderNo") == null || refundInfo.get("orderNo").toString().trim().isEmpty()) {
|
||||
return "订单号不能为空";
|
||||
|
|
@ -522,7 +551,7 @@ public class WechatPayUtil {
|
|||
/**
|
||||
* 验证企业付款参数
|
||||
*/
|
||||
private static String validateTransferParams(Map<String, Object> transferInfo) {
|
||||
private String validateTransferParams(Map<String, Object> transferInfo) {
|
||||
if (transferInfo == null) return "付款信息不能为空";
|
||||
if (transferInfo.get("partnerTradeNo") == null || transferInfo.get("partnerTradeNo").toString().trim().isEmpty()) {
|
||||
return "商户订单号不能为空";
|
||||
|
|
@ -540,38 +569,53 @@ public class WechatPayUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* 构建统一下单参数
|
||||
* 构建代付附加数据
|
||||
*/
|
||||
private static Map<String, String> buildUnifiedOrderParams(Map<String, Object> orderInfo) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", WECHAT_APP_ID);
|
||||
params.put("mch_id", WECHAT_MCH_ID);
|
||||
params.put("nonce_str", generateNonceStr());
|
||||
params.put("body", orderInfo.get("body").toString());
|
||||
params.put("out_trade_no", orderInfo.get("orderNo").toString());
|
||||
params.put("total_fee", orderInfo.get("totalFee").toString());
|
||||
params.put("spbill_create_ip", "127.0.0.1");
|
||||
params.put("notify_url", orderInfo.get("notifyUrl").toString());
|
||||
params.put("trade_type", TRADE_TYPE_JSAPI);
|
||||
params.put("openid", orderInfo.get("openid").toString());
|
||||
|
||||
if (orderInfo.get("attach") != null) {
|
||||
params.put("attach", orderInfo.get("attach").toString());
|
||||
}
|
||||
if (orderInfo.get("timeExpire") != null) {
|
||||
params.put("time_expire", orderInfo.get("timeExpire").toString());
|
||||
private String buildPayForAttach(Map<String, Object> payForInfo) {
|
||||
JSONObject attach = new JSONObject();
|
||||
attach.put("type", "payfor");
|
||||
attach.put("originalOrderNo", payForInfo.get("orderNo"));
|
||||
attach.put("payerOpenid", payForInfo.get("payerOpenid"));
|
||||
attach.put("payeeOpenid", payForInfo.get("payeeOpenid"));
|
||||
if (payForInfo.get("remark") != null) {
|
||||
attach.put("remark", payForInfo.get("remark"));
|
||||
}
|
||||
return attach.toString();
|
||||
}
|
||||
|
||||
return params;
|
||||
/**
|
||||
* 生成代付订单号
|
||||
*/
|
||||
private String generatePayForOrderNo(String originalOrderNo) {
|
||||
return "PF" + originalOrderNo + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为代付订单
|
||||
*/
|
||||
private boolean isPayForOrder(String orderNo) {
|
||||
return orderNo != null && orderNo.startsWith("PF");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建通知响应XML
|
||||
*/
|
||||
private String buildNotifyResponse(String returnCode, String returnMsg) {
|
||||
StringBuilder xml = new StringBuilder();
|
||||
xml.append("<xml>");
|
||||
xml.append("<return_code><![CDATA[").append(returnCode).append("]]></return_code>");
|
||||
xml.append("<return_msg><![CDATA[").append(returnMsg).append("]]></return_msg>");
|
||||
xml.append("</xml>");
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建退款参数
|
||||
*/
|
||||
private static Map<String, String> buildRefundParams(Map<String, Object> refundInfo) {
|
||||
private Map<String, String> buildRefundParams(Map<String, Object> refundInfo) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", WECHAT_APP_ID);
|
||||
params.put("mch_id", WECHAT_MCH_ID);
|
||||
params.put("appid", wechatAppId);
|
||||
params.put("mch_id", wechatMchId);
|
||||
params.put("nonce_str", generateNonceStr());
|
||||
params.put("out_trade_no", refundInfo.get("orderNo").toString());
|
||||
params.put("out_refund_no", refundInfo.get("refundNo").toString());
|
||||
|
|
@ -591,10 +635,10 @@ public class WechatPayUtil {
|
|||
/**
|
||||
* 构建企业付款参数
|
||||
*/
|
||||
private static Map<String, String> buildTransferParams(Map<String, Object> transferInfo) {
|
||||
private Map<String, String> buildTransferParams(Map<String, Object> transferInfo) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("mch_appid", WECHAT_APP_ID);
|
||||
params.put("mchid", WECHAT_MCH_ID);
|
||||
params.put("mch_appid", wechatAppId);
|
||||
params.put("mchid", wechatMchId);
|
||||
params.put("nonce_str", generateNonceStr());
|
||||
params.put("partner_trade_no", transferInfo.get("partnerTradeNo").toString());
|
||||
params.put("openid", transferInfo.get("openid").toString());
|
||||
|
|
@ -610,178 +654,4 @@ public class WechatPayUtil {
|
|||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建小程序支付参数
|
||||
*/
|
||||
private static Map<String, Object> buildMiniProgramPayParams(String prepayId) {
|
||||
Map<String, Object> payParams = new HashMap<>();
|
||||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String nonceStr = generateNonceStr();
|
||||
String packageStr = "prepay_id=" + prepayId;
|
||||
String signType = "MD5";
|
||||
|
||||
// 构建签名参数
|
||||
Map<String, String> signParams = new HashMap<>();
|
||||
signParams.put("appId", WECHAT_APP_ID);
|
||||
signParams.put("timeStamp", timeStamp);
|
||||
signParams.put("nonceStr", nonceStr);
|
||||
signParams.put("package", packageStr);
|
||||
signParams.put("signType", signType);
|
||||
|
||||
String paySign = generateSign(signParams, WECHAT_API_KEY);
|
||||
|
||||
payParams.put("timeStamp", timeStamp);
|
||||
payParams.put("nonceStr", nonceStr);
|
||||
payParams.put("package", packageStr);
|
||||
payParams.put("signType", signType);
|
||||
payParams.put("paySign", paySign);
|
||||
|
||||
return payParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建代付附加数据
|
||||
*/
|
||||
private static String buildPayForAttach(Map<String, Object> payForInfo) {
|
||||
JSONObject attach = new JSONObject();
|
||||
attach.put("type", "payfor");
|
||||
attach.put("originalOrderNo", payForInfo.get("orderNo"));
|
||||
attach.put("payerOpenid", payForInfo.get("payerOpenid"));
|
||||
attach.put("payeeOpenid", payForInfo.get("payeeOpenid"));
|
||||
if (payForInfo.get("remark") != null) {
|
||||
attach.put("remark", payForInfo.get("remark"));
|
||||
}
|
||||
return attach.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成代付订单号
|
||||
*/
|
||||
private static String generatePayForOrderNo(String originalOrderNo) {
|
||||
return "PF" + originalOrderNo + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为代付订单
|
||||
*/
|
||||
private static boolean isPayForOrder(String orderNo) {
|
||||
return orderNo != null && orderNo.startsWith("PF");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建通知响应XML
|
||||
*/
|
||||
private static String buildNotifyResponse(String returnCode, String returnMsg) {
|
||||
StringBuilder xml = new StringBuilder();
|
||||
xml.append("<xml>");
|
||||
xml.append("<return_code><![CDATA[").append(returnCode).append("]]></return_code>");
|
||||
xml.append("<return_msg><![CDATA[").append(returnMsg).append("]]></return_msg>");
|
||||
xml.append("</xml>");
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
*/
|
||||
private static String generateNonceStr() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*/
|
||||
private static String generateSign(Map<String, String> params, String key) {
|
||||
try {
|
||||
// 1. 排序参数
|
||||
Map<String, String> sortedParams = new TreeMap<>(params);
|
||||
|
||||
// 2. 拼接参数
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
|
||||
if (entry.getValue() != null && !entry.getValue().isEmpty() && !"sign".equals(entry.getKey())) {
|
||||
stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
||||
}
|
||||
}
|
||||
stringBuilder.append("key=").append(key);
|
||||
|
||||
// 3. MD5加密
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md5.digest(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 4. 转换为大写16进制字符串
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
result.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("生成签名失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*/
|
||||
private static boolean verifySign(Map<String, String> params, String key) {
|
||||
String sign = params.get("sign");
|
||||
if (sign == null || sign.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<String, String> paramsWithoutSign = new HashMap<>(params);
|
||||
paramsWithoutSign.remove("sign");
|
||||
|
||||
String generatedSign = generateSign(paramsWithoutSign, key);
|
||||
return sign.equals(generatedSign);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map转XML
|
||||
*/
|
||||
private static String mapToXml(Map<String, String> params) {
|
||||
StringBuilder xml = new StringBuilder();
|
||||
xml.append("<xml>");
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
xml.append("<").append(entry.getKey()).append("><![CDATA[")
|
||||
.append(entry.getValue()).append("]]></").append(entry.getKey()).append(">");
|
||||
}
|
||||
xml.append("</xml>");
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* XML转Map(简化实现)
|
||||
*/
|
||||
private static Map<String, String> xmlToMap(String xml) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
try {
|
||||
// 简化的XML解析实现
|
||||
xml = xml.replaceAll("<xml>", "").replaceAll("</xml>", "");
|
||||
String[] elements = xml.split("</\\w+>");
|
||||
|
||||
for (String element : elements) {
|
||||
if (element.trim().isEmpty()) continue;
|
||||
|
||||
int startTag = element.indexOf("<");
|
||||
int endTag = element.indexOf(">");
|
||||
if (startTag >= 0 && endTag > startTag) {
|
||||
String key = element.substring(startTag + 1, endTag);
|
||||
String value = element.substring(endTag + 1);
|
||||
|
||||
// 处理CDATA
|
||||
if (value.startsWith("<![CDATA[") && value.endsWith("]]>")) {
|
||||
value = value.substring(9, value.length() - 3);
|
||||
}
|
||||
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("XML解析失败", e);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue