WithdrawApplyService.java 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package com.izouma.nineth.service;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.izouma.nineth.annotations.RedisLock;
  4. import com.izouma.nineth.config.Constants;
  5. import com.izouma.nineth.domain.WithdrawApply;
  6. import com.izouma.nineth.dto.PageQuery;
  7. import com.izouma.nineth.dto.UserBankCard;
  8. import com.izouma.nineth.enums.BalanceType;
  9. import com.izouma.nineth.enums.WithdrawStatus;
  10. import com.izouma.nineth.exception.BusinessException;
  11. import com.izouma.nineth.repo.BalanceRecordRepo;
  12. import com.izouma.nineth.repo.UserBalanceRepo;
  13. import com.izouma.nineth.repo.UserBankCardRepo;
  14. import com.izouma.nineth.repo.WithdrawApplyRepo;
  15. import com.izouma.nineth.utils.JpaUtils;
  16. import com.izouma.nineth.utils.SnowflakeIdWorker;
  17. import lombok.AllArgsConstructor;
  18. import lombok.extern.slf4j.Slf4j;
  19. import org.apache.commons.lang3.StringUtils;
  20. import org.redisson.api.RLock;
  21. import org.redisson.api.RedissonClient;
  22. import org.springframework.core.env.Environment;
  23. import org.springframework.data.domain.Page;
  24. import org.springframework.scheduling.annotation.Async;
  25. import org.springframework.stereotype.Service;
  26. import java.math.BigDecimal;
  27. import java.time.LocalDate;
  28. import java.time.LocalDateTime;
  29. import java.time.LocalTime;
  30. import java.util.Arrays;
  31. import java.util.Optional;
  32. import java.util.concurrent.ExecutionException;
  33. import java.util.concurrent.ForkJoinPool;
  34. import java.util.concurrent.TimeUnit;
  35. import java.util.regex.Pattern;
  36. @Service
  37. @AllArgsConstructor
  38. @Slf4j
  39. public class WithdrawApplyService {
  40. private WithdrawApplyRepo withdrawApplyRepo;
  41. private UserBalanceService userBalanceService;
  42. private UserBalanceRepo userBalanceRepo;
  43. private BalanceRecordRepo balanceRecordRepo;
  44. private SysConfigService sysConfigService;
  45. private SandPayService sandPayService;
  46. private UserBankCardRepo userBankCardRepo;
  47. private SnowflakeIdWorker snowflakeIdWorker;
  48. private Environment env;
  49. private RedissonClient redissonClient;
  50. public Page<WithdrawApply> all(PageQuery pageQuery) {
  51. return withdrawApplyRepo.findAll(JpaUtils.toSpecification(pageQuery, WithdrawApply.class), JpaUtils.toPageRequest(pageQuery));
  52. }
  53. @RedisLock("'withdrawApple'+#userId")
  54. public WithdrawApply apply(Long userId, BigDecimal amount) {
  55. if (!sysConfigService.getBoolean("enable_withdraw")) {
  56. throw new BusinessException("提现功能暂时关闭");
  57. }
  58. int limit = sysConfigService.getInt("daily_withdraw_limit");
  59. if (withdrawApplyRepo.countByUserIdAndCreatedAtBetween(userId,
  60. LocalDate.now().atStartOfDay(), LocalDate.now().atTime(LocalTime.MAX)) >= limit) {
  61. throw new BusinessException("每天只能申请提现" + limit + "次");
  62. }
  63. userBalanceService.preWithdraw(userId, amount);
  64. WithdrawApply withdrawApply = WithdrawApply.builder()
  65. .userId(userId)
  66. .amount(amount)
  67. .status(WithdrawStatus.PENDING)
  68. .build();
  69. return withdrawApplyRepo.save(withdrawApply);
  70. }
  71. @RedisLock("'finishWithdrawApply'+#id")
  72. public WithdrawApply finishWithdrawApply(Long id, boolean approve, String reason) {
  73. WithdrawApply apply = withdrawApplyRepo.findById(id).orElseThrow(new BusinessException("提现申请不存在"));
  74. if (apply.getStatus() != WithdrawStatus.PENDING) {
  75. throw new BusinessException("提现申请已处理");
  76. }
  77. if (approve) {
  78. UserBankCard bankCard = userBankCardRepo.findByUserId(apply.getUserId())
  79. .stream().findFirst()
  80. .orElse(null);
  81. if (bankCard == null) {
  82. apply.setStatus(WithdrawStatus.FAIL);
  83. apply.setFinishTime(LocalDateTime.now());
  84. apply.setReason(Optional.ofNullable(reason).orElse("用户未绑卡"));
  85. userBalanceService.modifyBalance(apply.getUserId(), apply.getAmount(), BalanceType.DENY,
  86. "用户未绑卡", false, null);
  87. } else {
  88. BigDecimal chargeAmount;
  89. String withdrawCharge = sysConfigService.getString("withdraw_charge");
  90. if (StringUtils.isNotEmpty(withdrawCharge) && Pattern.matches("^(\\d+|\\d+.\\d+),\\d+$", withdrawCharge)) {
  91. String[] arr = withdrawCharge.split(",");
  92. chargeAmount = apply.getAmount().multiply(new BigDecimal(arr[0]));
  93. BigDecimal minChargeAmount = new BigDecimal(arr[1]);
  94. if (chargeAmount.compareTo(minChargeAmount) < 0) {
  95. chargeAmount = minChargeAmount;
  96. }
  97. } else {
  98. chargeAmount = BigDecimal.ZERO;
  99. }
  100. String withdrawId = snowflakeIdWorker.nextId() + "";
  101. try {
  102. String msg = "";
  103. boolean success = false;
  104. if (Arrays.asList(env.getActiveProfiles()).contains("prod")) {
  105. try {
  106. JSONObject res = sandPayService.transfer(withdrawId, bankCard.getRealName(), bankCard.getBankNo(),
  107. apply.getAmount().subtract(chargeAmount));
  108. if ("0000".equals(res.getString("respCode"))) {
  109. success = true;
  110. } else {
  111. msg = res.getString("respDesc");
  112. }
  113. } catch (Exception e) {
  114. msg = e.getMessage();
  115. }
  116. if (!success) {
  117. throw new BusinessException(msg);
  118. }
  119. apply.setStatus(WithdrawStatus.SUCCESS);
  120. apply.setFinishTime(LocalDateTime.now());
  121. apply.setWithdrawId(withdrawId);
  122. apply.setChannel(Constants.PayChannel.SAND);
  123. } else {
  124. if (Math.random() > 0.5) {
  125. success = true;
  126. apply.setStatus(WithdrawStatus.SUCCESS);
  127. apply.setFinishTime(LocalDateTime.now());
  128. apply.setWithdrawId(withdrawId);
  129. apply.setChannel(Constants.PayChannel.SAND);
  130. } else {
  131. throw new BusinessException("测试服随机失败");
  132. }
  133. }
  134. } catch (Exception e) {
  135. apply.setStatus(WithdrawStatus.FAIL);
  136. apply.setFinishTime(LocalDateTime.now());
  137. apply.setReason(e.getMessage());
  138. boolean lock = e.getMessage() == null || !e.getMessage().contains("系统繁忙");
  139. userBalanceService.modifyBalance(apply.getUserId(), apply.getAmount(), BalanceType.RETURN,
  140. e.getMessage(), lock, withdrawId);
  141. }
  142. }
  143. } else {
  144. apply.setStatus(WithdrawStatus.FAIL);
  145. apply.setFinishTime(LocalDateTime.now());
  146. apply.setReason(Optional.ofNullable(reason).orElse("审核不通过"));
  147. userBalanceService.modifyBalance(apply.getUserId(), apply.getAmount(), BalanceType.DENY,
  148. apply.getReason(), false, null);
  149. }
  150. return withdrawApplyRepo.save(apply);
  151. }
  152. @Async
  153. @RedisLock(value = "'approveAll'", expire = 1, unit = TimeUnit.HOURS)
  154. public void approveAll() throws ExecutionException, InterruptedException {
  155. new ForkJoinPool(5).submit(() -> {
  156. withdrawApplyRepo.findByCreatedAtBeforeAndStatus(LocalDate.now().minusDays(1).atStartOfDay(),
  157. WithdrawStatus.PENDING)
  158. .parallelStream().forEach(withdrawApply -> {
  159. try {
  160. finishWithdrawApply(withdrawApply.getId(), true, null);
  161. } catch (Exception ignored) {
  162. }
  163. });
  164. }).get();
  165. }
  166. @Async
  167. public void fixCharge() throws ExecutionException, InterruptedException {
  168. new ForkJoinPool(5).submit(() -> {
  169. withdrawApplyRepo.findByStatusAndAmountLessThanEqual(WithdrawStatus.SUCCESS, new BigDecimal("1000"))
  170. .parallelStream().forEach(apply -> {
  171. try {
  172. UserBankCard bankCard = userBankCardRepo.findByUserId(apply.getUserId())
  173. .stream().findFirst()
  174. .orElseThrow(new BusinessException("未绑卡"));
  175. JSONObject res = sandPayService.transfer(snowflakeIdWorker.nextId() + "", bankCard.getRealName(),
  176. bankCard.getBankNo(), new BigDecimal("6"));
  177. } catch (Exception e) {
  178. log.error("fixCharge", e);
  179. }
  180. });
  181. }).get();
  182. }
  183. }