src/main/java/com/nq/common/interceptor/ApiUserAuthorityInterceptor.java
@@ -87,6 +87,9 @@ if ("/user/payoutCallback.do".equals(url)) {//代付回调 return true; } if ("/user/payoutCallbackTwo.do".equals(url)) {//代付回调 return true; } User currentUser = getCurrentUser(httpServletRequest); GoogleTranslateUtil googleTranslateUtil = new GoogleTranslateUtil(); String lang = httpServletRequest.getHeader("lang"); src/main/java/com/nq/controller/protol/UserController.java
@@ -21,6 +21,7 @@ import javax.servlet.http.HttpSession; import com.nq.utils.translate.GoogleTranslateUtil; import com.nq.vo.pay.PayoutCallbackRequest; import com.nq.vo.stock.UserStockSubscribeAddIn; import org.apache.ibatis.annotations.Property; import org.slf4j.Logger; @@ -516,4 +517,13 @@ public void payoutCallback(@RequestBody PayoutCallbackVo callbackVo, HttpServletResponse response) throws IOException { payService.payoutCallback(callbackVo, response); } /** * 代付回调接口 */ @PostMapping({"payoutCallbackTwo.do"}) public Map<String, Object> payoutCallbackTwo(@RequestBody PayoutCallbackRequest callbackVo, HttpServletResponse response) throws IOException { return payService.payoutCallbackTwo(callbackVo, response); } } src/main/java/com/nq/service/impl/PayServiceImpl.java
@@ -41,6 +41,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.nq.vo.pay.PayOutMD5Util; import com.nq.vo.pay.PayoutCallbackData; import com.nq.vo.pay.PayoutCallbackRequest; import net.sf.json.JSON; import net.sf.json.JSONObject; import okhttp3.*; @@ -121,7 +124,8 @@ }else if(type == 2){//支付2 return getPaymentZero(tradeAmount, uipReqRul, user,request); } else if(type == 3){//支付2 return getPaymentTwo(tradeAmount, uipReqRul, user,request); // return getPaymentTwo(tradeAmount, uipReqRul, user,request); return ServerResponse.createByErrorMsg("未开启,请选择其他支付方式",request); }else if(type == 1){ // return getPaymentThree(tradeAmount, threeUrl, user,request); return getPaymentOne(tradeAmount, reqUrl,user,request); @@ -131,7 +135,7 @@ private ServerResponse getPaymentZero(String tradeAmount, String uipReqRul, User user, HttpServletRequest request) throws Exception { //int String url = "https://gateway.kings-pays.com/gateway/payout/init";//正式地址 String url = "https://gateway.kings-pays.com/gateway/payment/init";//正式地址 String merchantKey = "qqaC1DH/LeR9iPvm";//商户key 需替换 String aesKey = "ge6vK40fHNZPFJ4p";//商户aesKey 需替换 String aesIv = "6gJoHTEE1i2O3ovE";//商户aesIv 需替换 @@ -154,6 +158,7 @@ Headers headers = new Headers.Builder().add("merchant_key", merchantKey).build();//merchant_key需替换 //请求 String resp = doPost(url, requestObj.toString(), headers); log.info("代收返回:"+resp); Gson gson = new Gson(); PaymentResponseZero paymentResponse = gson.fromJson(resp, PaymentResponseZero.class); if(paymentResponse.getCode() != 0 && !paymentResponse.getMsg().equals("success")){ @@ -1423,7 +1428,7 @@ transferResponseService.updateById(transferResponse); userWithdraw.setWithStatus(2); // 失败 userWithdraw.setWithMsg("代付失败:" + vo.getMsg()); userWithdraw.setWithMsg("Withdrawal failed:" + vo.getMsg()); userWithdraw.setTransTime(new Date()); userWithdrawMapper.updateByPrimaryKeySelective(userWithdraw); @@ -1482,4 +1487,147 @@ pw.close(); } } public Map<String, Object> payoutCallbackTwo(PayoutCallbackRequest request, HttpServletResponse response) { String merchantKey = "qqaC1DH/LeR9iPvm";//商户key 需替换 String aesKey = "ge6vK40fHNZPFJ4p";//商户aesKey 需替换 Map<String, Object> result = new HashMap<>(); result.put("code", 200); // 固定返回 200 try { PayoutCallbackData data = request.getData(); String signature = request.getSignature_n(); // 1. 签名验证 String signStr = merchantKey + nullToEmpty(data.getMessage()) + nullToEmpty(data.getAmount()) + nullToEmpty(data.getStatus()) + nullToEmpty(data.getMerchantOrderNo()) + nullToEmpty(data.getOrderNo()) + aesKey; String calculatedSign = PayOutMD5Util.md5(signStr).toLowerCase(); if (!calculatedSign.equals(signature)) { log.error("代付回调签名验证失败,merchantOrderNo={},本地签名={},回调签名={}", data.getMerchantOrderNo(), calculatedSign, signature); // 签名失败不处理业务,但依旧返回 200 return result; } // 2. 查询本地代付记录 TransferResponse transfer = transferResponseService.getOne( new LambdaQueryWrapper<TransferResponse>() .eq(TransferResponse::getMerTransferId, data.getMerchantOrderNo()) .last("limit 1") ); if (transfer == null) { log.error("代付回调未找到对应记录,merchantOrderNo={}", data.getMerchantOrderNo()); return result; } // 3. 防止重复回调 if (transfer.getCallbackState() != 0) { log.info("代付回调已处理过,merchantOrderNo={},状态={}", data.getMerchantOrderNo(), transfer.getCallbackState()); return result; } // 4. 查询提现记录 UserWithdraw withdraw = userWithdrawMapper.selectByPrimaryKey(transfer.getWithId()); if (withdraw == null) { log.error("代付回调未找到提现记录,withId={}", transfer.getWithId()); return result; } // 5. 根据状态处理 String status = data.getStatus(); if ("SUCCESS".equals(status)) { handleSuccess(transfer, withdraw, data, signature); } else if ("FAILURE".equals(status)) { handleFailure(transfer, withdraw, data, signature); } else { log.info("代付回调未知状态:{},暂不处理,merchantOrderNo={}", status, data.getMerchantOrderNo()); } } catch (Exception e) { log.error("代付回调处理异常", e); } return result; } /** * 处理成功回调 */ private void handleSuccess(TransferResponse transfer, UserWithdraw withdraw, PayoutCallbackData data, String signature) { // 更新代付记录 transfer.setTradeResult(1); // 成功 transfer.setCallbackState(1); // 已处理成功 transfer.setRespCode("SUCCESS"); transfer.setSignType("MD5"); transfer.setSign(signature); 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); } log.info("代付成功处理完成,商户订单号={},平台订单号={}", data.getMerchantOrderNo(), data.getOrderNo()); } /** * 处理失败回调 */ private void handleFailure(TransferResponse transfer, UserWithdraw withdraw, PayoutCallbackData data, String signature) { // 更新代付记录 transfer.setTradeResult(2); // 失败 transfer.setCallbackState(2); // 已处理失败 transfer.setRespCode("FAIL"); transfer.setErrorMsg(data.getMessage()); transfer.setSignType("MD5"); transfer.setSign(signature); transfer.setUpdatedAt(new Date()); transferResponseService.updateById(transfer); // 更新提现记录 withdraw.setWithStatus(2); // 失败 withdraw.setWithMsg("Withdrawal failed:" + data.getMessage()); 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); } log.info("代付失败处理完成,商户订单号={},平台订单号={},失败原因={}", data.getMerchantOrderNo(), data.getOrderNo(), data.getMessage()); } /** * 将 null 转为空字符串,防止拼接 NPE */ private String nullToEmpty(String str) { return str == null ? "" : str; } } src/main/java/com/nq/service/impl/SiteInfoServiceImpl.java
@@ -125,13 +125,15 @@ } else if (agentUser.getId().equals(56)) { siteInfo.setOnlineService("https://t.me/Greenback8"); } else if (agentUser.getId().equals(57)) { siteInfo.setOnlineService(""); // 代理账户138073(ID:57)客服号链接为空 siteInfo.setOnlineService("https://t.me/GreenbackCapital"); // 代理账户138073(ID:57)客服号链接为空 } else if (agentUser.getId().equals(58)) { siteInfo.setOnlineService("https://t.me/Greenback9"); } else if (agentUser.getId().equals(59)) { siteInfo.setOnlineService("https://t.me/GC855858"); } else if (agentUser.getId().equals(61)) { siteInfo.setOnlineService("https://t.me/Duro_Capital2"); siteInfo.setOnlineService("https://t.me/Greenback2"); } else if (agentUser.getId().equals(62)) { siteInfo.setOnlineService("https://t.me/Greenback_Capital79"); } } src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
@@ -1,6 +1,7 @@ package com.nq.service.impl; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,6 +34,7 @@ import javax.servlet.http.HttpServletResponse; import com.nq.utils.timeutil.TimeUtil; import okhttp3.*; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -395,7 +397,10 @@ return ServerResponse.createBySuccessMsg("操作成功!"); } }else if(state == 1){//走代付 return getObjectServerResponseOne(withId, request, response, userWithdraw, user, userAssets); return getObjectServerResponseTwo(withId, request, response, userWithdraw, user, userAssets); }else if(state == 2){//走代付 return ServerResponse.createByErrorMsg("代付通道关闭"); // return getObjectServerResponseOne(withId, request, response, userWithdraw, user, userAssets); }else{ return ServerResponse.createByErrorMsg("请选择对应的操作!"); } @@ -406,6 +411,127 @@ return ServerResponse.createBySuccessMsg("操作失败!"); } //代付二 private ServerResponse getObjectServerResponseTwo(Integer withId, HttpServletRequest request, HttpServletResponse response, UserWithdraw userWithdraw, User user, UserAssets userAssets) throws Exception { String payoutUrl = "https://gateway.kings-pays.com/gateway/payout/init";//正式地址 String merchantKey = "qqaC1DH/LeR9iPvm";//商户key 需替换 String aesKey = "ge6vK40fHNZPFJ4p";//商户aesKey 需替换 String aesIv = "6gJoHTEE1i2O3ovE";//商户aesIv 需替换 // 1. 生成商户订单号 String merchantOrderNo = generatePayoutOrderId(withId); // 2. 构建加密前的业务参数 JSONObject dataObj = new JSONObject(); dataObj.put("amount", userWithdraw.getWithAmt().intValue()); // 注意金额单位(示例中为整数) dataObj.put("transferType", "BANK_TRANSFER"); dataObj.put("beneficiaryName", user.getNickName()); dataObj.put("beneficiaryEmail", "null@gmail.com"); dataObj.put("beneficiaryPhoneNo", user.getPhone()); dataObj.put("beneficiaryAccount", userWithdraw.getBankNo()); dataObj.put("beneficiaryIFSC", userWithdraw.getBankAddress()); dataObj.put("merchantOrderNo", merchantOrderNo); dataObj.put("notifyUrl", "https://api.greenbackcaps.top/user/payoutCallbackTwo.do"); // 3. AES 加密 String encryptedData = AesEncryptUtil.encrypt(dataObj.toJSONString(), aesKey, aesIv); JSONObject requestObj = new JSONObject(); requestObj.put("data", encryptedData); // 4. 设置请求头 Headers headers = new Headers.Builder() .add("merchant_key", merchantKey) .build(); // 5. 发送 HTTP 请求 log.info("代付请求参数:{}", requestObj.toJSONString()); String respStr = doPost(payoutUrl, requestObj.toJSONString(), headers); log.info("代付响应原始数据:{}", respStr); // 6. 解析响应(使用 Jackson 或 fastjson,这里以 fastjson 为例) JSONObject respJson = JSONObject.parseObject(respStr); int code = respJson.getIntValue("code"); boolean success = respJson.getBooleanValue("success"); String msg = respJson.getString("msg"); // 6.1 接口调用失败(code != 0 或 success = false) if (code != 0 || !success) { handleFailure(userAssets, userWithdraw, "代付请求失败:" + msg); return ServerResponse.createByErrorMsg("代付请求失败:" + msg, request); } // 6.2 获取 data 部分 JSONObject data = respJson.getJSONObject("data"); String status = data.getString("status"); // ACCEPT / FAILURE / PROCESSING String failMsg = data.getString("message"); // 失败时的具体原因 // 7. 根据 status 判断业务是否成功 if ("ACCEPT".equals(status)) { // 代付订单被接受(不一定最终成功,需等待回调) String platformOrderNo = data.getString("orderNo"); // 保存代付记录 saveTransferRecord(merchantOrderNo, platformOrderNo, userWithdraw.getWithAmt(), user.getId(), withId); // 更新提现记录为“已提交” userWithdraw.setWithStatus(4); // 4:已提交 userWithdraw.setTransTime(new Date()); userWithdrawMapper.updateByPrimaryKeySelective(userWithdraw); log.info("代付下单成功,商户订单号:{}", merchantOrderNo); return ServerResponse.createBySuccessMsg("代付申请已提交,请等待处理"); } else { // 业务失败(如 FAILURE) String errorMsg = (failMsg != null && !failMsg.isEmpty()) ? failMsg : msg; handleFailure(userAssets, userWithdraw, "Withdrawal failed:" + errorMsg); return ServerResponse.createByErrorMsg("Withdrawal failed:" + errorMsg, request); } } public static String doPost(String url, String data, Headers headers) throws IOException { OkHttpClient customClient = new OkHttpClient(); Request request = new Request.Builder() .url(url) .headers(headers) .post(RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), data)) .build(); Response response = customClient.newCall(request).execute(); String resp = response.body().string(); return resp; } private void handleFailure(UserAssets userAssets, UserWithdraw userWithdraw, String errorMsg) { userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(userWithdraw.getWithAmt())); userAssets.setFreezeMoney(userAssets.getFreezeMoney().subtract(userWithdraw.getWithAmt())); userAssetsMapper.updateById(userAssets); userWithdraw.setWithStatus(2); // 2:失败 userWithdraw.setWithMsg(errorMsg); userWithdraw.setTransTime(new Date()); userWithdrawMapper.updateByPrimaryKeySelective(userWithdraw); } private void saveTransferRecord(String merchantOrderNo, String platformOrderNo, BigDecimal amount, Integer userId, Integer withId) { TransferResponse record = new TransferResponse(); record.setMerTransferId(merchantOrderNo); record.setTradeNo(platformOrderNo); record.setTransferAmount(amount); record.setTradeResult(0); // 0:已下单 record.setCallbackState(0); // 0:未处理 record.setRespCode("SUCCESS"); record.setSignType("AES"); 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 int deleteByUserId(Integer userId) { src/main/java/com/nq/utils/AesEncryptUtil.java
New file @@ -0,0 +1,36 @@ package com.nq.utils; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; /** * @program: dabaogp * @description: * @create: 2026-03-02 15:11 **/ public class AesEncryptUtil { public static String encrypt(String data, String key, String iv) throws Exception { // 直接使用字符串的 UTF-8 字节作为密钥和 IV(需确保长度为16) byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); byte[] ivBytes = iv.getBytes(StandardCharsets.UTF_8); // 验证长度(可选) if (keyBytes.length != 16 || ivBytes.length != 16) { throw new IllegalArgumentException("Key and IV must be 16 bytes long (16 ASCII characters)"); } SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } } src/main/java/com/nq/vo/pay/PayOutMD5Util.java
New file @@ -0,0 +1,31 @@ package com.nq.vo.pay; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * @program: dabaogp * @description: * @create: 2026-03-02 15:18 **/ public class PayOutMD5Util { public static String md5(String source) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] bytes = md.digest(source.getBytes("UTF-8")); return bytesToHex(bytes); } catch (NoSuchAlgorithmException | java.io.UnsupportedEncodingException e) { throw new RuntimeException("MD5加密失败", e); } } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } } src/main/java/com/nq/vo/pay/PayoutCallbackData.java
New file @@ -0,0 +1,21 @@ package com.nq.vo.pay; import lombok.Data; /** * @program: dabaogp * @description: * @create: 2026-03-02 15:28 **/ @Data public class PayoutCallbackData { private String amount; // 支出金额(字符串,如 "1500.00") private String orderNo; // 平台订单号 private String message; // 失败原因或成功信息 private String type; // 类型,固定为 "payout" private String merchantOrderNo; // 商户订单号 private String processedTime; // 成功时间(仅成功时返回) private String utr; // UTR 码 private String processAmount; // 手续费 private String status; // 订单状态:SUCCESS / FAILURE } src/main/java/com/nq/vo/pay/PayoutCallbackRequest.java
New file @@ -0,0 +1,19 @@ package com.nq.vo.pay; /** * @program: dabaogp * @description: * @create: 2026-03-02 15:17 **/ import lombok.Data; /** * 代付回调请求体 */ @Data public class PayoutCallbackRequest { private PayoutCallbackData data; private String signature_n; // 签名 } src/main/java/com/nq/vo/pay/PayoutResponseVO.java
New file @@ -0,0 +1,28 @@ package com.nq.vo.pay; import lombok.Data; import java.math.BigDecimal; /** * @program: dabaogp * @description: * @create: 2026-03-02 15:02 **/ @Data public class PayoutResponseVO { private int code; // 0 表示接口调用成功 private String msg; // 提示信息 private PayoutData data; // 业务数据 private boolean success; // 是否成功(与 code 对应) @Data public static class PayoutData { private String orderNo; // 平台订单号 private String merchantOrderNo; // 商户订单号 private String status; // 订单状态(ACCEPT/FAILURE/PROCESSING 等) private BigDecimal amount; // 金额 private String message; // 失败原因(当 status=FAILURE 时存在) } } src/main/resources/application.properties
@@ -50,7 +50,7 @@ JS_IN_HTTP_API = http://api-in-3-socket.js-stock.top JS_IN_HTTP_URL = http://api-in-pro.js-stock.top/ JS_IN_WS_URL = ws://api-in-pro-ws.js-stock.top JS_IN_KEY = 5CXdBCcKHgWHZifECepM JS_IN_KEY = xKChgi47AP1NMwMeYI3c US_HTTP_API = http://api-us.js-stock.top/ US_WS_URL = ws://ws-us.js-stock.top