From befbb28b2810ed108d2744bceee2bb9b3edaa9bc Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Sat, 13 Jun 2026 13:53:04 +0800
Subject: [PATCH] feat(mall): 添加USDT支付功能和相关服务

---
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewContentModel.java |   24 +
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/OkHttpUtil2.java                |  203 +++++++++++++++
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java           |   21 +
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewModel.java        |   17 +
 src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java                             |  185 ++++++++++++++
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/constant/OrderConstants.java          |   43 +++
 src/main/java/cc/mrbird/febs/mall/service/impl/ApiChatPayServiceImpl.java                           |  123 +++++++++
 src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallOrderInfoServiceImpl.java                     |   36 ++
 src/main/java/cc/mrbird/febs/mall/dto/ApiOrderPayDto.java                                           |   22 +
 src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java                                |    2 
 src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallMemberServiceImpl.java                        |   20 +
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/enums/SalesServiceEnums.java          |    8 
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallShoppingCartController.java    |    1 
 src/main/java/cc/mrbird/febs/mall/service/ApiChatPayService.java                                    |   10 
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiLoginController.java               |   12 
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallNewsController.java            |    4 
 src/main/java/cc/mrbird/febs/mall/service/IApiMallOrderInfoService.java                             |    2 
 src/main/java/cc/mrbird/febs/mall/vo/ApiOrderPayVo.java                                             |   25 +
 src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java                                         |    1 
 src/main/java/cc/mrbird/febs/mall/vo/MallMoneyChangeVo.java                                         |   19 +
 20 files changed, 770 insertions(+), 8 deletions(-)

diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiLoginController.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiLoginController.java
index 24218d8..7ecc950 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiLoginController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiLoginController.java
@@ -6,6 +6,7 @@
 import cc.mrbird.febs.common.utils.RedisUtils;
 import cc.mrbird.febs.mall.dto.*;
 import cc.mrbird.febs.mall.service.IApiMallMemberService;
+import cc.mrbird.febs.mall.vo.MallMoneyChangeVo;
 import cc.mrbird.febs.mall.vo.MallSalesServiceVo;
 import cc.mrbird.febs.pay.model.WxGenerateQrCodeDto;
 import cc.mrbird.febs.pay.service.IXcxPayService;
@@ -71,4 +72,15 @@
         return memberService.salesService();
     }
 
+
+
+    @ApiOperation(value = "价格转换", notes = "价格转换")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "success", response = MallMoneyChangeVo.class)
+    })
+    @GetMapping(value = "/moneyChange")
+    public FebsResponse moneyChange() {
+        return memberService.moneyChange();
+    }
+
 }
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/ApiMallNewsController.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallNewsController.java
similarity index 95%
rename from src/main/java/cc/mrbird/febs/mall/controller/ApiMallNewsController.java
rename to src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallNewsController.java
index cbdaa62..d2026c5 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/ApiMallNewsController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallNewsController.java
@@ -1,4 +1,4 @@
-package cc.mrbird.febs.mall.controller;
+package cc.mrbird.febs.mall.controller.dependentStation;
 
 import cc.mrbird.febs.common.entity.FebsResponse;
 import cc.mrbird.febs.mall.dto.NewsListDto;
@@ -23,7 +23,7 @@
 @RestController
 @RequestMapping(value = "/api/news")
 @RequiredArgsConstructor
-@Api(value = "ApiMallNewsController", tags = "新闻接口类")
+@Api(value = "ApiMallNewsController", tags = "ds-新闻接口")
 public class ApiMallNewsController {
 
     private final IApiMallNewsService newsService;
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java
index 22cebeb..2417866 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java
@@ -5,6 +5,7 @@
 import cc.mrbird.febs.common.entity.LimitType;
 import cc.mrbird.febs.mall.dto.*;
 import cc.mrbird.febs.mall.service.IApiMallOrderInfoService;
+import cc.mrbird.febs.mall.vo.ApiOrderPayVo;
 import cc.mrbird.febs.mall.vo.OrderDetailVo;
 import cc.mrbird.febs.mall.vo.OrderListVo;
 import cc.mrbird.febs.pay.service.IXcxPayService;
@@ -43,12 +44,17 @@
     }
 
     @ApiOperation(value = "创建订单", notes = "创建订单")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "success", response = ApiOrderPayVo.class)
+    })
     @PostMapping(value = "/createOrder")
     @Limit(key = "createOrder", period = 1, count = 1, name = "注册", prefix = "limit",limitType = LimitType.IP)
     public FebsResponse createOrder(@RequestBody @Validated AddOrderDto addOrderDto) {
         Long orderId = mallOrderInfoService.createOrder(addOrderDto);
-
-        return new FebsResponse().success().data(orderId).message("Order successfully created");
+        ApiOrderPayDto apiOrderPayDto = new ApiOrderPayDto();
+        apiOrderPayDto.setOrderId(orderId);
+        apiOrderPayDto.setPayType(3);
+        return mallOrderInfoService.payOrderByCoin(apiOrderPayDto);
     }
 
     @ApiOperation(value = "取消订单", notes = "取消订单")
@@ -70,6 +76,17 @@
         return new FebsResponse().success().data(map).message("支付成功");
     }
 
+
+    @ApiOperation(value = "USDT支付订单", notes = "USDT支付订单")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "success", response = ApiOrderPayVo.class)
+    })
+    @PostMapping(value = "/payOrderByCoin", produces = "application/json")
+    public FebsResponse payOrder(@RequestBody @Validated ApiOrderPayDto payDto) {
+
+        return mallOrderInfoService.payOrderByCoin(payDto);
+    }
+
     @ApiOperation(value = "订单列表", notes = "订单列表")
     @ApiResponses({
             @ApiResponse(code = 200, message = "success", response = OrderListVo.class)
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallShoppingCartController.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallShoppingCartController.java
index 7dc5036..f5fba52 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallShoppingCartController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallShoppingCartController.java
@@ -54,7 +54,6 @@
 
     @ApiOperation(value = "从购物车中删除商品", notes = "从购物车中删除商品")
     @PostMapping(value = "/delGoods")
-    @Limit(key = "delGoods", period = 1, count = 1, name = "注册", prefix = "limit",limitType = LimitType.IP)
     public FebsResponse delGoods(@RequestBody @Validated DelCartGoodsDto delCartGoodsDto) {
         List<String> ids = StrUtil.split(delCartGoodsDto.getIds(), ',');
         mallShoppingCartService.removeByIds(ids);
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/constant/OrderConstants.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/constant/OrderConstants.java
new file mode 100644
index 0000000..39b8621
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/constant/OrderConstants.java
@@ -0,0 +1,43 @@
+package cc.mrbird.febs.mall.controller.dependentStation.constant;
+
+public class OrderConstants {
+
+    public static final String ORDER_EXPIRE_MINUTES = "chat.order.expire";
+
+    /**
+     * 充值类型 1-自助充值 2-后台充值
+     */
+    public static final Integer ORDER_CHARGE_TYPE_MEMBER = 1;
+    public static final Integer ORDER_CHARGE_TYPE_SYSTEM = 2;
+    public static final String ORDER_CHARGE_TYPE = "order_charge_type";
+
+
+    /**
+     * 订单生效状态 1-生效中 2-已失效
+     */
+    public static final Integer ORDER_WORK_SUCCESS = 1;
+    public static final Integer ORDER_WORK_FAIL = 2;
+    public static final String ORDER_WORK_STATE = "order_work_state";
+
+    /**
+     * 支付状态 1-待支付 2-支付中 3-支付成功 0-支付失败
+     */
+    public static final Integer ORDER_STATE_WAIT = 1;
+    public static final Integer ORDER_STATE_ING = 2;
+    public static final Integer ORDER_STATE_SUCCESS = 3;
+    public static final Integer ORDER_STATE_FAIL = 0;
+    public static final String ORDER_STATE = "order_state";
+
+    /**
+     * 支付方式 1-微信 2-支付宝 3-USDT
+     */
+    public static final Integer PAY_TYPE_WECHAT = 1;
+    public static final Integer PAY_TYPE_ALIPAY = 2;
+    public static final Integer PAY_TYPE_USDT = 3;
+    public static final Integer PAY_TYPE_SYSTEM = 4;
+    public static final String ORDER_PAY_TYPE = "order_pay_type";
+
+    public static final String TRC20_SYSTEM_ADDRESS = "pay.trc20.address";
+
+    public static final String TRC20_ORDER_KEY = "pay:trc20:order:";
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/enums/SalesServiceEnums.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/enums/SalesServiceEnums.java
index 3245649..a43405e 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/enums/SalesServiceEnums.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/enums/SalesServiceEnums.java
@@ -5,7 +5,13 @@
 @Getter
 public enum SalesServiceEnums {
 
-    // 发货人
+    // 价格转换
+    USD("MONEY_CHANGE", "USD"),
+
+    // trc20地址
+    TRC_ADDRESS("SALES_SERVICE", "TRC_ADDRESS"),
+
+    // 售后方式
     ADDRESS("SALES_SERVICE", "ADDRESS"),
     WORKINGHOURS("SALES_SERVICE", "WORKINGHOURS"),
     WHATSAPP("SALES_SERVICE", "WHATSAPP"),
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/OkHttpUtil2.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/OkHttpUtil2.java
new file mode 100644
index 0000000..45dc0b0
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/OkHttpUtil2.java
@@ -0,0 +1,203 @@
+package cc.mrbird.febs.mall.controller.dependentStation.utils;
+
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.Objects;
+
+public class OkHttpUtil2 {
+    protected final static Logger logger = LoggerFactory.getLogger(OkHttpUtil2.class);
+    private static Dispatcher dispatcher = new Dispatcher();
+
+    static {
+        dispatcher.setMaxRequests(200);
+        dispatcher.setMaxRequestsPerHost(100);
+    }
+
+//    private static OkHttpClient httpClient = new OkHttpClient.Builder().dispatcher(dispatcher).build();
+    private static OkHttpClient httpClient = createUnsafeOkHttpClient();
+
+    private static OkHttpClient createUnsafeOkHttpClient() {
+        try {
+            final TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public void checkClientTrusted(X509Certificate[] chain, String authType) {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(X509Certificate[] chain, String authType) {
+                        }
+
+                        @Override
+                        public X509Certificate[] getAcceptedIssuers() {
+                            return new X509Certificate[0];
+                        }
+                    }
+            };
+
+            final SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, trustAllCerts, new SecureRandom());
+            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+
+            OkHttpClient.Builder builder = new OkHttpClient.Builder();
+            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
+            builder.hostnameVerifier((hostname, session) -> true);
+            builder.dispatcher(dispatcher);
+
+            return builder.build();
+        } catch (NoSuchAlgorithmException | KeyManagementException e) {
+            throw new RuntimeException("Failed to create unsafe SSL client", e);
+        }
+    }
+    public static byte[] doGet(String url, Map<String, String[]> header, Map<String, String[]> params, String expectContentType) {
+        Request.Builder builder = new Request.Builder();
+        addHeader(builder, header);
+        addUrlParam(builder, url, params);
+        return requestExec(builder.build(), expectContentType);
+    }
+
+    public static byte[] doGetSingle(String url, Map<String, String> header, Map<String, String> params, String expectContentType) {
+        Request.Builder builder = new Request.Builder();
+        addHeaderSingle(builder, header);
+        addUrlParamSingle(builder, url, params);
+        return requestExec(builder.build(), expectContentType);
+    }
+
+    public static byte[] doPost(String url, Map<String, String[]> header, Map<String, String[]> body, String expectContentType) {
+        Request.Builder builder = new Request.Builder().url(url);
+        addHeader(builder, header);
+        addBodyParam(builder, body, "POST");
+        return requestExec(builder.build(), expectContentType);
+    }
+
+
+
+    private static void addHeaderSingle(Request.Builder builder, Map<String, String> header) {
+        if (header == null) {
+            return;
+        }
+        for (String key : header.keySet()) {
+            String value = header.get(key);
+            if (value != null) {
+                builder.addHeader(key, value);
+            }
+        }
+    }
+
+    private static void addHeader(Request.Builder builder, Map<String, String[]> header) {
+        if (header == null) {
+            return;
+        }
+        for (String key : header.keySet()) {
+            String[] values = header.get(key);
+            if (values != null) {
+                for (String value : values) {
+                    builder.addHeader(key, value);
+                }
+            }
+        }
+    }
+
+    private static void addUrlParam(Request.Builder builder, String url, Map<String, String[]> params) {
+        if (params == null) {
+            return;
+        }
+        HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+        for (String key : params.keySet()) {
+            String[] values = params.get(key);
+            if (values != null) {
+                for (String value : values) {
+                    urlBuilder.addQueryParameter(key, value);
+                }
+            }
+        }
+        builder.url(urlBuilder.build());
+    }
+
+    private static void addUrlParamSingle(Request.Builder builder, String url, Map<String, String> params) {
+        if (params == null) {
+            return;
+        }
+        HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+        for (String key : params.keySet()) {
+            String value = params.get(key);
+            if (value != null) {
+                urlBuilder.addQueryParameter(key, value);
+            }
+        }
+        builder.url(urlBuilder.build());
+    }
+
+    private static void addBodyParam(Request.Builder builder, Map<String, String[]> body, String method) {
+        if (body == null) {
+            return;
+        }
+        FormBody.Builder formBodyBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
+        for (String key : body.keySet()) {
+            String[] values = body.get(key);
+            if (values != null) {
+                for (String value : values) {
+                    formBodyBuilder.add(key, value);
+                }
+            }
+        }
+        builder.method(method, formBodyBuilder.build());
+    }
+
+    private static byte[] requestExec(Request request, String expectContentType) {
+        Objects.requireNonNull(request, "okHttp request is null");
+
+        try (Response response = httpClient.newCall(request).execute()) {
+            if (response.code() == 200) {
+                ResponseBody body = response.body();
+                if (body != null) {
+                    byte[] bytes = body.bytes();
+                    String contentType = response.header("Content-Type");
+                    if (contentType != null && !contentType.contains(expectContentType)) {
+                        String res = new String(bytes, StandardCharsets.UTF_8);
+                        System.out.println(res);
+                        return bytes;
+                    }
+                    return bytes;
+                }
+                logger.error("response body is null");
+                System.out.println("response body is null");
+            } else {
+                ResponseBody body = response.body();
+                String res = "";
+                byte[] bytes = null;
+                if (body != null) {
+                    bytes = body.bytes();
+                    String contentType = response.header("Content-Type");
+                    if (contentType != null && !contentType.contains(expectContentType)) {
+                        //res = new String(body.bytes(), StandardCharsets.UTF_8);
+                        //return body.bytes();
+                    }
+                    res = new String(bytes, StandardCharsets.UTF_8);
+
+                }
+                logger.error("request failed, http code:{},responseBody:{} ", response.code(), res);
+                System.out.println("request failed, http code: " + response.code());
+
+                return bytes;
+            }
+        } catch (IOException ioException) {
+            logger.error("request exec error:", ioException);
+            System.out.println("request exec error: " + ioException.getMessage());
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewContentModel.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewContentModel.java
new file mode 100644
index 0000000..1a9f766
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewContentModel.java
@@ -0,0 +1,24 @@
+package cc.mrbird.febs.mall.controller.dependentStation.utils;
+
+import lombok.Data;
+
+/**
+ * @author your name
+ * @description TODO
+ * @date 2025/5/30
+ */
+@Data
+public class Trc20TokenviewContentModel {
+    private Integer index;
+    private Integer confirmations;
+    private Integer block_no;
+    private String token;
+    private String tokenAddr;
+    private String tokenSymbol;
+    private String tokenDecimals;
+    private Long time;
+    private String txid;
+    private String from;
+    private String to;
+    private String value;
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewModel.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewModel.java
new file mode 100644
index 0000000..8f4d600
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewModel.java
@@ -0,0 +1,17 @@
+package cc.mrbird.febs.mall.controller.dependentStation.utils;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author your name
+ * @description TODO
+ * @date 2025/5/30
+ */
+@Data
+public class Trc20TokenviewModel {
+    private Integer code;
+    private String msg;
+    List<Trc20TokenviewContentModel> data;
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/dto/ApiOrderPayDto.java b/src/main/java/cc/mrbird/febs/mall/dto/ApiOrderPayDto.java
new file mode 100644
index 0000000..0317ad9
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/dto/ApiOrderPayDto.java
@@ -0,0 +1,22 @@
+package cc.mrbird.febs.mall.dto;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel(value = "ApiOrderPayDto", description = "参数")
+public class ApiOrderPayDto {
+
+
+    @NotNull(message = "订单ID不能为空")
+    @ApiModelProperty(value = "订单ID", example = "you_ke_*****")
+    private Long orderId;
+
+    @NotNull(message = "支付方式不能为空")
+    @ApiModelProperty(value = "支付方式 1-微信 2-支付宝 3-USDT", example = "you_ke_*****")
+    private Integer payType;
+}
\ No newline at end of file
diff --git a/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java b/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java
index 5754d1c..e2350ea 100644
--- a/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java
+++ b/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java
@@ -26,6 +26,7 @@
 
     private Date payTime;
 
+    private String tradeHash;
     private BigDecimal amount;
 
     private String payMethod;
diff --git a/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java b/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
new file mode 100644
index 0000000..ace4816
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
@@ -0,0 +1,185 @@
+package cc.mrbird.febs.mall.quartz;
+
+import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
+import cc.mrbird.febs.common.utils.RedisUtils;
+import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants;
+import cc.mrbird.febs.mall.controller.dependentStation.enums.SalesServiceEnums;
+import cc.mrbird.febs.mall.controller.dependentStation.utils.OkHttpUtil2;
+import cc.mrbird.febs.mall.controller.dependentStation.utils.Trc20TokenviewContentModel;
+import cc.mrbird.febs.mall.controller.dependentStation.utils.Trc20TokenviewModel;
+import cc.mrbird.febs.mall.entity.DataDictionaryCustom;
+import cc.mrbird.febs.mall.entity.MallOrderInfo;
+import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper;
+import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+@Slf4j
+@Component
+@ConditionalOnProperty(prefix = "system", name = "job", havingValue = "true")
+public class ChatTrc20ChargeOkLinkTask {
+
+
+    @Resource
+    private RedisUtils redisUtils;
+    @Resource
+    private MallOrderInfoMapper mallOrderInfoMapper;
+    @Resource
+    private DataDictionaryCustomMapper dataDictionaryCustomMapper;
+    /**
+     * 五分钟  毫秒
+     */
+    private final static long TIME_INTERVAL = 300000*6;
+
+    private final static String TRC20_TRANSFER_API = "https://apilist.tronscanapi.com/api/token_trc20/transfers";
+
+    public final static String TRC20_CONTRACT_ADDRESS = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
+
+    private final static String TRON_API_KEY = "32c9841d-19ae-48cf-a535-cf56d9d25f1c";
+
+    private static Map<String, String> REQUEST_HEADER = new HashMap<>();
+    static {
+        REQUEST_HEADER.put("TRON-PRO-API-KEY", TRON_API_KEY);
+    }
+
+    @Scheduled(cron = "0 0/5 * * * ? ")
+    public void recharge() {
+        // 查询过去5分钟的记录
+
+        DataDictionaryCustom dataDictionaryCustom = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
+                SalesServiceEnums.TRC_ADDRESS.getType(),
+                SalesServiceEnums.TRC_ADDRESS.getCode()
+        );
+        String receiveAddress = dataDictionaryCustom.getValue();
+        if (receiveAddress == null) {
+            log.error("请先配置系统地址");
+            return;
+        }
+        String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"+receiveAddress+"/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10";
+        // 定时任务的执行时间和转账时间区间间隔5分钟
+
+        // 每次将上次的时间存入redis,第一次使用默认的时间
+        long endTime = System.currentTimeMillis();
+        long startTime = endTime-TIME_INTERVAL;
+
+        System.out.println(new Date()+" 自动充值定时任务fyTrc20RechargeTokenviewTask "+startTime+" "+endTime);
+        // 当前的充值地址 TRC20USDT_ADDRESS
+        Map<String, String> param = new HashMap<>();
+        param.put("timestampStart", startTime + "");
+        param.put("timestampEnd", endTime + "");
+        param.put("toAddress", receiveAddress);
+        param.put("apikey", TRON_API_KEY);
+        long l = System.currentTimeMillis();
+        byte[] bytes = OkHttpUtil2.doGetSingle(url, REQUEST_HEADER, param, "application/json");
+
+        if (ObjectUtil.isEmpty( bytes )) {
+            log.error("FyTrc20RechargeTask查询链上数据返回为空,传参:{}",param);
+            return;
+        }
+
+        String s = new String(bytes, StandardCharsets.UTF_8);
+        log.info("查询到的充值记录:{}", s);
+        Trc20TokenviewModel trc20TransfersModel = JSONObject.parseObject(s, Trc20TokenviewModel.class);
+        List<Trc20TokenviewContentModel> tokenTransfers = trc20TransfersModel.getData();
+        if (CollUtil.isEmpty(tokenTransfers)) {
+            //logger.error("FyTrc20RechargeTask查询链上数据返回没有token转账,返回结果:{}",s);
+            return;
+        }
+        long l1 = System.currentTimeMillis();
+        log.info("查询trc耗时:{}", (l1 - l));
+        log.info("查询到的充值记录:{}", JSONObject.toJSONString(trc20TransfersModel));
+        log.info("时间区间,start:{},end:{}", startTime, endTime);
+        // 有记录
+        for (Trc20TokenviewContentModel tokenTransfer : tokenTransfers) {
+            log.info("链上时间:{}", tokenTransfer.getTime());
+            // 从数据库
+            String transactionId = tokenTransfer.getTxid();
+            List<MallOrderInfo> chatOrders = mallOrderInfoMapper.selectList(
+                    Wrappers.lambdaQuery(MallOrderInfo.class)
+                    .eq(MallOrderInfo::getTradeHash, transactionId)
+            );
+            if(CollUtil.isNotEmpty(chatOrders)){
+                log.info("扫描到HASH已使用:{}",transactionId);
+                continue;
+            }
+            // 金额
+            String quant = tokenTransfer.getValue();
+            BigDecimal amount = new BigDecimal(quant).divide(new BigDecimal("1000000")).setScale(2, RoundingMode.DOWN);
+            String amountKey = OrderConstants.TRC20_ORDER_KEY + amount;
+            String orderCode = redisUtils.getString(amountKey);
+            if (StrUtil.isBlank(orderCode)) {
+                log.info("Redis未扫描到充值金额:{}",transactionId);
+                continue;
+            }
+            MallOrderInfo chatOrder = mallOrderInfoMapper.selectOne(
+                    Wrappers.lambdaQuery(MallOrderInfo.class)
+                            .eq(MallOrderInfo::getOrderNo, orderCode)
+            );
+            if(chatOrder==null){
+                log.error("未找到订单:{}",orderCode);
+                continue;
+            }
+            if(OrderStatusEnum.WAIT_PAY.getValue() != chatOrder.getStatus()){
+                log.error("订单不是待充值状态: {},订单编号:{}",transactionId,orderCode);
+                continue;
+            }
+
+            mallOrderInfoMapper.update(
+                    null,
+                    Wrappers.lambdaUpdate(MallOrderInfo.class)
+                            .set(MallOrderInfo::getStatus, OrderStatusEnum.WAIT_SHIPPING.getValue())
+                            .set(MallOrderInfo::getTradeHash, transactionId)
+                            .set(MallOrderInfo::getPayTime, new Date())
+                            .set(MallOrderInfo::getPayResult, "1")
+                            .eq(MallOrderInfo::getId, chatOrder.getId())
+            );
+            redisUtils.del(amountKey);
+            log.info("Redis扫描到充值记录:{},订单编号:{}",transactionId,orderCode);
+        }
+    }
+
+
+    public static void main(String[] args) {
+        String receiveAddress = "TExto1UjtFcXKw5QdJDRqtx7wTQ15D37GD";
+        String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"+receiveAddress+"/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10";
+        // 定时任务的执行时间和转账时间区间间隔5分钟
+
+        // 每次将上次的时间存入redis,第一次使用默认的时间
+        long endTime = System.currentTimeMillis();
+        long startTime = endTime-TIME_INTERVAL;
+
+        System.out.println(new Date()+" 自动充值定时任务fyTrc20RechargeTokenviewTask "+startTime+" "+endTime);
+        // 当前的充值地址 TRC20USDT_ADDRESS
+        Map<String, String> param = new HashMap<>();
+        param.put("timestampStart", startTime + "");
+        param.put("timestampEnd", endTime + "");
+        param.put("toAddress", receiveAddress);
+        param.put("apikey", TRON_API_KEY);
+        long l = System.currentTimeMillis();
+        byte[] bytes = OkHttpUtil2.doGetSingle(url, REQUEST_HEADER, param, "application/json");
+
+        if (bytes == null) {
+            log.error("FyTrc20RechargeTask查询链上数据返回为空,传参:{}",param);
+            return;
+        }
+
+        String s = new String(bytes, StandardCharsets.UTF_8);
+        System.out.println(s);
+        System.exit(0);
+    }
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/service/ApiChatPayService.java b/src/main/java/cc/mrbird/febs/mall/service/ApiChatPayService.java
new file mode 100644
index 0000000..5afd5ab
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/service/ApiChatPayService.java
@@ -0,0 +1,10 @@
+package cc.mrbird.febs.mall.service;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.mall.entity.MallOrderInfo;
+
+public interface ApiChatPayService {
+
+
+    FebsResponse usPay(MallOrderInfo chatOrder);
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java b/src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java
index 2b2a227..d475298 100644
--- a/src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java
+++ b/src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java
@@ -49,4 +49,6 @@
     FebsResponse setInvite(ApiSetInviteDto apiSetInviteDto);
 
     FebsResponse salesService();
+
+    FebsResponse moneyChange();
 }
diff --git a/src/main/java/cc/mrbird/febs/mall/service/IApiMallOrderInfoService.java b/src/main/java/cc/mrbird/febs/mall/service/IApiMallOrderInfoService.java
index 1036672..ca463e4 100644
--- a/src/main/java/cc/mrbird/febs/mall/service/IApiMallOrderInfoService.java
+++ b/src/main/java/cc/mrbird/febs/mall/service/IApiMallOrderInfoService.java
@@ -19,6 +19,8 @@
 
     Map<String, Object> payOrder(PayOrderDto payOrderDto);
 
+    FebsResponse payOrderByCoin(ApiOrderPayDto payDto);
+
     List<OrderListVo> findOrderList(OrderListDto orderListDto);
 
     OrderDetailVo findOrderDetailsById(Long id);
diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/ApiChatPayServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/ApiChatPayServiceImpl.java
new file mode 100644
index 0000000..cefa3bb
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/service/impl/ApiChatPayServiceImpl.java
@@ -0,0 +1,123 @@
+package cc.mrbird.febs.mall.service.impl;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
+import cc.mrbird.febs.common.exception.FebsException;
+import cc.mrbird.febs.common.utils.RedisUtils;
+import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants;
+import cc.mrbird.febs.mall.controller.dependentStation.enums.SalesServiceEnums;
+import cc.mrbird.febs.mall.entity.DataDictionaryCustom;
+import cc.mrbird.febs.mall.entity.MallOrderInfo;
+import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper;
+import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
+import cc.mrbird.febs.mall.service.ApiChatPayService;
+import cc.mrbird.febs.mall.vo.ApiOrderPayVo;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ApiChatPayServiceImpl implements ApiChatPayService {
+
+    @Resource
+    private RedisTemplate<String, String> redisTemplate;
+    private final MallOrderInfoMapper mallOrderInfoMapper;
+    private final DataDictionaryCustomMapper dataDictionaryCustomMapper;
+    @Override
+    public FebsResponse usPay(MallOrderInfo chatOrder) {
+
+        //判断是否redis已经缓存订单
+        BigDecimal amount = chatOrder.getAmount();
+        if (StrUtil.isEmpty(chatOrder.getTradeHash())) {
+            amount = generateUniqueNumber(chatOrder.getOrderNo(), amount);
+        }else{
+            amount = new BigDecimal(chatOrder.getTradeHash());
+        }
+        // 调用Mapper更新数据库中的订单信息
+        mallOrderInfoMapper.update(null,
+                Wrappers.lambdaUpdate(MallOrderInfo.class)
+                        .set(MallOrderInfo::getPayMethod, OrderConstants.PAY_TYPE_USDT)
+                        .set(MallOrderInfo::getTradeHash, null)
+                        .eq(MallOrderInfo::getId, chatOrder.getId())
+        );
+
+        DataDictionaryCustom dataDictionaryCustom = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
+                SalesServiceEnums.TRC_ADDRESS.getType(),
+                SalesServiceEnums.TRC_ADDRESS.getCode()
+        );
+        String addressSystem = dataDictionaryCustom.getValue();
+        ApiOrderPayVo apiOrderPayVo = new ApiOrderPayVo();
+        apiOrderPayVo.setOrderCode(chatOrder.getOrderNo());
+        apiOrderPayVo.setAmount(amount.toString());
+        apiOrderPayVo.setAddress(addressSystem);
+        apiOrderPayVo.setCreatedTime(chatOrder.getCreatedTime());
+        return new FebsResponse().success().data(apiOrderPayVo);
+    }
+
+    /**
+     * 生成唯一数字
+     * 通过随机数和给定的金额计算生成一个唯一数字,并确保这个数字在Redis中是唯一的
+     *
+     * @param amount 金额
+     * @param orderCode 订单编码
+     * @return 生成的唯一数字
+     * @throws RuntimeException 如果在指定次数内无法生成唯一数字
+     */
+    private BigDecimal generateUniqueNumber(String orderCode,BigDecimal amount) {
+        int maxAttempts = 50;
+        int attempts = 0;
+        int num;
+        BigDecimal bigDecimal;
+
+        do {
+            if (attempts++ >= maxAttempts) {
+                throw new FebsException("Payment exception");
+            }
+            num = ThreadLocalRandom.current().nextInt(1, 51);
+
+            bigDecimal = calculateAmount(amount, num);
+
+        } while (!setRedisValue(bigDecimal, orderCode)); // 原子操作检查并写入
+
+        return bigDecimal;
+    }
+
+    /**
+     * 计算调整后的金额
+     * 根据给定的金额和一个随机数计算新的金额,用于生成唯一数字
+     *
+     * @param amount 原始金额
+     * @param num 随机生成的数字
+     * @return 调整后的金额
+     */
+    private BigDecimal calculateAmount(BigDecimal amount, int num) {
+        BigDecimal multiplier = new BigDecimal("0.01");
+        BigDecimal increment = new BigDecimal(num).multiply(multiplier);
+        return amount.add(increment).setScale(2, RoundingMode.DOWN);
+    }
+
+    /**
+     * 将生成的数字设置到Redis中
+     * 此方法确保生成的数字是唯一的,通过在Redis中设置值并使用NX参数来实现
+     *
+     * @param amountReal 生成的唯一数字
+     * @param orderCode 订单编码
+     * @return 如果设置成功则返回true,否则返回false
+     */
+    private boolean setRedisValue(BigDecimal amountReal, String orderCode) {
+        String key = OrderConstants.TRC20_ORDER_KEY + amountReal; // 添加前缀避免键冲突
+        return redisTemplate.opsForValue().setIfAbsent(key, orderCode, 60L * Integer.parseInt("30"), TimeUnit.SECONDS);
+    }
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallMemberServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallMemberServiceImpl.java
index 6f04506..4d3e4e5 100644
--- a/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallMemberServiceImpl.java
+++ b/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallMemberServiceImpl.java
@@ -422,6 +422,26 @@
         return new FebsResponse().success().data(mallSalesServiceVo);
     }
 
+    @Override
+    public FebsResponse moneyChange() {
+        List<MallMoneyChangeVo> vos = new ArrayList<>();
+        List<DataDictionaryCustom> dataDictionaryCustoms = dataDictionaryCustomMapper.selectDicByType(
+                SalesServiceEnums.USD.getType()
+        );
+        if (dataDictionaryCustoms.size() > 0){
+            for (DataDictionaryCustom dataDictionaryCustom : dataDictionaryCustoms){
+                MallMoneyChangeVo vo = new MallMoneyChangeVo();
+                    vo.setMoneyChange(dataDictionaryCustom.getValue());
+                    vo.setCode(dataDictionaryCustom.getCode());
+                    vo.setMoneyCode(dataDictionaryCustom.getDescription());
+                    vos.add(vo);
+            }
+
+        }
+        return new FebsResponse().success().data(vos);
+
+    }
+
     public static void main(String[] args) {
         Long userld = 173L;
         String shopAccount = "luohu";
diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallOrderInfoServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallOrderInfoServiceImpl.java
index b181d54..b7d279d 100644
--- a/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallOrderInfoServiceImpl.java
+++ b/src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallOrderInfoServiceImpl.java
@@ -5,6 +5,7 @@
 import cc.mrbird.febs.common.exception.FebsException;
 import cc.mrbird.febs.common.properties.XcxProperties;
 import cc.mrbird.febs.common.utils.*;
+import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants;
 import cc.mrbird.febs.mall.conversion.MallGoodsCommentConversion;
 import cc.mrbird.febs.mall.conversion.MallOrderInfoConversion;
 import cc.mrbird.febs.mall.conversion.MallOrderRefundConversion;
@@ -71,6 +72,7 @@
     private final RedisUtils redisUtils;
 
     private final AgentProducer agentProducer;
+    private final ApiChatPayService apiChatPayService;
     private final IPayService payService;
     private final IXcxPayService iXcxPayService;
     private final IMallAchieveService mallAchieveService;
@@ -175,8 +177,8 @@
         orderInfo.setLatitude(address.getLatitude());
         orderInfo.setLongitude(address.getLongitude());
         this.baseMapper.updateById(orderInfo);
-        //过期时间修改成24小时
-        agentProducer.sendOrderCancelDelayMsg(orderInfo.getId(),  24 * 60 * 60 * 1000L);
+        //过期时间修改成30分钟
+        agentProducer.sendOrderCancelDelayMsg(orderInfo.getId(),  30 * 60 * 1000L);
         return orderInfo.getId();
     }
 
@@ -477,6 +479,36 @@
         return map;
     }
 
+    /**
+     * 处理支付订单的请求
+     *
+     * @param payDto 包含支付订单所需信息的DTO
+     * @return 返回支付结果的AjaxResult对象
+     */
+    @Override
+    @Transactional
+    public FebsResponse payOrderByCoin(ApiOrderPayDto payDto) {
+        // 获取当前的用户
+        Long memberId = LoginUserUtil.getLoginUser().getId();
+        // 提取订单ID和支付类型
+        Long orderId = payDto.getOrderId();
+        Integer payType = payDto.getPayType();
+
+        // 验证订单是否存在
+
+        MallOrderInfo orderInfo =
+                ValidateEntityUtils.ensureColumnReturnEntity(orderId, MallOrderInfo::getId, this.baseMapper::selectOne, "Order does not exist");
+        ValidateEntityUtils.ensureEqual(memberId,orderInfo.getMemberId(),"Order does not exist");
+        ValidateEntityUtils.ensureEqual(OrderStatusEnum.WAIT_PAY.getValue(),orderInfo.getStatus(),"The order status is not pending payment");
+
+        // 根据支付类型调用相应的支付方法
+        if(OrderConstants.PAY_TYPE_USDT == payType){
+            return apiChatPayService.usPay(orderInfo);
+        }
+        // 如果支付类型不匹配或支付过程中出现异常,返回错误信息
+        return new FebsResponse().fail().message("Order exception, please contact us");
+    }
+
     private String balancePay(MallOrderInfo orderInfo, String tradePwd, String field) {
         if (StrUtil.isBlank(tradePwd)) {
             throw new FebsException("支付密码错误");
diff --git a/src/main/java/cc/mrbird/febs/mall/vo/ApiOrderPayVo.java b/src/main/java/cc/mrbird/febs/mall/vo/ApiOrderPayVo.java
new file mode 100644
index 0000000..91d096a
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/vo/ApiOrderPayVo.java
@@ -0,0 +1,25 @@
+package cc.mrbird.febs.mall.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@ApiModel(value = "ApiOrderPayVo", description = "信息返回类")
+public class ApiOrderPayVo {
+
+
+    @ApiModelProperty(value = "订单编号")
+    public String orderCode;
+    @ApiModelProperty(value = "支付金额")
+    public String amount;
+    @ApiModelProperty(value = "收款地址")
+    public String address;
+    @ApiModelProperty(value = "时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/vo/MallMoneyChangeVo.java b/src/main/java/cc/mrbird/febs/mall/vo/MallMoneyChangeVo.java
new file mode 100644
index 0000000..a7bdbfa
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/vo/MallMoneyChangeVo.java
@@ -0,0 +1,19 @@
+package cc.mrbird.febs.mall.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "MallMoneyChangeVo", description = "商城用户信息返回类")
+public class MallMoneyChangeVo {
+
+    @ApiModelProperty(value = "倍数")
+    private String moneyChange;
+
+    @ApiModelProperty(value = "编码")
+    private String code;
+
+    @ApiModelProperty(value = "小图标")
+    private String moneyCode;
+}

--
Gitblit v1.9.1