Administrator
2026-06-13 befbb28b2810ed108d2744bceee2bb9b3edaa9bc
feat(mall): 添加USDT支付功能和相关服务

- 新增ApiChatPayService接口和ApiChatPayServiceImpl实现类
- 添加USDT支付相关实体类ApiOrderPayDto和ApiOrderPayVo
- 在ApiMallOrderController中添加USDT支付订单接口payOrderByCoin
- 实现payOrderByCoin方法支持USDT支付订单功能
- 添加ChatTrc20ChargeOkLinkTask定时任务处理TRC20代币转账
- 新增OkHttpUtil2工具类用于HTTP请求
- 添加TRC20相关的常量定义和枚举
- 在MallOrderInfo实体中添加tradeHash字段
- 修改订单过期时间为30分钟
- 添加价格转换功能和相关VO类
- 移动ApiMallNewsController到dependentStation包下
- 移除购物车删除商品接口的限流注解
10 files added
1 files renamed
9 files modified
778 ■■■■■ changed files
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiLoginController.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallNewsController.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallShoppingCartController.java 1 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/constant/OrderConstants.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/enums/SalesServiceEnums.java 8 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/OkHttpUtil2.java 203 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewContentModel.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewModel.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/ApiOrderPayDto.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java 1 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java 185 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/ApiChatPayService.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/IApiMallOrderInfoService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/ApiChatPayServiceImpl.java 123 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallMemberServiceImpl.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallOrderInfoServiceImpl.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/vo/ApiOrderPayVo.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/vo/MallMoneyChangeVo.java 19 ●●●●● patch | view | raw | blame | history
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();
    }
}
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallNewsController.java
File was renamed from src/main/java/cc/mrbird/febs/mall/controller/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;
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)
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);
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/constant/OrderConstants.java
New file
@@ -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:";
}
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"),
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/OkHttpUtil2.java
New file
@@ -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;
    }
}
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewContentModel.java
New file
@@ -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;
}
src/main/java/cc/mrbird/febs/mall/controller/dependentStation/utils/Trc20TokenviewModel.java
New file
@@ -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;
}
src/main/java/cc/mrbird/febs/mall/dto/ApiOrderPayDto.java
New file
@@ -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;
}
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;
src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
New file
@@ -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);
    }
}
src/main/java/cc/mrbird/febs/mall/service/ApiChatPayService.java
New file
@@ -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);
}
src/main/java/cc/mrbird/febs/mall/service/IApiMallMemberService.java
@@ -49,4 +49,6 @@
    FebsResponse setInvite(ApiSetInviteDto apiSetInviteDto);
    FebsResponse salesService();
    FebsResponse moneyChange();
}
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);
src/main/java/cc/mrbird/febs/mall/service/impl/ApiChatPayServiceImpl.java
New file
@@ -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);
    }
}
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";
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("支付密码错误");
src/main/java/cc/mrbird/febs/mall/vo/ApiOrderPayVo.java
New file
@@ -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;
}
src/main/java/cc/mrbird/febs/mall/vo/MallMoneyChangeVo.java
New file
@@ -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;
}