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
| | |
| | | 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; |
| | |
| | | return memberService.salesService(); |
| | | } |
| | | |
| | | |
| | | |
| | | @ApiOperation(value = "价格转换", notes = "价格转换") |
| | | @ApiResponses({ |
| | | @ApiResponse(code = 200, message = "success", response = MallMoneyChangeVo.class) |
| | | }) |
| | | @GetMapping(value = "/moneyChange") |
| | | public FebsResponse moneyChange() { |
| | | return memberService.moneyChange(); |
| | | } |
| | | |
| | | } |
| File was renamed from src/main/java/cc/mrbird/febs/mall/controller/ApiMallNewsController.java |
| | |
| | | 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; |
| | |
| | | @RestController |
| | | @RequestMapping(value = "/api/news") |
| | | @RequiredArgsConstructor |
| | | @Api(value = "ApiMallNewsController", tags = "新闻接口类") |
| | | @Api(value = "ApiMallNewsController", tags = "ds-新闻接口") |
| | | public class ApiMallNewsController { |
| | | |
| | | private final IApiMallNewsService newsService; |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | @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 = "取消订单") |
| | |
| | | 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) |
| | |
| | | |
| | | @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); |
| New file |
| | |
| | | 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:"; |
| | | } |
| | |
| | | @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"), |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | |
| | | |
| | | private Date payTime; |
| | | |
| | | private String tradeHash; |
| | | private BigDecimal amount; |
| | | |
| | | private String payMethod; |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | |
| | | FebsResponse setInvite(ApiSetInviteDto apiSetInviteDto); |
| | | |
| | | FebsResponse salesService(); |
| | | |
| | | FebsResponse moneyChange(); |
| | | } |
| | |
| | | |
| | | Map<String, Object> payOrder(PayOrderDto payOrderDto); |
| | | |
| | | FebsResponse payOrderByCoin(ApiOrderPayDto payDto); |
| | | |
| | | List<OrderListVo> findOrderList(OrderListDto orderListDto); |
| | | |
| | | OrderDetailVo findOrderDetailsById(Long id); |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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"; |
| | |
| | | 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; |
| | |
| | | private final RedisUtils redisUtils; |
| | | |
| | | private final AgentProducer agentProducer; |
| | | private final ApiChatPayService apiChatPayService; |
| | | private final IPayService payService; |
| | | private final IXcxPayService iXcxPayService; |
| | | private final IMallAchieveService mallAchieveService; |
| | |
| | | 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(); |
| | | } |
| | | |
| | |
| | | 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("支付密码错误"); |
| New file |
| | |
| | | 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; |
| | | |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |