WithdrawService.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package modules.withdraw;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.jfinal.plugin.activerecord.Db;
  4. import common.jfinal.AppConfig;
  5. import common.model.BalanceLog;
  6. import common.model.User;
  7. import common.model.Withdraw;
  8. import common.utils.http.MyRet;
  9. import common.utils.hyg.HygSDK;
  10. import modules.order.OrderService;
  11. import java.util.ArrayList;
  12. import java.util.HashMap;
  13. import java.util.List;
  14. import java.util.Map;
  15. public class WithdrawService {
  16. // 提现手续费,750代表万分之750(7.5%)
  17. public static final long WITHDRAW_FEE = 750;
  18. public String hello() {
  19. return "Hello Withdraw";
  20. }
  21. public MyRet create(long amount, User user) {
  22. long amountWithoutFee = amount * (10000 - WITHDRAW_FEE) / 10000;
  23. // 用户余额判断
  24. if (user.getBalance() < (float)amount) {
  25. return MyRet.fail(String.format("提现失败,余额不足,你仅有%s而提现%s", user.getBalance(), amount));
  26. }
  27. try {
  28. String orderSn = "DLTBH_WD_" + OrderService.generateOrderSn();
  29. Db.tx(() -> {
  30. user.setBalance(user.getBalance() - (float)amount);
  31. if (!user.update()) {
  32. throw new RuntimeException("用户余额扣减失败");
  33. }
  34. // 保存提现单据
  35. Withdraw withdraw = new Withdraw();
  36. withdraw.setWithdrawSn(orderSn);
  37. withdraw.setAmount((float)amount);
  38. withdraw.setStatus(10);
  39. withdraw.setUserId(user.getId());
  40. withdraw.setCreateTime(System.currentTimeMillis());
  41. withdraw.setUpdateTime(System.currentTimeMillis());
  42. withdraw.setIsDeleted(0);
  43. // 保存资金流水
  44. BalanceLog l = new BalanceLog();
  45. l.set("create_time", System.currentTimeMillis());
  46. l.set("is_deleted", 0);
  47. l.set("description", "用户提现:" + amount); // 描述中带上百分比
  48. l.set("amount", -amount);
  49. l.set("user_id", user.getLong("id"));
  50. if (!l.save()) {
  51. throw new RuntimeException("资金流水记录创建失败: " + user.getId());
  52. }
  53. if (!withdraw.save()) {
  54. throw new RuntimeException("提现单据保存失败");
  55. }
  56. return true;
  57. });
  58. float amountCNY = (float)amountWithoutFee / (float)100;
  59. float feePercent = (float)WITHDRAW_FEE / (float)100;
  60. return MyRet.ok(String.format("提现%s火花,实际到账%.2f(元),手续费%s%%,申请已提交", amount, amountCNY, feePercent)).setData(findByWithdrawSn(orderSn));
  61. } catch (Exception e) {
  62. return MyRet.fail("提现申请失败: " + e.getMessage());
  63. }
  64. }
  65. public MyRet pass(Withdraw withdraw, long approverId) {
  66. // 记录操作人
  67. withdraw.setApproverId(approverId);
  68. try {
  69. User user = User.dao.findById(withdraw.getUserId());
  70. long amountWithoutFee = (long) (withdraw.getAmount() * (10000 - WITHDRAW_FEE) / 10000);
  71. String workerId = user.getHygWorkerId();
  72. String amountWithoutFeeStr = amountWithoutFee + "";
  73. String hygPositionId = System.getenv("HYG_POSITION_ID");
  74. AppConfig.LOGGER.info("{}, {}, {}, {}", workerId, amountWithoutFeeStr, withdraw.getWithdrawSn(), hygPositionId);
  75. JSONObject withdrawRst = HygSDK.singleDistribute(workerId, amountWithoutFeeStr, withdraw.getWithdrawSn(), hygPositionId);
  76. // 首先保证提现是成功的
  77. if (!withdrawRst.getString("statusCode").equals("000000")) {
  78. withdraw.setHygOrigin(withdrawRst.toString());
  79. withdraw.setReason("慧用工提现失败");
  80. withdraw.setStatus(40);
  81. // 原子化操作user和withdraw的状态
  82. Db.tx(() -> {
  83. // 把款项退给用户
  84. user.setBalance(user.getBalance() + withdraw.getAmount());
  85. if (!user.update()) {
  86. throw new RuntimeException("用户余额归还失败");
  87. }
  88. if (!withdraw.update()) {
  89. throw new RuntimeException("单据状态更新失败");
  90. }
  91. // 保存资金流水
  92. BalanceLog l = new BalanceLog();
  93. l.set("create_time", System.currentTimeMillis());
  94. l.set("is_deleted", 0);
  95. l.set("description", "慧用工转款失败退还"); // 描述中带上百分比
  96. l.set("amount", withdraw.getAmount());
  97. l.set("user_id", user.getLong("id"));
  98. if (!l.save()) {
  99. throw new RuntimeException("资金流水记录创建失败: " + user.getId());
  100. }
  101. return true;
  102. });
  103. return MyRet.fail("慧用工提现失败:" + withdrawRst.getString("statusText"));
  104. }
  105. // 如果是成功提现
  106. withdraw.setHygOrigin(withdrawRst.toString());
  107. withdraw.setStatus(20);
  108. if (withdraw.update()) {
  109. return MyRet.ok("打款请求已发起").setData(withdraw);
  110. } else {
  111. return MyRet.fail("单据状态更新失败");
  112. }
  113. } catch (Exception e) {
  114. return MyRet.fail("打款请求已发起失败: " + e.getMessage());
  115. }
  116. }
  117. public MyRet refuse(Withdraw withdraw, String reason, long approverId) {
  118. User user = User.dao.findById(withdraw.getUserId());
  119. try {
  120. Db.tx(() -> {
  121. user.setBalance(user.getBalance() + withdraw.getAmount());
  122. if (!user.update()) {
  123. throw new RuntimeException("用户余额扣减失败");
  124. }
  125. // 记录操作人
  126. withdraw.setApproverId(approverId);
  127. withdraw.setReason(reason);
  128. withdraw.setStatus(40);
  129. if (!withdraw.update()) {
  130. throw new RuntimeException("提现单据保存失败");
  131. }
  132. // 管理员拒绝
  133. BalanceLog l = new BalanceLog();
  134. l.set("create_time", System.currentTimeMillis());
  135. l.set("is_deleted", 0);
  136. l.set("description", "管理员拒绝提现,请查看原因"); // 描述中带上百分比
  137. l.set("amount", withdraw.getAmount());
  138. l.set("user_id", user.getLong("id"));
  139. if (!l.save()) {
  140. throw new RuntimeException("资金流水记录创建失败: " + user.getId());
  141. }
  142. return true;
  143. });
  144. return MyRet.ok("该单据已拒绝").setData(withdraw);
  145. } catch (Exception e) {
  146. return MyRet.fail("拒绝失败:" + e.getMessage());
  147. }
  148. }
  149. public MyRet list(int pageNumber, int pageSize, Integer status, Long userId) {
  150. // 接收 Integer 类型的 orderStatus
  151. // 构建用于查询当前页订单列表的 SQL
  152. String columns = " * ";
  153. String select = "SELECT " + columns;
  154. StringBuilder fromWhere = new StringBuilder("FROM t_withdraw"); // 基础 from 子句
  155. String orderBy = "ORDER BY update_time DESC";
  156. boolean hasWhereClause = false; // 标志位,用于判断是否已经添加了 WHERE 关键字
  157. List<Object> params = new ArrayList<>(); // 用于 SELECT 列表查询的参数
  158. // ✅ 根据 status 筛选
  159. if (status != null && status >= 0) { // status 通常是枚举,>=0 可能是有效的判断
  160. fromWhere.append(hasWhereClause ? " AND" : " WHERE").append(" status = ?");
  161. params.add(status);
  162. hasWhereClause = true;
  163. }
  164. // ✅ 根据 userId 筛选
  165. // 通常 userId 是正整数,如果 userId 可能为 0 需要特别对待
  166. if (userId != null && userId > 0) { // 通常 userId 从 1 开始,或者根据你的业务逻辑设定
  167. fromWhere.append(hasWhereClause ? " AND" : " WHERE").append(" user_id = ?");
  168. params.add(userId);
  169. hasWhereClause = true;
  170. }
  171. // 计算 LIMIT 的 offset (偏移量)
  172. int offset = (pageNumber - 1) * pageSize;
  173. // 构建查询当前页订单列表的最终 SQL (手动添加 LIMIT)
  174. String listSql = select + " " + fromWhere + " " + orderBy + " LIMIT ?, ?";
  175. params.add(offset);
  176. params.add(pageSize);
  177. List<Withdraw> withdrawList = Withdraw.dao.find(listSql, params.toArray());
  178. // 如果列表为空,直接返回
  179. if (withdrawList.isEmpty()) {
  180. Map<String, Object> response = new HashMap<>(); // 准备返回结构
  181. response.put("list", new ArrayList<>());
  182. response.put("total_row", 0);
  183. response.put("total_page", 0);
  184. response.put("page_size", pageSize);
  185. response.put("page_number", pageNumber);
  186. response.put("total_order_count", 0); // 与你的 total_user_count 对应
  187. return MyRet.ok("查询成功").setData(response);
  188. }
  189. // 获取符合条件的条目数量
  190. long totalRowLong = countWithdraws(status, userId);
  191. // 手动计算 total_page
  192. int totalPage = (int) Math.ceil((double) totalRowLong / pageSize);
  193. if (totalPage == 0 && totalRowLong > 0) { // 解决总行数大于0但总页数为0(pageSize大于totalRow)的问题
  194. totalPage = 1;
  195. }
  196. // 封装最终响应
  197. Map<String, Object> response = new HashMap<>();
  198. response.put("list", withdrawList);
  199. response.put("total_row", totalRowLong); // 符合条件的订单总数
  200. response.put("total_page", totalPage); // 手动计算的总页数
  201. response.put("page_size", pageSize);
  202. response.put("page_number", pageNumber);
  203. response.put("total_order_count", totalRowLong); // 对应 total_user_count,这里是符合订单状态筛选的总数
  204. return MyRet.ok("查询成功").setData(response);
  205. }
  206. public long countWithdraws(Integer status, Long userId) { // 接收 Integer 类型的 status
  207. StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM t_withdraw"); // 使用 COUNT(*),更通用
  208. List<Object> params = new ArrayList<>();
  209. boolean hasWhereClause = false; // 标志位,用于判断是否已经添加了 WHERE 关键字
  210. // ✅ 根据 status 筛选
  211. if (status != null && status >= 0) { // status 通常是枚举,>=0 可能是有效的判断
  212. sql.append(hasWhereClause ? " AND" : " WHERE").append(" status = ?");
  213. params.add(status);
  214. hasWhereClause = true;
  215. }
  216. // ✅ 根据 userId 筛选
  217. // 通常 userId 是正整数,如果 userId 可能为 0 需要特别对待
  218. if (userId != null && userId > 0) { // 通常 userId 从 1 开始,或者根据你的业务逻辑设定
  219. sql.append(hasWhereClause ? " AND" : " WHERE").append(" user_id = ?");
  220. params.add(userId);
  221. hasWhereClause = true;
  222. }
  223. // 执行查询并返回结果
  224. // Db.queryLong() 如果没有结果会返回 null,如果查询结果行数为 0,则返回 Long 0
  225. // 这符合 COUNT() 函数的预期
  226. return Db.queryLong(sql.toString(), params.toArray());
  227. }
  228. public Withdraw findByWithdrawSn(String sn) {
  229. return Withdraw.dao.findFirst("SELECT * FROM t_withdraw WHERE withdraw_sn=?", sn);
  230. }
  231. }