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