| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- package com.izouma.nineth.service;
- import com.alibaba.fastjson.JSONObject;
- import com.izouma.nineth.annotations.RedisLock;
- import com.izouma.nineth.config.Constants;
- import com.izouma.nineth.domain.WithdrawApply;
- import com.izouma.nineth.dto.PageQuery;
- import com.izouma.nineth.dto.UserBankCard;
- import com.izouma.nineth.enums.BalanceType;
- import com.izouma.nineth.enums.WithdrawStatus;
- import com.izouma.nineth.exception.BusinessException;
- import com.izouma.nineth.repo.BalanceRecordRepo;
- import com.izouma.nineth.repo.UserBalanceRepo;
- import com.izouma.nineth.repo.UserBankCardRepo;
- import com.izouma.nineth.repo.WithdrawApplyRepo;
- import com.izouma.nineth.utils.JpaUtils;
- import com.izouma.nineth.utils.SnowflakeIdWorker;
- import lombok.AllArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.redisson.api.RedissonClient;
- import org.springframework.core.env.Environment;
- import org.springframework.data.domain.Page;
- import org.springframework.scheduling.annotation.Async;
- import org.springframework.stereotype.Service;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.time.LocalDate;
- import java.time.LocalDateTime;
- import java.time.LocalTime;
- import java.util.Arrays;
- import java.util.Optional;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ForkJoinPool;
- import java.util.concurrent.TimeUnit;
- import java.util.regex.Pattern;
- @Service
- @AllArgsConstructor
- @Slf4j
- public class WithdrawApplyService {
- private WithdrawApplyRepo withdrawApplyRepo;
- private UserBalanceService userBalanceService;
- private UserBalanceRepo userBalanceRepo;
- private BalanceRecordRepo balanceRecordRepo;
- private SysConfigService sysConfigService;
- private SandPayService sandPayService;
- private UserBankCardRepo userBankCardRepo;
- private SnowflakeIdWorker snowflakeIdWorker;
- private Environment env;
- private RedissonClient redissonClient;
- private PayEaseService payEaseService;
- public Page<WithdrawApply> all(PageQuery pageQuery) {
- return withdrawApplyRepo.findAll(JpaUtils.toSpecification(pageQuery, WithdrawApply.class), JpaUtils.toPageRequest(pageQuery));
- }
- @RedisLock("'withdrawApply'+#userId")
- public WithdrawApply apply(Long userId, BigDecimal amount) {
- if (!sysConfigService.getBoolean("enable_withdraw")) {
- throw new BusinessException("提现功能暂时关闭");
- }
- int limit = sysConfigService.getInt("daily_withdraw_limit");
- if (withdrawApplyRepo.countByUserIdAndCreatedAtBetween(userId,
- LocalDate.now().atStartOfDay(), LocalDate.now().atTime(LocalTime.MAX)) >= limit) {
- throw new BusinessException("每天只能申请提现" + limit + "次");
- }
- userBalanceService.preWithdraw(userId, amount);
- WithdrawApply withdrawApply = WithdrawApply.builder()
- .userId(userId)
- .amount(amount)
- .status(WithdrawStatus.PENDING)
- .build();
- return withdrawApplyRepo.save(withdrawApply);
- }
- @RedisLock("'finishWithdrawApply'+#id")
- public WithdrawApply finishWithdrawApply(Long id, boolean approve, String reason) {
- String channel = sysConfigService.getString("withdraw_channel");
- if (!(Constants.PayChannel.SAND.equals(channel) || Constants.PayChannel.PE.equals(channel))) {
- throw new BusinessException("unsupported channel");
- }
- WithdrawApply apply = withdrawApplyRepo.findById(id).orElseThrow(new BusinessException("提现申请不存在"));
- if (apply.getStatus() != WithdrawStatus.PENDING) {
- throw new BusinessException("提现申请已处理");
- }
- if (approve) {
- UserBankCard bankCard = userBankCardRepo.findByUserId(apply.getUserId())
- .stream().findFirst()
- .orElse(null);
- if (bankCard == null) {
- apply.setStatus(WithdrawStatus.FAIL);
- apply.setFinishTime(LocalDateTime.now());
- apply.setReason(Optional.ofNullable(reason).orElse("用户未绑卡"));
- userBalanceService.modifyBalance(apply.getUserId(), apply.getAmount(), BalanceType.DENY,
- "用户未绑卡", false, null);
- } else {
- BigDecimal chargeAmount;
- String withdrawCharge = sysConfigService.getString("withdraw_charge");
- if (StringUtils.isNotEmpty(withdrawCharge) && Pattern.matches("^(\\d+|\\d+.\\d+),\\d+$", withdrawCharge)) {
- String[] arr = withdrawCharge.split(",");
- chargeAmount = apply.getAmount().multiply(new BigDecimal(arr[0]))
- .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
- BigDecimal minChargeAmount = new BigDecimal(arr[1]);
- if (chargeAmount.compareTo(minChargeAmount) < 0) {
- chargeAmount = minChargeAmount;
- }
- } else {
- chargeAmount = BigDecimal.ZERO;
- }
- String withdrawId = snowflakeIdWorker.nextId() + "";
- try {
- String msg = "";
- boolean success = false;
- if (Arrays.asList(env.getActiveProfiles()).contains("prod")) {
- try {
- if (Constants.PayChannel.SAND.equals(channel)) {
- JSONObject res = sandPayService.transfer(withdrawId, bankCard.getRealName(), bankCard.getBankNo(),
- apply.getAmount().subtract(chargeAmount), bankCard.getPhone());
- if ("0000".equals(res.getString("respCode"))) {
- success = true;
- } else {
- msg = res.getString("respDesc");
- }
- } else {
- payEaseService.transfer(withdrawId, bankCard.getRealName(), bankCard.getBankNo(),
- apply.getAmount().subtract(chargeAmount), bankCard.getPhone());
- success = true;
- }
- } catch (Exception e) {
- log.error("提现出错", e);
- msg = e.getMessage();
- }
- if (!success) {
- throw new BusinessException(msg);
- }
- apply.setStatus(WithdrawStatus.SUCCESS);
- apply.setFinishTime(LocalDateTime.now());
- apply.setWithdrawId(withdrawId);
- apply.setChannel(Constants.PayChannel.SAND);
- } else {
- if (Math.random() > 0.5) {
- success = true;
- apply.setStatus(WithdrawStatus.SUCCESS);
- apply.setFinishTime(LocalDateTime.now());
- apply.setWithdrawId(withdrawId);
- apply.setChannel(Constants.PayChannel.SAND);
- } else {
- throw new BusinessException("测试服随机失败");
- }
- }
- } catch (Exception e) {
- if (e.getMessage() == null ||
- "商户IP不合法".equals(e.getMessage()) ||
- "账户余额不足".equals(e.getMessage()) ||
- e.getMessage().contains("系统繁忙")) {
- log.info("提现失败", e);
- apply.setReason(e.getMessage());
- } else {
- apply.setStatus(WithdrawStatus.FAIL);
- apply.setFinishTime(LocalDateTime.now());
- apply.setReason(e.getMessage());
- boolean lock = e.getMessage() == null || !e.getMessage().contains("系统繁忙");
- userBalanceService.modifyBalance(apply.getUserId(), apply.getAmount(), BalanceType.RETURN,
- e.getMessage(), lock, withdrawId);
- }
- }
- }
- } else {
- apply.setStatus(WithdrawStatus.FAIL);
- apply.setFinishTime(LocalDateTime.now());
- apply.setReason(Optional.ofNullable(reason).orElse("审核不通过"));
- userBalanceService.modifyBalance(apply.getUserId(), apply.getAmount(), BalanceType.DENY,
- apply.getReason(), false, null);
- }
- return withdrawApplyRepo.save(apply);
- }
- @Async
- @RedisLock(value = "'approveAll'", expire = 1, unit = TimeUnit.HOURS)
- public void approveAllAsync() throws ExecutionException, InterruptedException {
- approveAll();
- }
- @RedisLock(value = "'approveAll'", expire = 1, unit = TimeUnit.HOURS)
- public void approveAll() throws ExecutionException, InterruptedException {
- new ForkJoinPool(5).submit(() -> {
- withdrawApplyRepo.findByCreatedAtBeforeAndStatus(LocalDate.now().atStartOfDay(),
- WithdrawStatus.PENDING)
- .parallelStream().forEach(withdrawApply -> {
- try {
- finishWithdrawApply(withdrawApply.getId(), true, null);
- } catch (Exception ignored) {
- }
- });
- }).get();
- }
- @Async
- @RedisLock(value = "'applyAll'", expire = 1, unit = TimeUnit.HOURS)
- public void applyAll() throws ExecutionException, InterruptedException {
- new ForkJoinPool(5).submit(() -> {
- userBalanceRepo.findAll().parallelStream().forEach(userBalance -> {
- LocalDateTime time = LocalDateTime.now().minusDays(14);
- if (userBalance.getCreatedAt() != null && userBalance.getCreatedAt().isBefore(time)
- && userBalance.getBalance().compareTo(new BigDecimal("100")) >= 0
- && withdrawApplyRepo.countByUserIdAndCreatedAtAfter(userBalance.getUserId(), time) <= 0) {
- log.info("apply withdraw for user {}", userBalance.getUserId());
- try {
- apply(userBalance.getUserId(), userBalance.getBalance());
- } catch (Exception e) {
- log.error("apply withdraw for user {}", userBalance.getUserId(), e);
- }
- }
- });
- }).get();
- }
- }
|