From 75ede9c934af63e6b75d3bdd34323b5d803b935f Mon Sep 17 00:00:00 2001
From: shi
Date: Mon, 21 Apr 2025 14:03:07 +0800
Subject: [PATCH] =?UTF-8?q?feat(game):=20=E6=B7=BB=E5=8A=A0=20PP=20?=
=?UTF-8?q?=E6=B8=B8=E6=88=8F=E5=B9=B3=E5=8F=B0=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 PP游戏平台的 API 接口实现类
- 添加 PP 游戏平台的缓存常量和时间点
- 实现 PP游戏平台的会员创建、登录、转账等功能
- 解析 PP 游戏平台的注单记录
- 添加 PP 游戏平台的地址源和成功条件判断
---
.../com/ff/base/constant/CacheConstants.java | 10 +-
.../java/com/ff/base/enums/GamePlatforms.java | 1 +
.../java/com/ff/base/enums/PPGameType.java | 64 ++
.../java/com/ff/base/utils/DateUtils.java | 19 +
.../api/pp/address/MyPPAddressSource.java | 35 +
.../com/ff/game/api/pp/client/PPClient.java | 121 ++++
.../api/pp/dto/DGBetRecordResponseDTO.java | 129 ++++
.../dto/DGLoginWithoutRedirectResponse.java | 74 ++
.../com/ff/game/api/pp/dto/DGResponse.java | 26 +
.../api/pp/dto/DGTransactionResponseDTO.java | 45 ++
.../api/pp/dto/DGUserAccountResponse.java | 40 ++
.../api/pp/dto/DGUserListResponseDTO.java | 70 ++
.../game/api/pp/dto/PPGameLaunchResponse.java | 35 +
.../ff/game/api/pp/dto/PPGameResponseDTO.java | 111 +++
.../ff/game/api/pp/dto/PPGameRoundRecord.java | 279 ++++++++
.../api/pp/dto/PPPlayerAccountResponse.java | 35 +
.../com/ff/game/api/pp/dto/PPResponse.java | 30 +
.../api/pp/dto/PPTransactionResponse.java | 43 ++
.../pp/dto/PPTransactionStatusResponse.java | 90 +++
.../api/pp/dto/PPUserAccountResponse.java | 33 +
.../api/pp/forest/MySuccessCondition.java | 31 +
.../game/api/pp/impl/GamesPPServiceImpl.java | 645 ++++++++++++++++++
.../com/ff/game/domain/GameExchangeMoney.java | 5 +
23 files changed, 1969 insertions(+), 2 deletions(-)
create mode 100644 ff-base/src/main/java/com/ff/base/enums/PPGameType.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java
create mode 100644 ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java
diff --git a/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java b/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java
index 01d8620..ca45741 100644
--- a/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java
+++ b/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java
@@ -137,8 +137,14 @@ public class CacheConstants {
public static final String SV388_TIME_FROM= "sv388:time:from";
public static final String SV388_GAMES = "sv388_games:";
-
-
+ /**
+ * pp游戏
+ */
+ public static final String PP_GAMES= "pp_games:";
+ /**
+ * pp时间点
+ */
+ public static final String PP_TIME_POINT = "pp:time:point:";
}
diff --git a/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java b/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java
index a6152c1..a9cf778 100644
--- a/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java
+++ b/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java
@@ -19,6 +19,7 @@ public enum GamePlatforms {
PGT("PGT", "PGT"),
FBSports("FBSports", "FB体育"),
SV388("SV388", "SV388真人"),
+ PP("PP", "PP"),
DBSports("DBSports", "DB体育");
private final String code;
diff --git a/ff-base/src/main/java/com/ff/base/enums/PPGameType.java b/ff-base/src/main/java/com/ff/base/enums/PPGameType.java
new file mode 100644
index 0000000..adfbefc
--- /dev/null
+++ b/ff-base/src/main/java/com/ff/base/enums/PPGameType.java
@@ -0,0 +1,64 @@
+package com.ff.base.enums;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+
+/**
+ * xkgame类型
+ *
+ * @author shi
+ * @date 2024/11/13
+ */
+@Getter
+@AllArgsConstructor
+public enum PPGameType {
+
+ ELECTRONIC("vs", 1,"电子"),
+// CARD_GAME("rl",2, "轮盘"),
+// VIDEO("vp",6, "视频扑克"),
+// SCRATCH_CARD("sc",7, "刮刮乐"),
+// BLACK_JACK("bj",2, "21点"),
+// CLASSIC_SLOTS("cs",1, "电子"),
+// BACCARAT_NEW("bn",2, "百家乐新"),
+// BACCARAT("bc",2, "百家乐"),
+// LIVE_GAMES("lg",6, "现场游戏"),
+ ;
+
+ private final String code;
+ private final Integer systemCode;
+ private final String info;
+
+
+ /**
+ * 按代码查找系统
+ *
+ * @param code 代码
+ * @return {@link String }
+ */
+ public static Integer findSystemByCode(String code) {
+ Optional system = Stream.of(PPGameType.values())
+ .filter(gameType -> gameType.getCode().equals(code))
+ .map(PPGameType::getSystemCode)
+ .findFirst();
+ return system.orElse(null);
+ }
+
+ /**
+ * 按代码查找信息
+ *
+ * @param code 代码
+ * @return {@link String }
+ */
+ public static String findInfoByCode(String code) {
+ Optional system = Stream.of(PPGameType.values())
+ .filter(gameType -> gameType.getCode().equals(code))
+ .map(PPGameType::getInfo)
+ .findFirst();
+ return system.orElse(null);
+ }
+}
diff --git a/ff-base/src/main/java/com/ff/base/utils/DateUtils.java b/ff-base/src/main/java/com/ff/base/utils/DateUtils.java
index a473e18..7448678 100644
--- a/ff-base/src/main/java/com/ff/base/utils/DateUtils.java
+++ b/ff-base/src/main/java/com/ff/base/utils/DateUtils.java
@@ -975,4 +975,23 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
}
+
+ /**
+ * 将自定义格式的时间字符串转换为毫秒级时间戳(UTC)
+ *
+ * @param dateStr 时间字符串
+ * @param pattern 时间格式模式(如"yyyy-MM-dd HH:mm:ss")
+ * @return 毫秒级时间戳
+ * @throws ParseException 如果解析失败
+ */
+ public static long toTimestamp(String dateStr, String pattern) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return sdf.parse(dateStr).getTime();
+ } catch (Exception e) {
+ return 0;
+ }
+
+ }
}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java b/ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java
new file mode 100644
index 0000000..51ee44b
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java
@@ -0,0 +1,35 @@
+package com.ff.game.api.pp.address;
+
+import com.dtflys.forest.callback.AddressSource;
+import com.dtflys.forest.http.ForestAddress;
+import com.dtflys.forest.http.ForestRequest;
+import com.ff.base.enums.GamePlatforms;
+import com.ff.game.domain.Platform;
+import com.ff.game.service.IPlatformService;
+import com.sun.media.jfxmediaimpl.platform.PlatformManager;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+
+/**
+ * 我jili address来源
+ *
+ * @author shi
+ * @date 2025/02/10
+ */
+@Component
+public class MyPPAddressSource implements AddressSource {
+ @Resource
+ private IPlatformService platformService;
+
+
+ @Override
+ public ForestAddress getAddress(ForestRequest request) {
+ Platform platform = platformService.get(GamePlatforms.PP.getCode());
+ String apiBaseUrl = platform.getUrlInfo().getUrl();
+ String host = platform.getUrlInfo().getHost();
+ String https = platform.getUrlInfo().getHttps();
+ return new ForestAddress(https, apiBaseUrl, Integer.parseInt(host), "/IntegrationService/v3");
+ }
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java b/ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java
new file mode 100644
index 0000000..8473e17
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java
@@ -0,0 +1,121 @@
+package com.ff.game.api.pp.client;
+
+import com.dtflys.forest.annotation.*;
+import com.ff.game.api.pp.address.MyPPAddressSource;
+import com.ff.game.api.pp.dto.*;
+import com.ff.game.api.pp.forest.MySuccessCondition;
+
+import java.util.Map;
+
+/**
+ * pp 请求
+ *
+ * @author shi
+ * @date 2025/02/10
+ */
+@Address(source = MyPPAddressSource.class)
+public interface PPClient {
+ /**
+ * 创建成员
+ *
+ * @param params 参数
+ * @return {@link PPUserAccountResponse }
+ */
+ @Post( url ="/http/CasinoGameAPI/player/account/create/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+ PPUserAccountResponse createMember(@Body Map params);
+
+
+ /**
+ * 获取会员信息
+ *
+ * @param params 参数
+ * @return {@link PPPlayerAccountResponse }
+ */
+ @Post( url ="/http/CasinoGameAPI/balance/current/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+ PPPlayerAccountResponse getMemberInfo(@Body Map params);
+
+
+
+ /**
+ * 无重定向登录
+ *
+ * @param params 参数
+ * @return {@link PPGameLaunchResponse }
+ */
+ @Post(url ="/http/CasinoGameAPI/game/start/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+ PPGameLaunchResponse loginWithoutRedirect(@Body Map params);
+
+ /**
+ * 获取游戏列表
+ *
+ * @param params 参数
+ * @return {@link PPGameResponseDTO }
+ */
+ @Post( url ="/http/CasinoGameAPI/getCasinoGames/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+
+ PPGameResponseDTO getGameList(@Body Map params);
+
+ /**
+ * 按代理id进行交换转账
+ *
+ * @param params 参数
+ * @return {@link PPTransactionResponse }
+ */
+ @Post(url = "/http/CasinoGameAPI/balance/transfer/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+ @Success(condition = MySuccessCondition.class)
+ PPTransactionResponse exchangeTransfer(@Body Map params);
+
+ /**
+ * 汇兑转移状态
+ *
+ * @param params 参数
+ * @return {@link PPTransactionStatusResponse }
+ */
+ @Post(url = "/http/CasinoGameAPI/balance/transfer/status/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+ PPTransactionStatusResponse exchangeTransferStatus( @Body Map params);
+
+ /**
+ * 按时间获取投注记录
+ *
+ * @param params 参数
+ * @return {@link String }
+ */
+ @Get(url ="/DataFeeds/gamerounds/finished/?{params}")
+ String getBetRecordByTime(@Var("params") String params);
+
+
+ /**
+ * 踢出队员
+ *
+ * @param params 参数
+ * @return {@link PPResponse }
+ */
+ @Post(url = "/http/CasinoGameAPI/game/session/terminate/",
+ headers = {
+ "Content-type: application/x-www-form-urlencoded"
+ })
+ PPResponse kickMember(@Body Map params);
+
+
+
+
+
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java
new file mode 100644
index 0000000..2fa59c2
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java
@@ -0,0 +1,129 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * dgbet记录响应数据
+ *
+ * @author shi
+ * @date 2025/03/27
+ */
+@Data
+public class DGBetRecordResponseDTO {
+
+ /** 错误码 (参考文档定义) */
+ @JsonProperty("codeId")
+ private Integer codeId;
+
+ /** 错误信息 */
+ @JsonProperty("msg")
+ private String msg;
+
+ /** 注单报告 */
+ @JsonProperty("list")
+ private List list;
+
+ /**
+ * 注单报告类
+ */
+ @Data
+ public static class ReportDTO {
+
+ /** 注单ID(唯一) */
+ @JsonProperty("id")
+ private Long id;
+
+ /** 游戏桌号(红包小费记录没有) */
+ @JsonProperty("tableId")
+ private Integer tableId;
+
+ /** 游戏靴号(红包小费记录没有) */
+ @JsonProperty("shoeId")
+ private Long shoeId;
+
+ /** 当靴局号(红包小费记录没有) */
+ @JsonProperty("playId")
+ private Long playId;
+
+ /** 游戏厅号(1:旗舰厅;2:亚洲厅;3,4:现场厅;5:性感厅;8,9:区块链厅) */
+ @JsonProperty("lobbyId")
+ private Integer lobbyId;
+
+ /** 注单类型(1:注单,2:红包小费) */
+ @JsonProperty("gameType")
+ private Integer gameType;
+
+ /** 游戏类型(百家乐,龙虎等) */
+ @JsonProperty("gameId")
+ private Integer gameId;
+
+ /** 下注时间 */
+ @JsonProperty("betTime")
+ private Date betTime;
+
+ /** 结算时间 */
+ @JsonProperty("calTime")
+ private Date calTime;
+
+ /** 派彩金额(含本金) */
+ @JsonProperty("winOrLoss")
+ private BigDecimal winOrLoss;
+
+ /** 下注前余额(仅作参考) */
+ @JsonProperty("balanceBefore")
+ private BigDecimal balanceBefore;
+
+ /** 下注金额(下注扣款金额) */
+ @JsonProperty("betPoints")
+ private BigDecimal betPoints;
+
+ /** 洗码金额(用于计算佣金) */
+ @JsonProperty("availableBet")
+ private BigDecimal availableBet;
+
+ /** 会员账号 */
+ @JsonProperty("userName")
+ private String userName;
+
+ /** 游戏结果 */
+ @JsonProperty("result")
+ private String result;
+
+ /** 注单详情 */
+ @JsonProperty("betDetail")
+ private String betDetail;
+
+ /** 客户端IP */
+ @JsonProperty("ip")
+ private String ip;
+
+ /** 游戏唯一局号 */
+ @JsonProperty("ext")
+ private String ext;
+
+ /** 结算状态(0:未结算,1:已结算,2:已撤销,3:冻结) */
+ @JsonProperty("isRevocation")
+ private Integer isRevocation;
+
+ /** 更改单时对应的注单记录 */
+ @JsonProperty("parentBetId")
+ private Long parentBetId;
+
+ /** 币种ID(请参考对应关系说明) */
+ @JsonProperty("currencyId")
+ private Integer currencyId;
+
+ /** 客户端平台ID(1: PC 2: 安卓 3: 苹果 5: H5) */
+ @JsonProperty("deviceType")
+ private Integer deviceType;
+
+ /** 钱包扣款记录(转账模式API没有) */
+ @JsonProperty("transfers")
+ private String transfers;
+ }
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java
new file mode 100644
index 0000000..576a000
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java
@@ -0,0 +1,74 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 响应结果类
+ */
+@Data
+public class DGLoginWithoutRedirectResponse {
+
+ /**
+ * 响应的代码ID
+ */
+ @JsonProperty("codeId")
+ private int codeId;
+
+ /**
+ * 响应的消息
+ */
+ @JsonProperty("msg")
+ private String msg;
+
+ /**
+ * 响应的 token
+ */
+ @JsonProperty("token")
+ private String token;
+
+ /**
+ * 响应的域名信息
+ */
+ @JsonProperty("domains")
+ private String domains;
+
+ /**
+ * 列表数据
+ */
+ @JsonProperty("list")
+ private List list;
+
+ /**
+ * 限制组
+ */
+ @JsonProperty("limitGroup")
+ private String limitGroup;
+
+ /**
+ * 限制区间
+ */
+ @JsonProperty("limits")
+ private List limits;
+
+ /**
+ * 限制区间内部类
+ */
+ @Data
+ public static class Limit {
+
+ /**
+ * 最小值
+ */
+ @JsonProperty("min")
+ private int min;
+
+ /**
+ * 最大值
+ */
+ @JsonProperty("max")
+ private int max;
+ }
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java
new file mode 100644
index 0000000..d7652d3
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java
@@ -0,0 +1,26 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 响应类,用于返回基本的状态信息。
+ *
+ * @author shi
+ * @date 2025/03/26
+ */
+@Data
+public class DGResponse {
+
+ /**
+ * 响应的代码ID。
+ */
+ @JsonProperty("codeId")
+ private int codeId;
+
+ /**
+ * 响应的消息。
+ */
+ @JsonProperty("msg")
+ private String msg;
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java
new file mode 100644
index 0000000..195e39e
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java
@@ -0,0 +1,45 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 交易信息返回实体
+ *
+ * @author shi
+ * @date 2025/03/27
+ */
+@Data
+public class DGTransactionResponseDTO {
+
+
+ /** 响应代码 */
+ @JsonProperty("codeId")
+ private Integer codeId;
+
+ /** 响应消息 */
+ @JsonProperty("msg")
+ private String msg;
+
+ /** 用户名 */
+ @JsonProperty("username")
+ private String username;
+
+ /** 交易金额 */
+ @JsonProperty("amount")
+ private BigDecimal amount;
+
+ /** 账户余额 */
+ @JsonProperty("balance")
+ private BigDecimal balance;
+
+ /** 交易流水号 */
+ @JsonProperty("serial")
+ private String serial;
+
+ /** 交易时间 */
+ @JsonProperty("time")
+ private String time;
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java
new file mode 100644
index 0000000..e5f6506
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java
@@ -0,0 +1,40 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 用户账户信息响应类
+ *
+ * @author shi
+ * @date 2025/03/26
+ */
+@Data
+public class DGUserAccountResponse {
+
+ /**
+ * 响应的代码ID。
+ */
+ @JsonProperty("codeId")
+ private int codeId;
+
+ /**
+ * 响应的消息。
+ */
+ @JsonProperty("msg")
+ private String msg;
+
+ /**
+ * 用户名。
+ */
+ @JsonProperty("username")
+ private String username;
+
+ /**
+ * 用户余额。
+ */
+ @JsonProperty("balance")
+ private BigDecimal balance;
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java
new file mode 100644
index 0000000..13fe63d
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java
@@ -0,0 +1,70 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 用户列表响应类
+ *
+ * @author shi
+ * @date 2025/03/27
+ */
+@Data
+public class DGUserListResponseDTO {
+
+ /**
+ * 响应的代码ID。
+ */
+ @JsonProperty("codeId")
+ private int codeId;
+
+ /**
+ * 响应的消息。
+ */
+ @JsonProperty("msg")
+ private String msg;
+ /** 用户列表 */
+ @JsonProperty("list")
+ private List list;
+
+ /**
+ * 用户信息类
+ */
+ @Data
+ public static class UserDTO {
+
+ /** 用户名 */
+ @JsonProperty("username")
+ private String username;
+
+ /** 昵称 */
+ @JsonProperty("nickname")
+ private String nickname;
+
+ /** 币种名称 */
+ @JsonProperty("currencyName")
+ private String currencyName;
+
+ /** 用户IP */
+ @JsonProperty("ip")
+ private String ip;
+
+ /** 用户设备 */
+ @JsonProperty("device")
+ private String device;
+
+ /** 登录时间 */
+ @JsonProperty("login")
+ private String login;
+
+ /** 会员ID */
+ @JsonProperty("memberId")
+ private Long memberId;
+
+ /** 用户余额 */
+ @JsonProperty("balance")
+ private Double balance;
+ }
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java
new file mode 100644
index 0000000..60af0f4
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java
@@ -0,0 +1,35 @@
+package com.ff.game.api.pp.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * ppgame发布响应
+ *
+ * @author shi
+ * @date 2025/04/14
+ */
+@Data
+public class PPGameLaunchResponse {
+ /**
+ * 响应状态码
+ * 示例: "0" 表示成功
+ */
+ @JsonProperty("error")
+ private String error;
+
+ /**
+ * 响应状态描述
+ * 示例: "OK" 表示成功
+ */
+ @JsonProperty("description")
+ private String description;
+
+ /**
+ * 游戏启动URL
+ * 包含token、游戏符号、技术平台、语言、货币等参数
+ */
+ @JsonProperty("gameURL")
+ private String gameUrl;
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java
new file mode 100644
index 0000000..bd767d6
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java
@@ -0,0 +1,111 @@
+package com.ff.game.api.pp.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 响应类 - 游戏信息列表
+ *
+ * @author shi
+ * @date 2025/03/27
+ */
+@Data
+public class PPGameResponseDTO {
+
+ /** 错误码 (参考文档定义) */
+ @JsonProperty("error")
+ private String error;
+
+ /** 错误信息 */
+ @JsonProperty("description")
+ private String description;
+
+ /** 游戏信息列表 */
+ @JsonProperty("gameList")
+ private List gameList;
+
+ /**
+ * 游戏信息类
+ */
+ @Data
+ public static class CasinoGame {
+
+ /** 游戏唯一标识符 */
+ @JsonProperty("gameID")
+ private String gameID;
+
+ /** 游戏名称 */
+ @JsonProperty("gameName")
+ private String gameName;
+
+ /** 游戏类型标识符 */
+ @JsonProperty("gameTypeID")
+ private String gameTypeID;
+
+ /** 游戏类型描述 */
+ @JsonProperty("typeDescription")
+ private String typeDescription;
+
+ /** 游戏技术描述 */
+ @JsonProperty("technology")
+ private String technology;
+
+ /** 游戏支持的平台 */
+ @JsonProperty("platform")
+ private String platform;
+
+ /** 是否提供演示游戏 */
+ @JsonProperty("demoGameAvailable")
+ private boolean demoGameAvailable;
+
+ /** 游戏宽高比 */
+ @JsonProperty("aspectRatio")
+ private String aspectRatio;
+
+ /** 游戏技术标识符 */
+ @JsonProperty("technologyID")
+ private String technologyID;
+
+ /** 数值型游戏 ID */
+ @JsonProperty("gameIdNumeric")
+ private Long gameIdNumeric;
+
+ /** 是否提供免费回合奖励 */
+ @JsonProperty("frbAvailable")
+ private boolean frbAvailable;
+
+ /** 是否提供可变免费回合奖励 */
+ @JsonProperty("variableFrbAvailable")
+ private boolean variableFrbAvailable;
+
+ /** 游戏的投注线数 */
+ @JsonProperty("lines")
+ private Integer lines;
+
+ /** 游戏支持的功能列表 */
+ @JsonProperty("features")
+ private List features;
+
+ /** 游戏数据类型 */
+ @JsonProperty("dataType")
+ private String dataType;
+
+ /** 支持的司法管辖区列表 */
+ @JsonProperty("jurisdictions")
+ private List jurisdictions;
+
+
+ /**
+ * 系统游戏id
+ */
+ private String systemGameId;
+
+ /**
+ * 系统游戏类型
+ */
+ private Integer systemPlatformType;
+ }
+}
\ No newline at end of file
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java
new file mode 100644
index 0000000..2f7b7ad
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java
@@ -0,0 +1,279 @@
+package com.ff.game.api.pp.dto;
+
+
+import com.ff.base.utils.DateUtils;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * ppgame回合记录
+ *
+ * @author shi
+ * @date 2025/04/15
+ */
+@Data
+public class PPGameRoundRecord {
+ private static final DateTimeFormatter DATE_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ /**
+ * 时间戳(Unix毫秒时间戳)
+ * 记录生成时的系统时间戳,格式为 timepoint=1618298161139
+ */
+ private Long timepoint;
+
+ /**
+ * 玩家在Pragmatic Play系统中的唯一ID
+ *
+ * @example 531288
+ */
+ private String playerID;
+
+ /**
+ * 娱乐场运营商系统中的玩家ID
+ *
+ * @example 889325
+ */
+ private String extPlayerID;
+
+ /**
+ * 游戏唯一标识符
+ *
+ * @example "vs20magicpot"
+ */
+ private String gameID;
+
+ /**
+ * 当前游戏会话ID
+ * 每个游戏回合的唯一编号
+ *
+ * @example 50994065
+ */
+ private String playSessionID;
+
+ /**
+ * 父游戏会话ID
+ * 对于免费游戏,此值为触发免费游戏的母游戏ID
+ *
+ * @example 50994064
+ */
+ private String parentSessionID;
+
+ /**
+ * 游戏回合开始时间
+ * 格式:yyyy-MM-dd HH:mm:ss
+ *
+ * @example "2021-04-13 07:06:18"
+ */
+ private Long startDate;
+
+ /**
+ * 游戏回合结束时间
+ * 未完成时为null,格式:yyyy-MM-dd HH:mm:ss
+ *
+ * @example "2021-04-13 07:06:18"
+ */
+ private Long endDate;
+
+ /**
+ * 游戏状态
+ * 可能值:
+ *
+ * - C - 已完成
+ * - F - 取消/最终确定
+ *
+ *
+ *
+ * @example "C"
+ */
+ private String status;
+
+ /**
+ * 游戏类型
+ * 可能值:
+ *
+ *
+ *
+ * @example "F"
+ */
+ private String type;
+
+ /**
+ * 投注金额(固定2位小数)
+ *
+ * @example 0.00
+ */
+ private BigDecimal bet;
+
+ /**
+ * 赢取金额(固定2位小数)
+ *
+ * @example 0.00
+ */
+ private BigDecimal win;
+
+ /**
+ * 货币代码(ISO 3字母)
+ *
+ * @example "EUR"
+ */
+ private String currency;
+
+ /**
+ * 累积奖金金额(固定2位小数)
+ *
+ * @example 0.00
+ */
+ private BigDecimal jackpot;
+
+ /**
+ * 免费回合奖励ID
+ * 当值为"null"字符串时表示无奖励
+ *
+ * @example "null"
+ */
+ private String bonusCode;
+
+ /**
+ * 奖金投注金额(固定2位小数)
+ *
+ * @example 0.00
+ */
+ private BigDecimal bonusBet;
+
+ /**
+ * 奖金赢取金额(固定2位小数)
+ *
+ * @example 0.00
+ */
+ private BigDecimal bonusWin;
+
+ public static List parse(String data) {
+ List transactions = new ArrayList<>();
+
+ // 按行分割数据
+ String[] lines = data.split("\n");
+
+ if (lines.length < 3) {
+ return new ArrayList<>();
+ }
+
+
+ // 解析列名(第二行)
+ String[] headers = lines[1].trim().split(",");
+
+ for (int j = 2; j < lines.length; j++) {
+ // 解析数据行(第三行开始)
+ String[] values = lines[j].trim().split(",");
+
+ // 确保列数和值数匹配
+ if (headers.length != values.length) {
+ continue;
+ }
+
+ // 创建交易记录对象
+ PPGameRoundRecord transaction = new PPGameRoundRecord();
+
+ // 根据列名设置对应的值
+ for (int i = 0; i < headers.length; i++) {
+ String header = headers[i].trim();
+ String value = values[i].trim();
+
+ switch (header) {
+ case "playerID":
+ transaction.setPlayerID(value);
+ break;
+ case "extPlayerID":
+ transaction.setExtPlayerID(value);
+ break;
+ case "gameID":
+ transaction.setGameID(value);
+ break;
+ case "playSessionID":
+ transaction.setPlaySessionID(value);
+ break;
+ case "parentSessionID":
+ transaction.setParentSessionID(value);
+ break;
+ case "startDate":
+ transaction.setStartDate(DateUtils.toTimestamp(value, DateUtils.YYYY_MM_DD_HH_MM_SS));
+ break;
+ case "endDate":
+ transaction.setEndDate(DateUtils.toTimestamp(value, DateUtils.YYYY_MM_DD_HH_MM_SS));
+ break;
+ case "status":
+ transaction.setStatus(value);
+ break;
+ case "type":
+ transaction.setType(value);
+ break;
+ case "bet":
+ transaction.setBet(new BigDecimal(value));
+ break;
+ case "win":
+ transaction.setWin(new BigDecimal(value));
+ break;
+ case "currency":
+ transaction.setCurrency(value);
+ break;
+ case "jackpot":
+ transaction.setJackpot(new BigDecimal(value));
+ break;
+ case "bonusCode":
+ transaction.setBonusCode("null".equals(value) ? null : value);
+ break;
+ case "bonusBet":
+ transaction.setBonusBet(new BigDecimal(value));
+ break;
+ case "bonusWin":
+ transaction.setBonusWin(new BigDecimal(value));
+ break;
+
+ }
+ }
+
+ transactions.add(transaction);
+ }
+
+ return transactions;
+ }
+
+
+ /**
+ * 解析长
+ *
+ * @param s s
+ * @return {@link Long }
+ */// 辅助解析方法
+ private static Long parseLong(String s) {
+ return s.isEmpty() ? null : Long.parseLong(s);
+ }
+
+ /**
+ * 解析双精度
+ *
+ * @param s s
+ * @return {@link BigDecimal }
+ */
+ private static BigDecimal parseDouble(String s) {
+ return s.isEmpty() ? null : new BigDecimal(s);
+ }
+
+ /**
+ * 解析日期时间
+ *
+ * @param s s
+ * @return {@link LocalDateTime }
+ */
+ private static Date parseDateTime(String s) {
+ return s.isEmpty() ? null : DateUtils.parseDate(s);
+ }
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java
new file mode 100644
index 0000000..fec9362
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java
@@ -0,0 +1,35 @@
+package com.ff.game.api.pp.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 玩家账户信息响应实体
+ */
+@Data
+public class PPPlayerAccountResponse {
+
+ /**
+ * 响应状态码 (必需)
+ * 示例: "0" 表示成功
+ */
+ @JsonProperty("error")
+ private String error;
+
+ /**
+ * 响应状态描述 (必需)
+ * 示例: "OK" 表示成功
+ */
+ @JsonProperty("description")
+ private String description;
+
+ /**
+ * 玩家余额 (必需)
+ * 示例: 999.99
+ */
+ @JsonProperty("balance")
+ private BigDecimal balance;
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java
new file mode 100644
index 0000000..3903c35
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java
@@ -0,0 +1,30 @@
+package com.ff.game.api.pp.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * ppgame发布响应
+ *
+ * @author shi
+ * @date 2025/04/14
+ */
+@Data
+public class PPResponse {
+ /**
+ * 响应状态码
+ * 示例: "0" 表示成功
+ */
+ @JsonProperty("error")
+ private String error;
+
+ /**
+ * 响应状态描述
+ * 示例: "OK" 表示成功
+ */
+ @JsonProperty("description")
+ private String description;
+
+
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java
new file mode 100644
index 0000000..be0b8e2
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java
@@ -0,0 +1,43 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 回复
+ *
+ * @author shi
+ * @date 2025/04/14
+ */
+@Data
+public class PPTransactionResponse {
+ /**
+ * 响应状态码 (必需)
+ * 示例: "0" 表示成功
+ */
+ @JsonProperty("error")
+ private String error;
+
+ /**
+ * 响应状态描述 (必需)
+ * 示例: "OK" 表示成功
+ */
+ @JsonProperty("description")
+ private String description;
+
+ /**
+ * 交易ID (必需)
+ * 示例: 1908759
+ */
+ @JsonProperty("transactionId")
+ private String transactionId;
+
+ /**
+ * 账户余额 (必需)
+ * 示例: 999.99
+ */
+ @JsonProperty("balance")
+ private BigDecimal balance;
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java
new file mode 100644
index 0000000..ac29195
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java
@@ -0,0 +1,90 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 替换
+ *
+ * @author shi
+ * @date 2025/04/14
+ */
+@Data
+public class PPTransactionStatusResponse {
+ /**
+ * 响应状态码
+ *
+ * 遵循系统定义的错误码规范:
+ *
+ * - "0" - 表示成功
+ * - 其他值 - 表示具体错误类型
+ *
+ *
+ *
+ * @apiNote 必填字段
+ * @example "0"
+ */
+ @JsonProperty("error")
+ private String error;
+
+ /**
+ * 响应状态描述
+ * 人类可读的状态说明信息
+ *
+ * @apiNote 必填字段
+ * @example "OK"
+ */
+ @JsonProperty("description")
+ private String description;
+
+ /**
+ * 交易唯一标识符
+ * 系统生成的交易流水号,用于后续查询和跟踪
+ *
+ * @apiNote 必填字段
+ * @example 1908759
+ */
+ @JsonProperty("transactionId")
+ private Long transactionId;
+
+ /**
+ * 交易状态
+ *
+ * 可能的值包括:
+ *
+ * - "Success" - 交易成功
+ * - "Pending" - 处理中
+ * - "Failed" - 交易失败
+ *
+ *
+ *
+ * @apiNote 必填字段
+ * @example "Success"
+ */
+ @JsonProperty("status")
+ private String status;
+
+ /**
+ * 交易金额
+ *
+ * 字符串格式的金额数值,保留两位小数。
+ *
+ *
+ * @apiNote 必填字段
+ * @example "999.99"
+ */
+ @JsonProperty("amount")
+ private BigDecimal amount;
+
+ /**
+ * 账户余额
+ * 交易完成后的实时余额,使用BigDecimal保证精度
+ *
+ * @apiNote 可选字段(某些查询接口可能返回null)
+ * @example 999.99
+ */
+ @JsonProperty("balance")
+ private BigDecimal balance;
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java
new file mode 100644
index 0000000..fc9c1d6
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java
@@ -0,0 +1,33 @@
+package com.ff.game.api.pp.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 娱乐场运营商系统用户账户响应实体
+ * 严格保密 - 仅用于预期目的和接收者!
+ */
+@Data
+public class PPUserAccountResponse {
+
+ /**
+ * 响应状态码 (必需)
+ * 示例: "0" 表示成功
+ */
+ @JsonProperty("error")
+ private String error;
+
+ /**
+ * 响应状态描述 (必需)
+ * 示例: "OK" 表示成功
+ */
+ @JsonProperty("description")
+ private String description;
+
+ /**
+ * 玩家唯一标识符 (必需)
+ * 示例: 6749178
+ */
+ @JsonProperty("playerId")
+ private Long playerId;
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java b/ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java
new file mode 100644
index 0000000..6cfc231
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java
@@ -0,0 +1,31 @@
+package com.ff.game.api.pp.forest;
+
+
+import com.dtflys.forest.callback.SuccessWhen;
+import com.dtflys.forest.http.ForestRequest;
+import com.dtflys.forest.http.ForestResponse;
+
+/**
+ * 我kmsuccess状态
+ *
+ * @author shi
+ * @date 2025/04/02
+ */
+public class MySuccessCondition implements SuccessWhen {
+
+ /**
+ * 请求成功条件
+ * @param req Forest请求对象
+ * @param res Forest响应对象
+ * @return 是否成功,true: 请求成功,false: 请求失败
+ */
+ @Override
+ public boolean successWhen(ForestRequest req, ForestResponse res) {
+ // req 为Forest请求对象,即 ForestRequest 类实例
+ // res 为Forest响应对象,即 ForestResponse 类实例
+ // 返回值为 ture 则表示请求成功,false 表示请求失败
+ return res.noException();
+ // 当然在这里也可以写其它条件,比如 通过 res.getResult() 或 res.getContent() 获取业务数据
+ // 再根据业务数据判断是否成功
+ }
+}
diff --git a/ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java b/ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java
new file mode 100644
index 0000000..4df520d
--- /dev/null
+++ b/ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java
@@ -0,0 +1,645 @@
+package com.ff.game.api.pp.impl;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.NumberUtil;
+import com.ff.base.constant.CacheConstants;
+import com.ff.base.constant.Constants;
+import com.ff.base.core.redis.RedisCache;
+import com.ff.base.enums.*;
+import com.ff.base.exception.base.ApiException;
+import com.ff.base.exception.base.BaseException;
+import com.ff.base.utils.DateUtils;
+import com.ff.base.utils.JsonUtil;
+import com.ff.base.utils.StringUtils;
+import com.ff.base.utils.sign.Md5Utils;
+import com.ff.game.api.IGamesService;
+import com.ff.game.api.pp.client.PPClient;
+import com.ff.game.api.pp.dto.*;
+import com.ff.game.api.request.*;
+import com.ff.game.domain.*;
+import com.ff.game.service.IGameBettingDetailsService;
+import com.ff.game.service.IGameExchangeMoneyService;
+import com.ff.game.service.IGameFreeRecordService;
+import com.ff.game.service.IGameService;
+import com.ff.member.domain.Member;
+import com.ff.member.service.IMemberService;
+import com.ff.utils.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+
+/**
+ * DG 游戏 impl
+ *
+ * @author shi
+ * @date 2024/11/12
+ */
+@Service("PPService")
+@Slf4j
+public class GamesPPServiceImpl implements IGamesService {
+
+
+ @Resource
+ private RedisCache gRedisCache;
+
+ @Resource
+ private IGameService gameService;
+
+ @Resource
+ private IMemberService memberService;
+
+ @Resource
+ private IGameFreeRecordService gameFreeRecordService;
+
+ @Resource
+ private PPClient ppClient;
+
+ @Resource
+ private IGameBettingDetailsService gameBettingDetailsService;
+
+
+ @Resource
+ private IGameExchangeMoneyService gameExchangeMoneyService;
+
+
+
+ /**
+ * 获得就是成功
+ *
+ * @param errorCode 错误代码
+ * @return {@link Boolean }
+ */
+ private Boolean getIsSuccess(String errorCode) {
+ ApiException.isTrue(!"7".equals(errorCode), ErrorCode.INSUFFICIENT_PLAYER_BALANCE.getCode());
+ return "0".equals(errorCode);
+ }
+
+
+ /**
+ * 获取密钥
+ *
+ * @param gamesBaseRequestDTO 游戏请求dto
+ * @return {@link String }
+ */
+ private String getKey(GamesBaseRequestDTO gamesBaseRequestDTO) {
+ //取出对应的key跟密钥跟请求参数
+ String agentKey = gamesBaseRequestDTO.getAgentKey();
+
+ return Md5Utils.md5New(gamesBaseRequestDTO.getQuery() + agentKey);
+ }
+
+ /**
+ * 转换为ingress
+ *
+ * @param terminalTypes 终端类型
+ * @return {@link Integer }
+ */
+ public Integer convertToIngress(String terminalTypes) {
+ if (terminalTypes == null || terminalTypes.isEmpty()) {
+ return null;
+ }
+
+ boolean hasMobile = terminalTypes.contains("MOBILE");
+ boolean hasWeb = terminalTypes.contains("WEB");
+
+ if (hasMobile && hasWeb) {
+ return IngressType.PC_AND_MOBILE_WEB.getValue();
+ } else if (hasMobile) {
+ return IngressType.MOBILE_WEB.getValue();
+ } else if (hasWeb) {
+ return IngressType.PC_WEB.getValue();
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * 创建成员
+ *
+ * @param createMemberRequestDTO 创建成员请求dto
+ * @return {@link Boolean }
+ */
+ @Override
+ public Boolean createMember(CreateMemberRequestDTO createMemberRequestDTO) {
+ log.info("GamesPPServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO);
+ Map params = new TreeMap<>();
+ params.put("externalPlayerId", createMemberRequestDTO.getAccount());
+ params.put("secureLogin", createMemberRequestDTO.getAgentId());
+ params.put("currency", createMemberRequestDTO.getCurrency());
+ String query = JsonUtil.mapToQueryString(params);
+ createMemberRequestDTO.setQuery(query);
+ String key = this.getKey(createMemberRequestDTO);
+ params.put("hash", key);
+ PPUserAccountResponse member = ppClient.createMember(params);
+ if (this.getIsSuccess(member.getError())) {
+ return Boolean.TRUE;
+ }
+ //判断是否获取成功
+ return Boolean.FALSE;
+ }
+
+
+ /**
+ * 获取会员信息
+ *
+ * @param memberInfoRequestDTO 会员信息请求dto
+ * @return {@link MemberInfoResponseDTO }
+ */
+ @Override
+ public MemberInfoResponseDTO getMemberInfo(MemberInfoRequestDTO memberInfoRequestDTO) {
+ log.info("GamesPPServiceImpl [loginWithoutRedirect] 请求参数 {}", memberInfoRequestDTO);
+ Map params = new TreeMap<>();
+ params.put("secureLogin", memberInfoRequestDTO.getAgentId());
+ params.put("externalPlayerId", memberInfoRequestDTO.getAccounts());
+ String query = JsonUtil.mapToQueryString(params);
+ memberInfoRequestDTO.setQuery(query);
+ String key = this.getKey(memberInfoRequestDTO);
+ params.put("hash", key);
+ PPPlayerAccountResponse memberInfo = ppClient.getMemberInfo(params);
+ if (this.getIsSuccess(memberInfo.getError())) {
+ return MemberInfoResponseDTO.builder().account(memberInfoRequestDTO.getAccounts())
+ .balance(memberInfo.getBalance())
+ .status(GameMemberStatus.UNKNOWN.getCode()).build();
+ }
+ throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode());
+ }
+
+
+ /**
+ * 无重定向登录
+ *
+ * @param gamesLogin 游戏登录
+ * @return {@link String }
+ */
+ @Override
+ public String loginWithoutRedirect(GamesLogin gamesLogin) {
+ log.info("GamesPPServiceImpl [loginWithoutRedirect] 请求参数 {}", gamesLogin);
+
+ Map params = new TreeMap<>();
+ params.put("secureLogin", gamesLogin.getAgentId());
+ params.put("externalPlayerId", gamesLogin.getAccount());
+ params.put("gameId", gamesLogin.getGameId());
+ params.put("language", gamesLogin.getLang());
+ if (!StringUtils.isEmpty(gamesLogin.getPlatform())) {
+ if ("web".equalsIgnoreCase(gamesLogin.getPlatform())) {
+ params.put("platform", "WEB");
+ } else {
+ params.put("platform", "MOBILE");
+ }
+
+ }
+ params.put("lobbyURL", gamesLogin.getHomeUrl());
+ String query = JsonUtil.mapToQueryString(params);
+ gamesLogin.setQuery(query);
+ String key = this.getKey(gamesLogin);
+ params.put("hash", key);
+ PPGameLaunchResponse ppGameLaunchResponse = ppClient.loginWithoutRedirect(params);
+
+
+ if (this.getIsSuccess(ppGameLaunchResponse.getError())) {
+ return ppGameLaunchResponse.getGameUrl();
+ } else {
+ throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode());
+ }
+ }
+
+
+ /**
+ * 获取游戏列表
+ *
+ * @param gamesBaseRequestDTO 游戏请求dto
+ * @return {@link String }
+ */
+ @Transactional
+ @Override
+ public String getGameList(GamesBaseRequestDTO gamesBaseRequestDTO) {
+ List gamesDatas = gRedisCache.getCacheList(CacheConstants.PP_GAMES);
+ if (!CollectionUtils.isEmpty(gamesDatas)) {
+ return CacheConstants.PP_GAMES;
+ }
+
+
+ Map params = new LinkedHashMap<>();
+ params.put("secureLogin", gamesBaseRequestDTO.getAgentId());
+ String query = JsonUtil.mapToQueryString(params);
+ gamesBaseRequestDTO.setQuery(query);
+ String key = this.getKey(gamesBaseRequestDTO);
+ params.put("hash", key);
+
+ PPGameResponseDTO gameList = ppClient.getGameList(params);
+ if (this.getIsSuccess(gameList.getError())) {
+ for (PPGameResponseDTO.CasinoGame apiGameInfoResponseDTO : gameList.getGameList()) {
+ Integer platformType = PPGameType.findSystemByCode(apiGameInfoResponseDTO.getGameTypeID());
+ //不支持的游戏跳过
+ if (ObjectUtils.isEmpty(platformType)) {
+ continue;
+ }
+ Integer ingress = convertToIngress(apiGameInfoResponseDTO.getPlatform());
+ //不需要的平台
+ if (ObjectUtils.isEmpty(ingress)) {
+ continue;
+ }
+
+ Game game = Game.builder()
+ .platformCode(GamePlatforms.PP.getCode())
+ .platformType(platformType)
+ .gameCode(apiGameInfoResponseDTO.getGameID())
+ .build();
+ List games = gameService.selectGameList(game);
+ //不存在这个游戏
+ if (CollectionUtils.isEmpty(games)) {
+ game.setIngress(ingress);
+ game.setGameSourceType(apiGameInfoResponseDTO.getGameTypeID());
+ game.setFreespin(Boolean.FALSE);
+ game.setDemoStatus(Boolean.FALSE);
+ game.setSortNo(gameService.selectMaxSortNo(platformType, GamePlatforms.PP.getCode()) + 1);
+ game.setGameName(apiGameInfoResponseDTO.getGameName());
+ game.setCreateBy("system");
+ game.setPlatformCode(GamePlatforms.PP.getCode());
+ game.setPlatformType(platformType);
+ game.setGameId(StringUtils.addSuffix(GamePlatforms.PP.getCode(), apiGameInfoResponseDTO.getGameID()));
+ List nameInfos = new ArrayList<>();
+ nameInfos.add(new NameInfo(apiGameInfoResponseDTO.getGameName(), "en-US"));
+ game.setNameInfo(nameInfos);
+ gameService.insertGame(game);
+ } else {
+ game = games.get(0);
+ }
+ apiGameInfoResponseDTO.setSystemGameId(game.getGameId());
+ apiGameInfoResponseDTO.setSystemPlatformType(platformType);
+
+ }
+
+ gRedisCache.deleteObject(CacheConstants.PP_GAMES);
+ gRedisCache.setCacheList(CacheConstants.PP_GAMES, gameList.getGameList());
+ gRedisCache.expire(CacheConstants.PP_GAMES, 5L, TimeUnit.HOURS);
+ } else {
+ throw new BaseException(gameList.getDescription());
+ }
+
+ return CacheConstants.PP_GAMES;
+ }
+
+ /**
+ * 获取交易id
+ *
+ * @param transactionIdRequestDTO 事务id请求dto
+ * @return {@link String }
+ */
+ @Override
+ public String getTransactionId(TransactionIdRequestDTO transactionIdRequestDTO) {
+ return GamePlatforms.PP.getCode() + IdUtil.getSnowflakeNextIdStr();
+ }
+
+ /**
+ * 按代理id进行交换转账
+ *
+ * @param exchangeTransferMoneyRequestDTO 外汇转账moeny dto
+ * @return {@link Long }
+ */
+ @Override
+ @Transactional
+ public Long exchangeTransferByAgentId(ExchangeTransferMoneyRequestDTO exchangeTransferMoneyRequestDTO) {
+ log.info("GamesKMServiceImpl [exchangeTransferByAgentId] 请求参数 {}", exchangeTransferMoneyRequestDTO);
+
+
+ GameExchangeMoney exchangeMoney = gameExchangeMoneyService.selectGameExchangeMoneyById(exchangeTransferMoneyRequestDTO.getGameExchangeId());
+
+
+ BigDecimal amount = exchangeTransferMoneyRequestDTO.getAmount();
+ if (TransferType.ALL.getCode().equals(exchangeTransferMoneyRequestDTO.getTransferType())) {
+ // 获取第三方钱包余额
+ MemberInfoRequestDTO gamesBaseRequestDTO = MemberInfoRequestDTO.builder()
+ .accounts(exchangeTransferMoneyRequestDTO.getAccount())
+ .agentId(exchangeTransferMoneyRequestDTO.getAgentId())
+ .currency(exchangeTransferMoneyRequestDTO.getCurrency())
+ .agentKey(exchangeTransferMoneyRequestDTO.getAgentKey())
+ .build();
+
+ amount = this.getMemberInfo(gamesBaseRequestDTO).getBalance().negate();
+ }
+
+ Map params = new TreeMap<>();
+ params.put("secureLogin", exchangeTransferMoneyRequestDTO.getAgentId());
+ params.put("externalPlayerId", exchangeTransferMoneyRequestDTO.getAccount());
+ params.put("externalTransactionId", exchangeTransferMoneyRequestDTO.getTransactionId());
+ params.put("amount", amount);
+ String query = JsonUtil.mapToQueryString(params);
+ exchangeTransferMoneyRequestDTO.setQuery(query);
+ String key = this.getKey(exchangeTransferMoneyRequestDTO);
+ params.put("hash", key);
+
+ PPTransactionResponse transactionResponse = ppClient.exchangeTransfer(params);
+ //判断是否转移成功
+ if (this.getIsSuccess(transactionResponse.getError())) {
+ amount = amount.abs();
+ //更新数据
+ exchangeMoney.setBalance(amount);
+ exchangeMoney.setCoinBefore(NumberUtil.sub(amount, transactionResponse.getBalance()).abs());
+ exchangeMoney.setCoinAfter(transactionResponse.getBalance());
+ exchangeMoney.setCurrencyBefore(exchangeMoney.getCoinBefore());
+ exchangeMoney.setCurrencyAfter(exchangeMoney.getCoinAfter());
+ exchangeMoney.setStep(GameExchangeStep.PLATFORM_TRANSACTION.getCode());
+ exchangeMoney.setStepStatus(GameExchangeStepStatus.SUCCESS.getCode());
+ exchangeMoney.setPartyTransactionId(transactionResponse.getTransactionId());
+ gameExchangeMoneyService.updateGameExchangeMoney(exchangeMoney);
+ } else {
+
+ exchangeMoney.setStep(GameExchangeStep.PLATFORM_TRANSACTION.getCode());
+ exchangeMoney.setStepStatus(GameExchangeStepStatus.FAILURE.getCode());
+ gameExchangeMoneyService.updateGameExchangeMoney(exchangeMoney);
+ log.error("GamesPPServiceImpl [exchangeTransferByAgentId] 金额转移失败,错误代码{},错误信息{}", transactionResponse.getError(), transactionResponse.getDescription());
+ throw new ApiException(ErrorCode.BALANCE_TRANSFER_FAILED.getCode());
+ }
+
+ return exchangeMoney.getId();
+ }
+
+ /**
+ * 汇兑转移状态
+ *
+ * @param exchangeTransferMoneyRequestDTO 兑换转账请求dto
+ * @return {@link Boolean }
+ */
+ @Override
+ public ExchangeTransferStatusResponseDTO exchangeTransferStatus(ExchangeTransferStatusRequestDTO exchangeTransferMoneyRequestDTO) {
+ log.info("GamesPPServiceImpl [exchangeTransferStatus] 请求参数 {}", exchangeTransferMoneyRequestDTO);
+ GameExchangeMoney exchangeMoney = gameExchangeMoneyService.selectGameExchangeMoneyById(exchangeTransferMoneyRequestDTO.getGameExchangeMoneyId());
+
+ Map params = new TreeMap<>();
+ params.put("secureLogin", exchangeTransferMoneyRequestDTO.getAgentId());
+ params.put("externalTransactionId", StringUtils.isEmpty(exchangeMoney.getPartyTransactionId()) ? "0" : exchangeMoney.getPartyTransactionId());
+ params.put("externalPlayerId", exchangeTransferMoneyRequestDTO.getOrderId());
+ String query = JsonUtil.mapToQueryString(params);
+ exchangeTransferMoneyRequestDTO.setQuery(query);
+ String key = this.getKey(exchangeTransferMoneyRequestDTO);
+ params.put("hash", key);
+
+ PPTransactionStatusResponse ppTransactionStatusResponse = ppClient.exchangeTransferStatus(params);
+ if (this.getIsSuccess(ppTransactionStatusResponse.getError())) {
+ Integer status = StatusType.IN_PROGRESS.getValue();
+ if ("Success".equals(ppTransactionStatusResponse.getStatus())) {
+ status = StatusType.SUCCESS.getValue();
+ } else {
+ status = StatusType.FAILURE.getValue();
+ }
+ return ExchangeTransferStatusResponseDTO.builder()
+ .statusType(status)
+ .balance(ppTransactionStatusResponse.getAmount())
+ .coinAfter(ppTransactionStatusResponse.getBalance())
+ .build();
+ } else {
+ log.error("GamesPPServiceImpl [exchangeTransferStatus] 金额转移确认失败,错误代码{},错误信息{}", ppTransactionStatusResponse.getError(), ppTransactionStatusResponse.getDescription());
+ return ExchangeTransferStatusResponseDTO.builder()
+ .statusType(StatusType.FAILURE.getValue())
+ .build();
+ }
+
+ }
+
+
+ /**
+ * 按时间获取投注记录
+ *
+ * @param betRecordByTimeDTO 按时间dto投注记录
+ * @return {@link List }<{@link GameBettingDetails }>
+ */
+ @Override
+ public Boolean getBetRecordByTime(BetRecordByTimeDTO betRecordByTimeDTO) {
+ Long timepoint = gRedisCache.getCacheObject(CacheConstants.PP_TIME_POINT);
+ if (ObjectUtils.isEmpty(timepoint)) {
+ timepoint = betRecordByTimeDTO.getStartTime();
+ }
+
+ //请求参数
+ log.info("GamesPPServiceImpl [getBetRecordByTime] 请求参数 {}", betRecordByTimeDTO);
+ Map params = new LinkedHashMap<>();
+ params.put("login", betRecordByTimeDTO.getAgentId());
+ params.put("password", betRecordByTimeDTO.getAgentKey());
+ params.put("timepoint", timepoint);
+ String result = ppClient.getBetRecordByTime(JsonUtil.mapToQueryString(params));
+ this.batchInsert(PPGameRoundRecord.parse(result), betRecordByTimeDTO);
+ //重新保存最新的时间
+ String[] lines = result.split("\n");
+ timepoint = Long.parseLong(lines[0].replace("timepoint=", ""));
+ gRedisCache.setCacheObject(CacheConstants.PP_TIME_POINT, timepoint);
+
+ return Boolean.TRUE;
+
+ }
+
+
+ /**
+ * 按历史时间获取投注记录
+ *
+ * @param betRecordByTimeDTO 按时间dto投注记录
+ * @return {@link Boolean }
+ */
+ @Override
+ public Boolean getBetRecordByHistoryTime(BetRecordByTimeDTO betRecordByTimeDTO) {
+
+ //请求参数
+ log.info("GamesPPServiceImpl [getBetRecordByHistoryTime] 请求参数 {}", betRecordByTimeDTO);
+ long timepoint = betRecordByTimeDTO.getStartTime();
+ while (timepoint < betRecordByTimeDTO.getEndTime()) {
+ Map params = new LinkedHashMap<>();
+ params.put("login", betRecordByTimeDTO.getAgentId());
+ params.put("password", betRecordByTimeDTO.getAgentKey());
+ params.put("timepoint", timepoint);
+ String result = ppClient.getBetRecordByTime(JsonUtil.mapToQueryString(params));
+ this.batchInsert(PPGameRoundRecord.parse(result), betRecordByTimeDTO);
+ //重新保存最新的时间
+ String[] lines = result.split("\n");
+ timepoint = Long.parseLong(lines[0].replace("timepoint=", ""));
+ }
+
+
+ return Boolean.TRUE;
+ }
+
+ /**
+ * 赠送免费局数
+ *
+ * @param createFreeSpinRequest 创建自由旋转请求
+ * @return {@link Boolean }
+ */
+ @Override
+ public Boolean createFreeSpin(CreateFreeSpinRequestDTO createFreeSpinRequest) {
+ throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode());
+ }
+
+ /**
+ * 获取游戏详细信息
+ *
+ * @param getGameDetailRequestDTO 获取游戏详细信息请求dto
+ * @return {@link GetGameDetailResponseDTO }
+ */
+ @Override
+ public GetGameDetailResponseDTO getGameDetail(GetGameDetailRequestDTO getGameDetailRequestDTO) {
+ throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode());
+ }
+
+ /**
+ * 强制会员从游戏注销
+ *
+ * @param kickMemberRequestDTO 踢会员请求dto
+ * @return {@link Boolean }
+ */
+ @Override
+ public Boolean kickMember(KickMemberRequestDTO kickMemberRequestDTO) {
+ log.info("GamesPPServiceImpl [kickMember] 请求参数 {}", kickMemberRequestDTO);
+ Map params = new TreeMap<>();
+ params.put("secureLogin", kickMemberRequestDTO.getAgentId());
+ params.put("externalPlayerId", kickMemberRequestDTO.getAccount());
+ String query = JsonUtil.mapToQueryString(params);
+ kickMemberRequestDTO.setQuery(query);
+ String key = this.getKey(kickMemberRequestDTO);
+ params.put("hash", key);
+ PPResponse kickMember = ppClient.kickMember(params);
+ if (this.getIsSuccess(kickMember.getError())) {
+ return Boolean.TRUE;
+ } else {
+ throw new ApiException(ErrorCode.KICK_OUT_AILED.getCode());
+ }
+ }
+
+ /**
+ * 踢成员全部
+ *
+ * @param kickMemberAllDTO 踢成员全部dto
+ * @return {@link Boolean }
+ */
+ @Override
+ public Boolean kickMemberAll(KickMemberAllDTO kickMemberAllDTO) {
+ throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode());
+ }
+
+ /**
+ * 免费游戏玩家使用的纪录
+ *
+ * @param getFreeSpinDashflowRequestDTO 获取自由旋转dashflow请求dto
+ * @return {@link List }<{@link GameFreeRecord }>
+ */
+ @Override
+ public List getFreeSpinDashflow(GetFreeSpinDashflowRequestDTO getFreeSpinDashflowRequestDTO) {
+ throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode());
+ }
+
+ /**
+ * 取消赠送免费局数
+ *
+ * @param cancelFreeSpinRequestDTO 取消免费旋转请求
+ * @return {@link Boolean }
+ */
+ @Override
+ public Boolean cancelFreeSpin(CancelFreeSpinRequestDTO cancelFreeSpinRequestDTO) {
+ throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode());
+ }
+
+ @Override
+ public GameDemoLoginResponseDTO gameDemoLogin(GameDemoLoginRequestDTO gameDemoLoginRequestDTO) {
+ throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode());
+ }
+
+
+ /**
+ * 批量插入
+ */
+ private synchronized void batchInsert(List report, GamesBaseRequestDTO gamesBaseRequestDTO) {
+ List gameBettingDetails = new ArrayList<>();
+ List wagersIds = new ArrayList<>();
+ //数据转化
+ for (PPGameRoundRecord bean : report) {
+ GameBettingDetails bettingDetails = this.dataBuild(GamesDataBuildDTO.builder().platform(gamesBaseRequestDTO.getVendor()).data(bean).build());
+ if (!ObjectUtils.isEmpty(bettingDetails)) {
+ bettingDetails.setId(IdUtil.getSnowflakeNextId());
+ gameBettingDetails.add(bettingDetails);
+ }
+ wagersIds.add(String.valueOf(bean.getPlaySessionID()));
+ }
+ if (!CollectionUtils.isEmpty(gameBettingDetails)) {
+ //查询重复数据id
+ List removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds, GamePlatforms.PP.getCode());
+ //用steam流清除list中与wagersIds集合相同的数据
+ gameBettingDetails = gameBettingDetails.stream()
+ .filter(detail -> !removeWagersIds.contains(detail.getWagersId()))
+ .collect(Collectors.toList());
+ if (!CollectionUtils.isEmpty(gameBettingDetails)) {
+ gameBettingDetailsService.batchInsert(gameBettingDetails);
+ }
+ }
+
+ }
+
+ /**
+ * 数据构建
+ *
+ * @param gamesDataBuildDTO 数据
+ * @return {@link GameBettingDetails }
+ */
+ @Override
+ public GameBettingDetails dataBuild(GamesDataBuildDTO gamesDataBuildDTO) {
+ //转化类
+ PPGameRoundRecord resultBean = (PPGameRoundRecord) gamesDataBuildDTO.getData();
+
+
+ Member member = memberService.selectMemberByGameAccount(resultBean.getExtPlayerID());
+ if (ObjectUtils.isEmpty(member)) {
+ return null;
+ }
+ List gamesDatas = gRedisCache.getCacheList(CacheConstants.PP_GAMES);
+ Map dataDTOMap = gamesDatas.stream().collect(Collectors.toMap(
+ PPGameResponseDTO.CasinoGame::getGameID,
+ e -> e,
+ (existing, replacement) -> existing
+ ));
+ PPGameResponseDTO.CasinoGame casinoGame = dataDTOMap.get(resultBean.getGameID());
+
+ BigDecimal payoffAmount = NumberUtil.sub(resultBean.getWin(), resultBean.getBet());
+ Integer gameStatus = GameStatus.FLAT.getCode();
+ if (payoffAmount.compareTo(BigDecimal.ZERO) > 0) {
+ gameStatus = GameStatus.WIN.getCode();
+ } else if (payoffAmount.compareTo(BigDecimal.ZERO) < 0) {
+ gameStatus = GameStatus.FAIL.getCode();
+ }
+
+
+ //数据构造
+ GameBettingDetails gameBettingDetails = GameBettingDetails.builder()
+ .tenantKey(member.getTenantKey())
+ //保存我们的币种id
+ .currencyCode(gamesDataBuildDTO.getPlatform().getOurCurrency(resultBean.getCurrency()))
+ .memberId(member.getId())
+ .gameCode(resultBean.getGameID())
+ .gameType(casinoGame.getSystemPlatformType())
+ .platformCode(GamePlatforms.PP.getInfo())
+ .gameId(casinoGame.getSystemGameId())
+ .gameName(casinoGame.getGameName())
+ .gameStatus(gameStatus)
+ .gameStatusType("F".equals(resultBean.getType()) ? 19 : 1)
+ .gameCurrencyCode(resultBean.getCurrency())
+ .account(resultBean.getExtPlayerID())
+ .wagersId(resultBean.getPlaySessionID())
+ .wagersTime(resultBean.getStartDate())
+ .betAmount(resultBean.getBet())
+ .payoffTime(resultBean.getEndDate())
+ .payoffAmount(payoffAmount.abs())
+ .settlementTime(resultBean.getEndDate())
+ .turnover(resultBean.getBet())
+ .settlementStatus(SettlementStatusEnum.COMPLETED.getCode())
+ .build();
+ gameBettingDetails.setCreateBy(Constants.SYSTEM);
+ gameBettingDetails.setCreateTime(DateUtils.getNowDate());
+ return gameBettingDetails;
+ }
+}
diff --git a/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java b/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java
index 336dd12..db296fe 100644
--- a/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java
+++ b/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java
@@ -99,4 +99,9 @@ public class GameExchangeMoney extends BaseEntity
* 步骤状态 GameExchangeStepStatus 枚举
*/
private Integer stepStatus;
+
+ /**
+ * 部分平台使用 第三方平台交易id
+ */
+ private String partyTransactionId;
}