From efb07bcec37c49228d9760794f215c8549243ad2 Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Mon, 23 Mar 2026 18:52:21 +0800
Subject: [PATCH] 1
---
src/main/java/com/nq/utils/http/HttpClientRequest.java | 44 ++
src/main/java/com/nq/pojo/PayV2PayoutCreateResponse.java | 108 ++++
src/main/java/com/nq/service/IPayService.java | 6
src/main/java/com/nq/controller/protol/UserController.java | 16
src/main/java/com/nq/pojo/PayV2PayCreateResponse.java | 87 +++
pom.xml | 6
src/main/java/com/nq/common/interceptor/ApiUserAuthorityInterceptor.java | 46 +
src/main/java/com/nq/controller/backend/AdminWithDrawController.java | 5
src/main/java/com/nq/pojo/PayV2NotifyRequest.java | 92 ++++
src/main/java/com/nq/pojo/PayV2PayoutNotifyRequest.java | 102 ++++
src/main/resources/sql/site_pay_option.sql | 13
src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java | 165 +++++++
src/main/java/com/nq/service/IUserWithdrawService.java | 5
src/main/resources/sql/支付设置接口对接文档.md | 245 +++++++++++
src/main/resources/sql/site_pay_option_add_pay4.sql | 5
src/main/java/com/nq/service/impl/PayServiceImpl.java | 353 ++++++++++++++++
16 files changed, 1,281 insertions(+), 17 deletions(-)
diff --git a/pom.xml b/pom.xml
index 058a9cb..736d28a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -160,6 +160,12 @@
<artifactId>hutool-all</artifactId>
<version>5.7.12</version>
</dependency>
+ <!-- Pay v2:解析 PKCS#1(BEGIN RSA PRIVATE KEY)等 PEM 私钥 -->
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <version>1.70</version>
+ </dependency>
<dependency>
<groupId>cn.afterturn</groupId>
diff --git a/src/main/java/com/nq/common/interceptor/ApiUserAuthorityInterceptor.java b/src/main/java/com/nq/common/interceptor/ApiUserAuthorityInterceptor.java
index f744833..8ec8a65 100644
--- a/src/main/java/com/nq/common/interceptor/ApiUserAuthorityInterceptor.java
+++ b/src/main/java/com/nq/common/interceptor/ApiUserAuthorityInterceptor.java
@@ -1,9 +1,7 @@
package com.nq.common.interceptor;
-import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
-import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.nq.annotation.SameUrlData;
import com.nq.common.ServerResponse;
@@ -24,15 +22,19 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
-import java.util.Map;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class ApiUserAuthorityInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(ApiUserAuthorityInterceptor.class);
+ private static final Set<String> PAY_CALLBACK_ALLOW_IPS = new HashSet<>(
+ Arrays.asList("3.111.236.70", "13.233.3.123")
+ );
private RedisTemplate<String,String> redisTemplate;
@@ -54,6 +56,11 @@
}
String url = httpServletRequest.getRequestURI();
+ if (isPayCallbackUrl(url) && !isAllowedPayCallbackIp(httpServletRequest)) {
+ log.warn("拦截非白名单回调IP, url={}, ip={}", url, extractClientIp(httpServletRequest));
+ httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ return false;
+ }
if ("/user/upload.do".equals(url)) {
return true;
}
@@ -84,10 +91,16 @@
if ("/user/rechargeCallbackThree.do".equals(url)) {//支付回调
return true;
}
+ if ("/user/rechargeCallbackFour.do".equals(url)) {//支付4回调
+ return true;
+ }
if ("/user/payoutCallback.do".equals(url)) {//代付回调
return true;
}
if ("/user/payoutCallbackTwo.do".equals(url)) {//代付回调
+ return true;
+ }
+ if ("/user/payoutCallbackThree.do".equals(url)) {//代付v2回调
return true;
}
User currentUser = getCurrentUser(httpServletRequest);
@@ -111,6 +124,31 @@
//判断请求头
return true;
}
+
+ private boolean isPayCallbackUrl(String url) {
+ return "/user/rechargeCallbackFour.do".equals(url)
+ || "/user/payoutCallbackThree.do".equals(url);
+ }
+
+ private boolean isAllowedPayCallbackIp(HttpServletRequest request) {
+ String ip = extractClientIp(request);
+ return PAY_CALLBACK_ALLOW_IPS.contains(ip);
+ }
+
+ private String extractClientIp(HttpServletRequest request) {
+ String forwarded = request.getHeader("X-Forwarded-For");
+ if (forwarded != null && !forwarded.trim().isEmpty()) {
+ String first = forwarded.split(",")[0].trim();
+ if (!first.isEmpty()) {
+ return first;
+ }
+ }
+ String realIp = request.getHeader("X-Real-IP");
+ if (realIp != null && !realIp.trim().isEmpty()) {
+ return realIp.trim();
+ }
+ return request.getRemoteAddr();
+ }
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, ModelAndView modelAndView) throws Exception {
}
diff --git a/src/main/java/com/nq/controller/backend/AdminWithDrawController.java b/src/main/java/com/nq/controller/backend/AdminWithDrawController.java
index 92ef7e4..4db0114 100644
--- a/src/main/java/com/nq/controller/backend/AdminWithDrawController.java
+++ b/src/main/java/com/nq/controller/backend/AdminWithDrawController.java
@@ -61,7 +61,8 @@
/**
*
* @param withId
- * @param state 1.审核通过(走代付) 2.审核(不走代付) 3.驳回
+ * @param state 1.4审核通过(走代付) 2.审核(不走代付) 3.驳回
+ * @param payoutChannel
* @param authMsg
* @return
*/
@@ -83,7 +84,7 @@
// 更新请求时间戳
requestTimestamps.put(requestId, System.currentTimeMillis());
synchronized (withId){
- serverResponse = this.iUserWithdrawService.updateState(withId, state, authMsg,request,response);
+ serverResponse = this.iUserWithdrawService.updateState(withId, state, authMsg, request, response);
}
} catch (Exception e) {
log.error("admin修改充值订单状态出错 ,异常 = {}", e);
diff --git a/src/main/java/com/nq/controller/protol/UserController.java b/src/main/java/com/nq/controller/protol/UserController.java
index 90e29b2..dbcea91 100644
--- a/src/main/java/com/nq/controller/protol/UserController.java
+++ b/src/main/java/com/nq/controller/protol/UserController.java
@@ -511,6 +511,14 @@
}
/**
+ * 支付4(代收 v2)异步通知
+ */
+ @PostMapping({"rechargeCallbackFour.do"})
+ public void rechargeCallbackFour(@RequestBody PayV2NotifyRequest vo, HttpServletResponse response) throws IOException {
+ payService.rechargeCallbackFour(vo, response);
+ }
+
+ /**
* 代付回调接口
*/
@PostMapping({"payoutCallback.do"})
@@ -526,4 +534,12 @@
public Map<String, Object> payoutCallbackTwo(@RequestBody PayoutCallbackRequest callbackVo, HttpServletResponse response) throws IOException {
return payService.payoutCallbackTwo(callbackVo, response);
}
+
+ /**
+ * 代付 v2(payOutCreate)异步通知
+ */
+ @PostMapping({"payoutCallbackThree.do"})
+ public void payoutCallbackThree(@RequestBody PayV2PayoutNotifyRequest vo, HttpServletResponse response) throws IOException {
+ payService.payoutCallbackThree(vo, response);
+ }
}
diff --git a/src/main/java/com/nq/pojo/PayV2NotifyRequest.java b/src/main/java/com/nq/pojo/PayV2NotifyRequest.java
new file mode 100644
index 0000000..092eddf
--- /dev/null
+++ b/src/main/java/com/nq/pojo/PayV2NotifyRequest.java
@@ -0,0 +1,92 @@
+package com.nq.pojo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PayV2NotifyRequest {
+
+ private Integer mid;
+ @JsonProperty("m_order")
+ private String mOrder;
+ @JsonProperty("order_id")
+ private String orderId;
+ private String amount;
+ private String utr;
+ @JsonProperty("pay_status")
+ private Integer payStatus;
+ @JsonProperty("r_time")
+ private Long rTime;
+ private String sign;
+
+ public Integer getMid() {
+ return mid;
+ }
+
+ public void setMid(Integer mid) {
+ this.mid = mid;
+ }
+
+ public String getMOrder() {
+ return mOrder;
+ }
+
+ public void setMOrder(String mOrder) {
+ this.mOrder = mOrder;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getUtr() {
+ return utr;
+ }
+
+ public void setUtr(String utr) {
+ this.utr = utr;
+ }
+
+ public Integer getPayStatus() {
+ return payStatus;
+ }
+
+ public void setPayStatus(Integer payStatus) {
+ this.payStatus = payStatus;
+ }
+
+ public Long getRTime() {
+ return rTime;
+ }
+
+ public void setRTime(Long rTime) {
+ this.rTime = rTime;
+ }
+
+ public String getSign() {
+ return sign;
+ }
+
+ public void setSign(String sign) {
+ this.sign = sign;
+ }
+
+ @Override
+ public String toString() {
+ return "PayV2NotifyRequest{mid=" + mid + ", mOrder='" + mOrder + "', orderId='" + orderId
+ + "', amount='" + amount + "', utr='" + utr + "', payStatus=" + payStatus
+ + ", rTime=" + rTime + ", sign='" + sign + "'}";
+ }
+}
diff --git a/src/main/java/com/nq/pojo/PayV2PayCreateResponse.java b/src/main/java/com/nq/pojo/PayV2PayCreateResponse.java
new file mode 100644
index 0000000..b6ba291
--- /dev/null
+++ b/src/main/java/com/nq/pojo/PayV2PayCreateResponse.java
@@ -0,0 +1,87 @@
+package com.nq.pojo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PayV2PayCreateResponse {
+
+ private int status;
+ private String msg;
+ private Data data;
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public Data getData() {
+ return data;
+ }
+
+ public void setData(Data data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Data {
+ @JsonProperty("m_order")
+ private String mOrder;
+ @JsonProperty("order_id")
+ private String orderId;
+ private String amount;
+ private String sign;
+ private String url;
+
+ public String getMOrder() {
+ return mOrder;
+ }
+
+ public void setMOrder(String mOrder) {
+ this.mOrder = mOrder;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getSign() {
+ return sign;
+ }
+
+ public void setSign(String sign) {
+ this.sign = sign;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+ }
+}
diff --git a/src/main/java/com/nq/pojo/PayV2PayoutCreateResponse.java b/src/main/java/com/nq/pojo/PayV2PayoutCreateResponse.java
new file mode 100644
index 0000000..96e7552
--- /dev/null
+++ b/src/main/java/com/nq/pojo/PayV2PayoutCreateResponse.java
@@ -0,0 +1,108 @@
+package com.nq.pojo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PayV2PayoutCreateResponse {
+
+ private int status;
+ private String msg;
+ private Data data;
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public Data getData() {
+ return data;
+ }
+
+ public void setData(Data data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Data {
+ @JsonProperty("m_id")
+ private Integer mId;
+ @JsonProperty("m_order")
+ private String mOrder;
+ @JsonProperty("order_id")
+ private String orderId;
+ private String amount;
+ private String sign;
+ @JsonProperty("order_status")
+ private Integer orderStatus;
+ @JsonProperty("err_msg")
+ private String errMsg;
+
+ public Integer getMId() {
+ return mId;
+ }
+
+ public void setMId(Integer mId) {
+ this.mId = mId;
+ }
+
+ public String getMOrder() {
+ return mOrder;
+ }
+
+ public void setMOrder(String mOrder) {
+ this.mOrder = mOrder;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getSign() {
+ return sign;
+ }
+
+ public void setSign(String sign) {
+ this.sign = sign;
+ }
+
+ public Integer getOrderStatus() {
+ return orderStatus;
+ }
+
+ public void setOrderStatus(Integer orderStatus) {
+ this.orderStatus = orderStatus;
+ }
+
+ public String getErrMsg() {
+ return errMsg;
+ }
+
+ public void setErrMsg(String errMsg) {
+ this.errMsg = errMsg;
+ }
+ }
+}
diff --git a/src/main/java/com/nq/pojo/PayV2PayoutNotifyRequest.java b/src/main/java/com/nq/pojo/PayV2PayoutNotifyRequest.java
new file mode 100644
index 0000000..321bf51
--- /dev/null
+++ b/src/main/java/com/nq/pojo/PayV2PayoutNotifyRequest.java
@@ -0,0 +1,102 @@
+package com.nq.pojo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PayV2PayoutNotifyRequest {
+
+ private Integer mid;
+ @JsonProperty("m_order")
+ private String mOrder;
+ @JsonProperty("order_id")
+ private String orderId;
+ private String amount;
+ @JsonProperty("pay_status")
+ private Integer payStatus;
+ @JsonProperty("err_msg")
+ private String errMsg;
+ @JsonProperty("r_time")
+ private Long rTime;
+ private String utr;
+ private String sign;
+
+ public Integer getMid() {
+ return mid;
+ }
+
+ public void setMid(Integer mid) {
+ this.mid = mid;
+ }
+
+ public String getMOrder() {
+ return mOrder;
+ }
+
+ public void setMOrder(String mOrder) {
+ this.mOrder = mOrder;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ public void setOrderId(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public Integer getPayStatus() {
+ return payStatus;
+ }
+
+ public void setPayStatus(Integer payStatus) {
+ this.payStatus = payStatus;
+ }
+
+ public String getErrMsg() {
+ return errMsg;
+ }
+
+ public void setErrMsg(String errMsg) {
+ this.errMsg = errMsg;
+ }
+
+ public Long getRTime() {
+ return rTime;
+ }
+
+ public void setRTime(Long rTime) {
+ this.rTime = rTime;
+ }
+
+ public String getUtr() {
+ return utr;
+ }
+
+ public void setUtr(String utr) {
+ this.utr = utr;
+ }
+
+ public String getSign() {
+ return sign;
+ }
+
+ public void setSign(String sign) {
+ this.sign = sign;
+ }
+
+ @Override
+ public String toString() {
+ return "PayV2PayoutNotifyRequest{mid=" + mid + ", mOrder='" + mOrder + "', orderId='" + orderId
+ + "', amount='" + amount + "', payStatus=" + payStatus + ", errMsg='" + errMsg
+ + "', rTime=" + rTime + ", utr='" + utr + "'}";
+ }
+}
diff --git a/src/main/java/com/nq/service/IPayService.java b/src/main/java/com/nq/service/IPayService.java
index b90fe5c..02447a4 100644
--- a/src/main/java/com/nq/service/IPayService.java
+++ b/src/main/java/com/nq/service/IPayService.java
@@ -3,6 +3,8 @@
import com.nq.common.ServerResponse;
import com.nq.pojo.PaymentResponse;
+import com.nq.pojo.PayV2NotifyRequest;
+import com.nq.pojo.PayV2PayoutNotifyRequest;
import com.nq.pojo.PayoutCallbackVo;
import com.nq.pojo.RechargeCallbackVo;
import com.nq.pojo.TransactionStatusVo;
@@ -39,5 +41,9 @@
void rechargeCallbackThree(TransactionStatusVo vo, HttpServletResponse response) throws IOException;
+ void rechargeCallbackFour(PayV2NotifyRequest vo, HttpServletResponse response) throws IOException;
+
void payoutCallback(PayoutCallbackVo vo, HttpServletResponse response) throws IOException;
+
+ void payoutCallbackThree(PayV2PayoutNotifyRequest vo, HttpServletResponse response) throws IOException;
}
diff --git a/src/main/java/com/nq/service/IUserWithdrawService.java b/src/main/java/com/nq/service/IUserWithdrawService.java
index 95cdc16..d9868fe 100644
--- a/src/main/java/com/nq/service/IUserWithdrawService.java
+++ b/src/main/java/com/nq/service/IUserWithdrawService.java
@@ -24,7 +24,10 @@
ServerResponse<PageInfo> listByAdmin(Integer paramInteger1, Integer paramInteger2, String paramString1, Integer paramInteger3, String paramString2, String paramString3, HttpServletRequest paramHttpServletRequest, int paramInt1, int paramInt2);
- ServerResponse updateState(Integer paramInteger1, Integer paramInteger2, String paramString,HttpServletRequest request, HttpServletResponse response) throws Exception;
+ /**
+ * @param payoutChannel 审核通过走代付时:null 或 1=原 AES 代付;2= v2 payOutCreate(JSON+MD5)
+ */
+ ServerResponse updateState(Integer paramInteger1, Integer paramInteger2, String paramString, HttpServletRequest request, HttpServletResponse response) throws Exception;
int deleteByUserId(Integer paramInteger);
diff --git a/src/main/java/com/nq/service/impl/PayServiceImpl.java b/src/main/java/com/nq/service/impl/PayServiceImpl.java
index c61efce..d00f6e1 100644
--- a/src/main/java/com/nq/service/impl/PayServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/PayServiceImpl.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.nq.common.CmcPayConfig;
+import com.nq.common.PayV2GatewayKeys;
import com.nq.common.ServerResponse;
import com.nq.dao.*;
import com.nq.enums.EStockType;
@@ -14,7 +15,9 @@
import com.nq.pojo.*;
import com.nq.service.*;
import com.nq.utils.*;
+import com.nq.utils.http.HttpClientRequest;
import com.nq.utils.http.HttpClientUtil;
+import com.nq.utils.pay.PayV2RsaSignUtil;
import com.nq.utils.pay.CmcPayOuterRequestUtil;
import com.nq.utils.pay.CmcPayTool;
import com.nq.utils.timeutil.DateTimeUtil;
@@ -25,6 +28,7 @@
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.Key;
@@ -104,6 +108,11 @@
private static final String KEY_ALGORITHM = "AES";
private static final String AES128CBC = "AES/CBC/PKCS5Padding";
+ /** 代收 v2/payCreate 完整 URL(由商户后台提供域名与路径) */
+ private static final String PAY_V2_PAY_CREATE_URL = "https://pay.kkpay100.com/v2/payCreate";
+ /** 商户 id */
+ private static final int PAY_V2_MID = 10724;
+
public ServerResponse thirdPartyRecharge(HttpServletRequest request, String tradeAmount, Integer type) throws Exception {
// 支付1
String reqUrl = "https://api.watchglb.com/pay/web";
@@ -127,8 +136,127 @@
return getPaymentTwo(tradeAmount, uipReqRul, user,request);
}else if(type == 1){
return getPaymentThree(tradeAmount, threeUrl, user,request);
+ }else if(type == 4){
+ return getPaymentFour(tradeAmount, user, request);
}
return ServerResponse.createByErrorMsg("获取支付方式失败",request);
+ }
+
+ /**
+ * 支付4:代收 v2/payCreate(JSON)
+ */
+ private ServerResponse getPaymentFour(String tradeAmount, User user, HttpServletRequest request) throws Exception {
+ String orderId = generatePayOrderId();
+ BigDecimal amount = new BigDecimal(tradeAmount).setScale(2, RoundingMode.HALF_UP);
+
+ String notifyUrl = "https://api.greenbackcaps.top/user/rechargeCallbackFour.do";
+ String custId = "U" + user.getId();
+ String mobile = StringUtils.isNotBlank(user.getPhone()) ? user.getPhone() : "0000000000";
+ if (mobile.length() > 16) {
+ mobile = mobile.substring(0, 16);
+ }
+ String fn = UserNameUtil.formatCustomerName(user.getNickName());
+ if (StringUtils.isBlank(fn)) {
+ fn = "User";
+ }
+ if (fn.length() > 16) {
+ fn = fn.substring(0, 16);
+ }
+ String ln = String.valueOf(user.getId());
+ if (ln.length() > 16) {
+ ln = ln.substring(0, 16);
+ }
+ String email = custId + "@user.local";
+ if (email.length() > 64) {
+ email = email.substring(0, 64);
+ }
+
+ Map<String, String> signParams = new TreeMap<>();
+ signParams.put("amount", amount.toPlainString());
+ signParams.put("currency", "INR");
+ signParams.put("custId", custId);
+ signParams.put("email", email);
+ signParams.put("firstName", fn);
+ signParams.put("lastName", ln);
+ signParams.put("mid", String.valueOf(PAY_V2_MID));
+ signParams.put("mobile", mobile);
+ signParams.put("notifyUrl", notifyUrl);
+ signParams.put("orderId", orderId);
+ String signBaseString = PayV2RsaSignUtil.buildStringA(signParams);
+ String sign = PayV2RsaSignUtil.sign(signBaseString, PayV2GatewayKeys.MERCHANT_PRIVATE_KEY_PEM);
+ log.info("支付4代收签名串 stringA={}", signBaseString);
+
+ Map<String, Object> body = new LinkedHashMap<>();
+ body.put("mid", PAY_V2_MID);
+ body.put("orderId", orderId);
+ body.put("amount", amount.toPlainString());
+ body.put("currency", "INR");
+ body.put("custId", custId);
+ body.put("firstName", fn);
+ body.put("lastName", ln);
+ body.put("mobile", mobile);
+ body.put("email", email);
+ body.put("notifyUrl", notifyUrl);
+ body.put("sign", sign);
+
+ String json = new Gson().toJson(body);
+ log.info("支付4代收请求:{}", json);
+ String result = HttpClientRequest.doPostJsonBody(PAY_V2_PAY_CREATE_URL, json);
+ log.info("支付4代收返回:{}", result);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ PayV2PayCreateResponse resp = objectMapper.readValue(result, PayV2PayCreateResponse.class);
+ if (resp.getStatus() != 1 || resp.getData() == null) {
+ log.error("支付4下单失败:{}", result);
+ return ServerResponse.createByErrorMsg(
+ StringUtils.isNotBlank(resp.getMsg()) ? resp.getMsg() : "获取支付链接失败", request);
+ }
+
+ PayV2PayCreateResponse.Data data = resp.getData();
+ Map<String, String> respSignParams = new TreeMap<>();
+ if (StringUtils.isNotBlank(data.getAmount())) {
+ respSignParams.put("amount", data.getAmount());
+ }
+ if (StringUtils.isNotBlank(data.getMOrder())) {
+ respSignParams.put("m_order", data.getMOrder());
+ }
+ if (StringUtils.isNotBlank(data.getOrderId())) {
+ respSignParams.put("order_id", data.getOrderId());
+ }
+ if (StringUtils.isNotBlank(data.getUrl())) {
+ respSignParams.put("url", data.getUrl());
+ }
+ if (!PayV2RsaSignUtil.verify(respSignParams, data.getSign(), PayV2GatewayKeys.PLATFORM_PUBLIC_KEY)) {
+ log.warn("支付4返回签名验证未通过,stringA={}, sign={}",
+ PayV2RsaSignUtil.buildStringA(respSignParams), data.getSign());
+ }
+
+ PaymentRecharge paymentRecharge = new PaymentRecharge();
+ paymentRecharge.setUserId(user.getId());
+ paymentRecharge.setOrderNo(orderId);
+ paymentRecharge.setMchOrderNo(data.getOrderId());
+ paymentRecharge.setAmount(amount);
+ paymentRecharge.setStatus(1);
+ paymentRecharge.setPaymentTime(new Date());
+ paymentRecharge.setCreatedAt(new Date());
+ paymentRecharge.setNotifyUrl(notifyUrl);
+ paymentRecharge.setPayInfo(result);
+ paymentRechargeService.save(paymentRecharge);
+
+ UserRecharge userRecharge = new UserRecharge();
+ userRecharge.setUserId(user.getId());
+ userRecharge.setNickName(user.getRealName());
+ userRecharge.setAgentId(user.getAgentId());
+ userRecharge.setOrderSn(orderId);
+ userRecharge.setPayChannel("Payment 4");
+ userRecharge.setPayAmt(amount);
+ userRecharge.setAddTime(new Date());
+ userRecharge.setPayTime(new Date());
+ userRecharge.setOrderStatus(0);
+ userRecharge.setPayId(4);
+ userRechargeMapper.insert(userRecharge);
+
+ return ServerResponse.createBySuccess(data.getUrl());
}
private ServerResponse getPaymentZero(String tradeAmount, String uipReqRul, User user, HttpServletRequest request) throws Exception {
@@ -1342,6 +1470,112 @@
}
}
+ @Override
+ public void rechargeCallbackFour(PayV2NotifyRequest vo, HttpServletResponse response) throws IOException {
+ log.info("支付4充值回调:{}", vo);
+ PrintWriter pw = response.getWriter();
+ response.setStatus(200);
+ response.setContentType("text/plain;charset=UTF-8");
+ if (vo == null || StringUtils.isBlank(vo.getMOrder())) {
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ synchronized (vo.getMOrder()) {
+ Map<String, String> signParams = new TreeMap<>();
+ if (vo.getMid() != null) {
+ signParams.put("mid", String.valueOf(vo.getMid()));
+ }
+ signParams.put("m_order", vo.getMOrder());
+ signParams.put("order_id", vo.getOrderId());
+ signParams.put("amount", vo.getAmount());
+ if (StringUtils.isNotBlank(vo.getUtr())) {
+ signParams.put("utr", vo.getUtr());
+ }
+ if (vo.getPayStatus() != null) {
+ signParams.put("pay_status", String.valueOf(vo.getPayStatus()));
+ }
+ if (vo.getRTime() != null) {
+ signParams.put("r_time", String.valueOf(vo.getRTime()));
+ }
+ if (!PayV2RsaSignUtil.verify(signParams, vo.getSign(), PayV2GatewayKeys.PLATFORM_PUBLIC_KEY)) {
+ log.error("支付4回调签名验证失败:{}, stringA={}", vo, PayV2RsaSignUtil.buildStringA(signParams));
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+
+ PaymentRecharge paymentRecharge = paymentRechargeService.getOne(new LambdaQueryWrapper<>(PaymentRecharge.class)
+ .eq(PaymentRecharge::getOrderNo, vo.getMOrder())
+ .eq(PaymentRecharge::getStatus, 1)
+ .last("limit 1"));
+ if (ObjectUtils.isEmpty(paymentRecharge)) {
+ log.info("支付4未找到充值订单");
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ UserRecharge userRecharge = userRechargeMapper.selectOne(new LambdaQueryWrapper<>(UserRecharge.class)
+ .eq(UserRecharge::getOrderSn, paymentRecharge.getOrderNo())
+ .eq(UserRecharge::getOrderStatus, 0)
+ .last("limit 1"));
+ if (ObjectUtils.isEmpty(userRecharge)) {
+ log.info("支付4未找到待支付充值记录");
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ return;
+ }
+
+ Integer ps = vo.getPayStatus();
+ if (ps != null && ps == 0) {
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ if (ps != null && (ps == 3 || ps == 4)) {
+ paymentRecharge.setStatus(2);
+ paymentRecharge.setAmount(new BigDecimal(vo.getAmount()));
+ paymentRecharge.setPayInfo(vo.toString());
+ paymentRechargeService.updateById(paymentRecharge);
+ userRecharge.setOrderStatus(2);
+ userRechargeMapper.updateById(userRecharge);
+ log.warn("支付4回调失败状态 pay_status={}:{}", ps, vo);
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ if (ps == null || ps != 1) {
+ log.error("支付4回调未知状态:{}", vo);
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ return;
+ }
+
+ userAssetsServices.availablebalanceChange(EStockType.IN.getCode(),
+ paymentRecharge.getUserId(), EUserAssets.TOP_UP, new BigDecimal(vo.getAmount()), "", "");
+
+ paymentRecharge.setStatus(2);
+ paymentRecharge.setAmount(new BigDecimal(vo.getAmount()));
+ paymentRecharge.setPayInfo(vo.toString());
+ paymentRechargeService.updateById(paymentRecharge);
+
+ userRecharge.setOrderStatus(1);
+ userRechargeMapper.updateById(userRecharge);
+
+ log.info("支付4充值回调完成");
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ }
+ }
+
/**
* 代付回调处理
*/
@@ -1560,6 +1794,125 @@
}
/**
+ * 代付 v2(payOutCreate)异步通知
+ */
+ @Override
+ public void payoutCallbackThree(PayV2PayoutNotifyRequest vo, HttpServletResponse response) throws IOException {
+ log.info("代付v2回调:{}", vo);
+ response.setStatus(200);
+ response.setContentType("text/plain;charset=UTF-8");
+ PrintWriter pw = response.getWriter();
+ if (vo == null || StringUtils.isBlank(vo.getMOrder()) || vo.getMid() == null
+ || vo.getPayStatus() == null || vo.getRTime() == null) {
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ Map<String, String> signParams = new TreeMap<>();
+ if (StringUtils.isNotBlank(vo.getAmount())) {
+ signParams.put("amount", vo.getAmount());
+ }
+ if (StringUtils.isNotBlank(vo.getErrMsg())) {
+ signParams.put("err_msg", vo.getErrMsg());
+ }
+ signParams.put("mid", String.valueOf(vo.getMid()));
+ signParams.put("m_order", vo.getMOrder());
+ signParams.put("order_id", vo.getOrderId());
+ signParams.put("pay_status", String.valueOf(vo.getPayStatus()));
+ signParams.put("r_time", String.valueOf(vo.getRTime()));
+ if (StringUtils.isNotBlank(vo.getUtr())) {
+ signParams.put("utr", vo.getUtr());
+ }
+ if (!PayV2RsaSignUtil.verify(signParams, vo.getSign(), PayV2GatewayKeys.PLATFORM_PUBLIC_KEY)) {
+ log.error("代付v2回调签名验证失败, stringA={}", PayV2RsaSignUtil.buildStringA(signParams));
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ synchronized (vo.getMOrder()) {
+ TransferResponse transfer = transferResponseService.getOne(
+ new LambdaQueryWrapper<>(TransferResponse.class)
+ .eq(TransferResponse::getMerTransferId, vo.getMOrder())
+ .last("limit 1"));
+ if (transfer == null) {
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ if (transfer.getCallbackState() != 0) {
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ UserWithdraw withdraw = userWithdrawMapper.selectByPrimaryKey(transfer.getWithId());
+ if (withdraw == null) {
+ pw.print("FAIL");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ Integer ps = vo.getPayStatus();
+ if (ps != null && ps == 0) {
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ return;
+ }
+ if (ps != null && ps == 1) {
+ transfer.setTradeResult(1);
+ transfer.setCallbackState(1);
+ transfer.setRespCode("SUCCESS");
+ transfer.setSignType("MD5");
+ transfer.setSign(vo.getSign());
+ transfer.setUpdatedAt(new Date());
+ transferResponseService.updateById(transfer);
+
+ withdraw.setWithStatus(1);
+ withdraw.setWithMsg("代付成功");
+ withdraw.setTransTime(new Date());
+ userWithdrawMapper.updateByPrimaryKeySelective(withdraw);
+
+ UserAssets assets = iUserAssetsServices.assetsByTypeAndUserId("IN", withdraw.getUserId());
+ if (assets != null) {
+ assets.setFreezeMoney(assets.getFreezeMoney().subtract(withdraw.getWithAmt()));
+ userAssetsMapper.updateById(assets);
+ }
+ } else if (ps != null && (ps == 2 || ps == 3)) {
+ String err = StringUtils.defaultIfBlank(vo.getErrMsg(), "");
+ transfer.setTradeResult(2);
+ transfer.setCallbackState(2);
+ transfer.setRespCode("FAIL");
+ transfer.setErrorMsg(err);
+ transfer.setSignType("MD5");
+ transfer.setSign(vo.getSign());
+ transfer.setUpdatedAt(new Date());
+ transferResponseService.updateById(transfer);
+
+ withdraw.setWithStatus(2);
+ withdraw.setWithMsg("Withdrawal failed:" + err);
+ withdraw.setTransTime(new Date());
+ userWithdrawMapper.updateByPrimaryKeySelective(withdraw);
+
+ UserAssets assets = iUserAssetsServices.assetsByTypeAndUserId("IN", withdraw.getUserId());
+ if (assets != null) {
+ assets.setAvailableBalance(assets.getAvailableBalance().add(withdraw.getWithAmt()));
+ assets.setFreezeMoney(assets.getFreezeMoney().subtract(withdraw.getWithAmt()));
+ userAssetsMapper.updateById(assets);
+ }
+ } else {
+ log.warn("代付v2回调未知 pay_status={}", ps);
+ }
+ pw.print("OK");
+ pw.flush();
+ pw.close();
+ }
+ }
+
+ /**
* 处理成功回调
*/
private void handleSuccess(TransferResponse transfer, UserWithdraw withdraw,
diff --git a/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java b/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
index 5059a2d..a663d55 100644
--- a/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
@@ -14,14 +14,18 @@
import com.github.pagehelper.PageInfo;
+import com.nq.common.PayV2GatewayKeys;
import com.nq.common.ServerResponse;
import com.nq.utils.*;
+import com.nq.utils.http.HttpClientRequest;
import com.nq.utils.http.HttpClientUtil;
+import com.nq.utils.pay.PayV2RsaSignUtil;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -51,6 +55,9 @@
public class UserWithdrawServiceImpl implements IUserWithdrawService {
private static final Logger log = LoggerFactory.getLogger(UserWithdrawServiceImpl.class);
+
+ private static final String PAY_V2_PAYOUT_CREATE_URL = "https://pay.kkpay100.com/v2/payOutCreate";
+ private static final int PAY_V2_MID = 10724;
@Autowired
@@ -398,9 +405,8 @@
}
}else if(state == 1){//走代付
return getObjectServerResponseTwo(withId, request, response, userWithdraw, user, userAssets);
- }else if(state == 2){//走代付
- return ServerResponse.createByErrorMsg("代付通道关闭");
-// return getObjectServerResponseOne(withId, request, response, userWithdraw, user, userAssets);
+ }else if(state == 4){//走代付
+ return getObjectServerResponseThree(withId, request, response, userWithdraw, user, userAssets);
}else{
return ServerResponse.createByErrorMsg("请选择对应的操作!");
}
@@ -457,7 +463,7 @@
// 6.1 接口调用失败(code != 0 或 success = false)
if (code != 0 || !success) {
- handleFailure(userAssets, userWithdraw, "代付请求失败:" + msg);
+ handleFailure(userAssets, userWithdraw, "Failure? Please contact customer service.");
return ServerResponse.createByErrorMsg("代付请求失败:" + msg, request);
}
@@ -492,6 +498,157 @@
}
+ /**
+ * 代付三:v2/payOutCreate(JSON + SHA256withRSA),IMPS
+ */
+ private ServerResponse getObjectServerResponseThree(Integer withId, HttpServletRequest request, HttpServletResponse response,
+ UserWithdraw userWithdraw, User user, UserAssets userAssets) throws Exception {
+ String merchantOrderNo = generatePayoutOrderId(withId);
+ BigDecimal amount = userWithdraw.getWithAmt().setScale(2, RoundingMode.HALF_UP);
+ String notifyUrl = "https://api.greenbackcaps.top/user/payoutCallbackThree.do";
+
+ UserBank bank = userBankMapper.selectOne(new LambdaQueryWrapper<UserBank>()
+ .eq(UserBank::getUserId, user.getId())
+ .eq(UserBank::getBankNo, userWithdraw.getBankNo())
+ .last("limit 1"));
+ String email = (bank != null && StringUtils.isNotBlank(bank.getBankEmail()))
+ ? bank.getBankEmail().trim() : (user.getId() + "@user.local");
+ if (email.length() > 64) {
+ email = email.substring(0, 64);
+ }
+
+ String userName = StringUtils.defaultIfBlank(userWithdraw.getWithName(), user.getRealName());
+ userName = StringUtils.defaultIfBlank(userName, "User");
+ if (userName.length() > 16) {
+ userName = userName.substring(0, 16);
+ }
+ String bankName = StringUtils.defaultIfBlank(userWithdraw.getBankName(), "BANK");
+ if (bankName.length() > 32) {
+ bankName = bankName.substring(0, 32);
+ }
+ String bankCode = StringUtils.defaultIfBlank(userWithdraw.getBankAddress(), "");
+ if (bankCode.length() > 32) {
+ bankCode = bankCode.substring(0, 32);
+ }
+ String bankCardNumber = StringUtils.defaultIfBlank(userWithdraw.getBankNo(), "");
+ if (bankCardNumber.length() > 32) {
+ bankCardNumber = bankCardNumber.substring(0, 32);
+ }
+ String address = "India";
+ int paymentType = 1;
+
+ Map<String, String> signParams = new TreeMap<>();
+ signParams.put("amount", amount.toPlainString());
+ signParams.put("address", address);
+ signParams.put("bankCardNumber", bankCardNumber);
+ signParams.put("bankCode", bankCode);
+ signParams.put("bankName", bankName);
+ signParams.put("currency", "INR");
+ signParams.put("email", email);
+ signParams.put("mid", String.valueOf(PAY_V2_MID));
+ signParams.put("mobile", user.getPhone());
+ signParams.put("notifyUrl", notifyUrl);
+ signParams.put("orderId", merchantOrderNo);
+ signParams.put("paymentType", String.valueOf(paymentType));
+ signParams.put("userName", userName);
+ String signBaseString = PayV2RsaSignUtil.buildStringA(signParams);
+ String sign = PayV2RsaSignUtil.sign(signBaseString, PayV2GatewayKeys.MERCHANT_PRIVATE_KEY_PEM);
+ log.info("代付v2签名串 stringA={}", signBaseString);
+
+ Map<String, Object> body = new LinkedHashMap<>();
+ body.put("mid", PAY_V2_MID);
+ body.put("orderId", merchantOrderNo);
+ body.put("amount", amount.toPlainString());
+ body.put("currency", "INR");
+ body.put("paymentType", paymentType);
+ body.put("bankName", bankName);
+ body.put("bankCode", bankCode);
+ body.put("bankCardNumber", bankCardNumber);
+ body.put("userName", userName);
+ body.put("email", email);
+ body.put("mobile", user.getPhone());
+ body.put("address", address);
+ body.put("notifyUrl", notifyUrl);
+ body.put("sign", sign);
+
+ String json = new Gson().toJson(body);
+ log.info("代付v2请求:{}", json);
+ String respStr = HttpClientRequest.doPostJsonBody(PAY_V2_PAYOUT_CREATE_URL, json);
+ log.info("代付v2响应:{}", respStr);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ PayV2PayoutCreateResponse resp = objectMapper.readValue(respStr, PayV2PayoutCreateResponse.class);
+ if (resp.getStatus() != 1 || resp.getData() == null) {
+ handleFailure(userAssets, userWithdraw, "Failure? Please contact customer service.");
+ return ServerResponse.createByErrorMsg("代付请求失败:" + (StringUtils.isNotBlank(resp.getMsg()) ? resp.getMsg() : "unknown"), request);
+ }
+ PayV2PayoutCreateResponse.Data data = resp.getData();
+ Map<String, String> respSign = new TreeMap<>();
+ if (StringUtils.isNotBlank(data.getAmount())) {
+ respSign.put("amount", data.getAmount());
+ }
+ if (StringUtils.isNotBlank(data.getErrMsg())) {
+ respSign.put("err_msg", data.getErrMsg());
+ }
+ if (data.getMId() != null) {
+ respSign.put("m_id", String.valueOf(data.getMId()));
+ }
+ if (StringUtils.isNotBlank(data.getMOrder())) {
+ respSign.put("m_order", data.getMOrder());
+ }
+ if (StringUtils.isNotBlank(data.getOrderId())) {
+ respSign.put("order_id", data.getOrderId());
+ }
+ if (data.getOrderStatus() != null) {
+ respSign.put("order_status", String.valueOf(data.getOrderStatus()));
+ }
+ if (!PayV2RsaSignUtil.verify(respSign, data.getSign(), PayV2GatewayKeys.PLATFORM_PUBLIC_KEY)) {
+ log.warn("代付v2返回签名未通过校验, stringA={}, sign={}",
+ PayV2RsaSignUtil.buildStringA(respSign), data.getSign());
+ }
+ Integer os = data.getOrderStatus();
+ if (os != null && (os == 2 || os == 3)) {
+ String em = StringUtils.defaultIfBlank(data.getErrMsg(), resp.getMsg());
+ handleFailure(userAssets, userWithdraw, "Failure? Please contact customer service.");
+ return ServerResponse.createByErrorMsg("代付失败:" + em, request);
+ }
+
+ saveTransferRecordV2(merchantOrderNo, data.getOrderId(), amount, user.getId(), withId, data.getSign());
+ userWithdraw.setWithStatus(4);
+ userWithdraw.setTransTime(new Date());
+ userWithdrawMapper.updateByPrimaryKeySelective(userWithdraw);
+
+ log.info("代付v2下单成功,商户订单号:{}", merchantOrderNo);
+ return ServerResponse.createBySuccessMsg("代付申请已提交,请等待处理");
+ }
+
+ private static boolean isValidIndiaMobile(String digits10) {
+ if (digits10 == null || digits10.length() != 10) {
+ return false;
+ }
+ char c = digits10.charAt(0);
+ return c >= '6' && c <= '9';
+ }
+
+ private void saveTransferRecordV2(String merchantOrderNo, String platformOrderNo, BigDecimal amount,
+ Integer userId, Integer withId, String sign) {
+ TransferResponse record = new TransferResponse();
+ record.setMerTransferId(merchantOrderNo);
+ record.setTradeNo(platformOrderNo);
+ record.setTransferAmount(amount);
+ record.setTradeResult(0);
+ record.setCallbackState(0);
+ record.setRespCode("SUCCESS");
+ record.setSignType("MD5");
+ record.setSign(sign);
+ record.setApplyDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+ record.setUserId(userId);
+ record.setWithId(withId);
+ record.setCreatedAt(new Date());
+ record.setUpdatedAt(new Date());
+ transferResponseService.save(record);
+ }
+
public static String doPost(String url, String data, Headers headers) throws IOException {
OkHttpClient customClient = new OkHttpClient();
Request request = new Request.Builder()
diff --git a/src/main/java/com/nq/utils/http/HttpClientRequest.java b/src/main/java/com/nq/utils/http/HttpClientRequest.java
index b82a37b..a0c90cf 100644
--- a/src/main/java/com/nq/utils/http/HttpClientRequest.java
+++ b/src/main/java/com/nq/utils/http/HttpClientRequest.java
@@ -184,5 +184,49 @@
return result;
}
+ /**
+ * POST JSON 字符串,Content-Type: application/json; charset=utf-8
+ */
+ public static String doPostJsonBody(String url, String jsonBody) {
+ CloseableHttpClient httpClient = null;
+ CloseableHttpResponse httpResponse = null;
+ String result = "";
+ httpClient = HttpClients.createDefault();
+ HttpPost httpPost = new HttpPost(url);
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(35000)
+ .setConnectionRequestTimeout(35000)
+ .setSocketTimeout(60000)
+ .build();
+ httpPost.setConfig(requestConfig);
+ httpPost.addHeader("Content-Type", "application/json; charset=utf-8");
+ try {
+ httpPost.setEntity(new StringEntity(jsonBody, "utf-8"));
+ httpResponse = httpClient.execute(httpPost);
+ HttpEntity entity = httpResponse.getEntity();
+ result = EntityUtils.toString(entity);
+ } catch (ClientProtocolException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (null != httpResponse) {
+ try {
+ httpResponse.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (null != httpClient) {
+ try {
+ httpClient.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return result;
+ }
+
}
diff --git a/src/main/resources/sql/site_pay_option.sql b/src/main/resources/sql/site_pay_option.sql
index 988d8b3..7651419 100644
--- a/src/main/resources/sql/site_pay_option.sql
+++ b/src/main/resources/sql/site_pay_option.sql
@@ -1,16 +1,17 @@
--- 支付设置表:默认、支付1、支付2、支付3,可排序,参数随排序变动,可开关
+-- 支付设置表:默认、支付1、支付2、支付3、支付4,可排序,参数随排序变动,可开关
CREATE TABLE IF NOT EXISTS `site_pay_option` (
`id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(32) NOT NULL COMMENT '显示名称:默认、支付1、支付2、支付3',
- `param` int(11) NOT NULL DEFAULT '0' COMMENT '参数 0/1/2/3,随排序变动',
+ `name` varchar(32) NOT NULL COMMENT '显示名称:默认、支付1、支付2、支付3、支付4',
+ `param` int(11) NOT NULL DEFAULT '0' COMMENT '参数 0/1/2/3/4,随排序变动',
`sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序序号',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否开启:1开启 0关闭',
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付设置(4项可拖拽排序、开关)';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付设置(可拖拽排序、开关)';
--- 初始化 4 条:默认:0 支付1:1 支付2:2 支付3:3
+-- 初始化 5 条:默认:0 支付1:1 支付2:2 支付3:3 支付4:4
INSERT INTO `site_pay_option` (`name`, `param`, `sort_order`, `enabled`) VALUES
('默认', 0, 0, 1),
('支付1', 1, 1, 1),
('支付2', 2, 2, 1),
-('支付3', 3, 3, 1);
+('支付3', 3, 3, 1),
+('支付4', 4, 4, 1);
diff --git a/src/main/resources/sql/site_pay_option_add_pay4.sql b/src/main/resources/sql/site_pay_option_add_pay4.sql
new file mode 100644
index 0000000..6e79252
--- /dev/null
+++ b/src/main/resources/sql/site_pay_option_add_pay4.sql
@@ -0,0 +1,5 @@
+-- 已有库升级:增加「支付4」一项(param=4)。可重复执行,已存在则不会插入。
+INSERT INTO `site_pay_option` (`name`, `param`, `sort_order`, `enabled`)
+SELECT '支付4', 4, 4, 1
+FROM (SELECT 1 AS x) AS t
+WHERE NOT EXISTS (SELECT 1 FROM `site_pay_option` WHERE `param` = 4 LIMIT 1);
diff --git "a/src/main/resources/sql/\346\224\257\344\273\230\350\256\276\347\275\256\346\216\245\345\217\243\345\257\271\346\216\245\346\226\207\346\241\243.md" "b/src/main/resources/sql/\346\224\257\344\273\230\350\256\276\347\275\256\346\216\245\345\217\243\345\257\271\346\216\245\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..01cb44f
--- /dev/null
+++ "b/src/main/resources/sql/\346\224\257\344\273\230\350\256\276\347\275\256\346\216\245\345\217\243\345\257\271\346\216\245\346\226\207\346\241\243.md"
@@ -0,0 +1,245 @@
+# 支付设置 - 接口对接文档
+
+## 一、功能说明
+
+- **支付设置**共 4 项:**默认:0**、**支付1:1**、**支付2:2**、**支付3:3**(冒号后为参数 `param`)。
+- 管理后台可**拖拽排序**,排序后每项的 `param` 按新顺序自动变为 0、1、2、3。
+- 管理后台可对每项**开启/关闭**,H5 仅能查询到已开启的项。
+
+---
+
+## 二、通用说明
+
+### 2.1 基础地址
+
+- 管理后台接口前缀:`/admin/payOption/`
+- H5 接口前缀:`/api/site/`
+
+(实际请求需加上项目部署的域名与上下文路径,例如:`https://your-domain.com/admin/payOption/list.do`)
+
+### 2.2 统一响应结构
+
+所有接口均返回 `ServerResponse` 结构:
+
+| 字段 | 类型 | 说明 |
+|----------|--------|-------------------------|
+| status | int | 状态码:0 成功,非 0 失败 |
+| msg | string | 提示信息,可为空 |
+| data | object | 业务数据,可为 null |
+
+**状态码约定:**
+
+- `0`:成功
+- `1`:业务错误(见 `msg`)
+- `2`:参数错误
+- `10`:需要登录(管理后台接口未登录时)
+
+---
+
+## 三、管理后台接口(需登录)
+
+### 3.1 支付设置列表
+
+获取全部 4 项(含已关闭),按当前排序,用于列表展示与拖拽。
+
+**请求**
+
+- **URL:** `GET` 或 `POST` `/admin/payOption/list.do`
+- **请求参数:** 无
+
+**响应示例**
+
+```json
+{
+ "status": 0,
+ "msg": null,
+ "data": [
+ {
+ "id": 1,
+ "name": "默认",
+ "param": 0,
+ "sortOrder": 0,
+ "enabled": 1
+ },
+ {
+ "id": 2,
+ "name": "支付1",
+ "param": 1,
+ "sortOrder": 1,
+ "enabled": 1
+ },
+ {
+ "id": 3,
+ "name": "支付2",
+ "param": 2,
+ "sortOrder": 2,
+ "enabled": 0
+ },
+ {
+ "id": 4,
+ "name": "支付3",
+ "param": 3,
+ "sortOrder": 3,
+ "enabled": 1
+ }
+ ]
+}
+```
+
+| 字段 | 类型 | 说明 |
+|------------|-------|-------------------------------|
+| id | int | 主键 |
+| name | string| 显示名称:默认/支付1/支付2/支付3 |
+| param | int | 参数 0/1/2/3,随排序变动 |
+| sortOrder | int | 排序序号,越小越靠前 |
+| enabled | int | 1 开启,0 关闭 |
+
+---
+
+### 3.2 拖拽排序
+
+根据新顺序更新每项的 `param` 与 `sort_order`(第 1 个 id 对应 param=0,第 2 个对应 param=1,以此类推)。
+
+**请求**
+
+- **URL:** `POST` `/admin/payOption/updateSort.do`
+- **Content-Type:** `application/json`
+- **请求体:** 新顺序下的 id 数组,例如把「支付3」拖到第一位则传:`[4, 1, 2, 3]`
+
+**请求体示例**
+
+```json
+[4, 1, 2, 3]
+```
+
+**成功响应示例**
+
+```json
+{
+ "status": 0,
+ "msg": "排序已更新",
+ "data": null
+}
+```
+
+**失败示例**(如未传或空数组)
+
+```json
+{
+ "status": 1,
+ "msg": "排序数据不能为空",
+ "data": null
+}
+```
+
+---
+
+### 3.3 开启/关闭
+
+**请求**
+
+- **URL:** `GET` 或 `POST` `/admin/payOption/setEnabled.do`
+- **请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|----------|------|------|---------------------|
+| id | int | 是 | 支付项 id |
+| enabled | int | 是 | 1=开启,0=关闭 |
+
+**请求示例**
+
+```
+GET /admin/payOption/setEnabled.do?id=3&enabled=0
+```
+
+**成功响应示例**
+
+```json
+{
+ "status": 0,
+ "msg": "已关闭",
+ "data": null
+}
+```
+
+或
+
+```json
+{
+ "status": 0,
+ "msg": "已开启",
+ "data": null
+}
+```
+
+**失败示例**(参数错误)
+
+```json
+{
+ "status": 1,
+ "msg": "enabled 只能为 0 或 1",
+ "data": null
+}
+```
+
+---
+
+## 四、H5 接口(无需登录)
+
+### 4.1 查询支付设置列表(仅已开启)
+
+H5 端用此接口获取当前**已开启**的支付项,按 `sort_order` 排序。可用于展示「默认:0」「支付1:1」等选项。
+
+**请求**
+
+- **URL:** `GET` 或 `POST` `/api/site/getPayOptionList.do`
+- **请求参数:** 无
+
+**响应示例**
+
+```json
+{
+ "status": 0,
+ "msg": null,
+ "data": [
+ {
+ "id": 1,
+ "name": "默认",
+ "param": 0,
+ "sortOrder": 0,
+ "enabled": 1
+ },
+ {
+ "id": 2,
+ "name": "支付1",
+ "param": 1,
+ "sortOrder": 1,
+ "enabled": 1
+ },
+ {
+ "id": 4,
+ "name": "支付3",
+ "param": 3,
+ "sortOrder": 3,
+ "enabled": 1
+ }
+ ]
+}
+```
+
+说明:若某项在管理后台被关闭,则不会出现在本接口的 `data` 中。H5 只需根据 `name` 与 `param` 展示,如「默认:0」「支付1:1」。
+
+---
+
+## 五、对接流程简述
+
+| 端 | 流程 |
+|----------|------|
+| 管理后台 | 1. 调用 `list.do` 展示列表 → 2. 拖拽后按新顺序把 id 数组调 `updateSort.do` → 3. 开关调 `setEnabled.do` |
+| H5 | 调用 `getPayOptionList.do` 获取已开启的支付项,按返回顺序展示(name + param) |
+
+---
+
+## 六、数据表与初始化
+
+表名:`site_pay_option`。建表及初始化语句见同目录下 **`site_pay_option.sql`**。首次部署需执行该 SQL。
--
Gitblit v1.9.3