36. 尚融宝放款

后台放款

需求

管理员在后台对标的进行放款

在这里插入图片描述
在这里插入图片描述

相关数据库表

还款人还款记录:lend_return

投资人回款记录:lend_item_return

参考文档

参考《汇付宝商户账户技术文档》3.11满标放款

后端

controller

AdminLendController.java

    @ApiOperation("放款")
    @GetMapping("/makeLoan/{id}")
    public R makeLoan(
            @ApiParam(value = "标的id",required = true)
            @PathVariable Long id){
     
        lendService.makeLoan(id);
        return R.ok().setMessage("放款成功");
    }

service

LendService.java

    void makeLoan(Long id);

LendServiceImpl.java

    @Resource
    private UserAccountMapper userAccountMapper;

    @Resource
    private UserBindService userBindService;

    @Resource
    private TransFlowService transFlowService;

    @Resource
    private LendItemService lendItemService;

	@Override
    @Transactional(rollbackFor = Exception.class)
    public void makeLoan(Long id) {
     
        Lend lend = baseMapper.selectById(id);
        String agentBillNo = LendNoUtils.getLoanNo();
        Map<String, Object> map = new HashMap<>();

        map.put("agentId", HfbConst.AGENT_ID);
        map.put("agentProjectCode", lend.getLendNo());
        map.put("agentBillNo", agentBillNo);

        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        // 平台实际收益:已投金额 * 平台月化率 * 投资时长
        BigDecimal realAmount = lend.getInvestAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));
        map.put("mchFee", realAmount);    

        map.put("timestamp", RequestHelper.getTimestamp());
        map.put("sign", RequestHelper.getSign(map));

        // 放款是一个同步接口,如果放款返回成功,那么我们要处理平台业务
        JSONObject result = RequestHelper.sendRequest(map, HfbConst.MAKE_LOAN_URL);
        BigDecimal voteAmt = result.getBigDecimal("voteAmt");   // 投资金额
        if (!"0000".equals(result.get("resultCode"))) {
     
            throw new BusinessException(result.getString("resultMsg"));
        }

        // 1、更新标的:平台实际收益、状态、支付时间
        lend.setRealAmount(realAmount);
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        lend.setPaymentTime(LocalDateTime.now());
        baseMapper.updateById(lend);

        // 2、更新借款人账户
        String benefitBindCode = userBindService.getBindCodeByUserId(lend.getUserId());
        userAccountMapper.updateAccount(benefitBindCode, voteAmt, new BigDecimal(0));

        // 3、添加交易流水
        TransFlowBO transFlowBO = new TransFlowBO(
                agentBillNo,
                benefitBindCode,
                TransTypeEnum.BORROW_BACK,
                voteAmt,
                "放款到账,项目编号:" + lend.getLendNo()
        );
        transFlowService.saveTransFlow(transFlowBO);

        // 4、解冻并扣除投资人资金
        List<LendItem> lendItemList = lendItemService.listByIdAndStatus(lend.getId(), LendItemEnum.PAY_OK.getStatus());
        lendItemList.forEach(lendItem -> {
     
            String voteBindCode = userBindService.getBindCodeByUserId(lendItem.getInvestUserId());
            userAccountMapper.updateAccount(voteBindCode, new BigDecimal(0), lendItem.getInvestAmount().negate());

            // 5、增加投资人交易流水
            TransFlowBO investTransFlow = new TransFlowBO(
                    LendNoUtils.getLendNo(),
                    voteBindCode,
                    TransTypeEnum.INVEST_UNLOCK,
                    lendItem.getInvestAmount(),
                    "冻结资金转出放款,项目编号:" + lend.getLendNo() + "项目名称:" + lend.getTitle()
            );
            transFlowService.saveTransFlow(investTransFlow);
        });
        
        // 6、生成借款人还款计划、投资人回款计划
        repaymentPlan(lend);
     }       
    @Resource
    private LendReturnService lendReturnService;
    
	/**
     * 生成借款人还款计划	
     *      
     * @param lend 标的
     */
    private void repaymentPlan(Lend lend) {
     
        // 一、创建还款计划列表
        List<LendReturn> lendReturnList = new ArrayList<>();

        // 根据标的还款期限(period)生成还款计划
        // 你借多少期,我就给你生成多少条还款计划
        int period = lend.getPeriod().intValue();
        // 遍历还款期限,从第1期开始
        for (int i = 1; i <= period; i++) {
     
            // 创建还款计划
            LendReturn lendReturn = new LendReturn();

            // 填充属性
            lendReturn.setLendId(lend.getId());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());
            lendReturn.setBaseAmount(lend.getInvestAmount());// 计算利息所需要用到的本金:根据已投金额,得出利息
            lendReturn.setCurrentPeriod(i);
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setReturnMethod(lend.getReturnMethod());

            lendReturn.setFee(BigDecimal.ZERO);
            lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i)); // 开始日期往后i个月开始还款
            lendReturn.setOverdue(false);   // 不逾期
            lendReturn.setLast(i == period);    // 是否为最后一期
            lendReturn.setStatus(0);    // 设置还款状态

            lendReturnList.add(lendReturn); // 将还款计划加入还款计划列表
        }

        // 批量保存还款计划
        lendReturnService.saveBatch(lendReturnList);
        // 将还款计划列表的还款期数、还款计划id提取出来,转换成一个还款计划map
        Map<Integer, Long> lendReturnMap = lendReturnList.stream().collect(
                Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId)
        );


        // ------------------------------------------------------------------
        // ---------------------------获取回款计划----------------------------
        // ------------------------------------------------------------------


        // 创建当前标的下所有投资人的回款计划列表
        List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();

        // 获取当前标的下所有已支付的投资记录
        List<LendItem> lendItemList = lendItemService.listByIdAndStatus(lend.getId(), LendItemEnum.PAY_OK.getStatus());

        // 遍历投资记录
        for (LendItem lendItem : lendItemList) {
     
            // 三、根据投资记录、还款计划map、标的,当前投资记录的所有回款计划
            List<LendItemReturn> lendItemReturnList = this.returnInvest(lendItem, lendReturnMap, lend);
            // 将每个投资记录的回款计划添加至回款计划列表中
            lendItemReturnAllList.addAll(lendItemReturnList);
        }


        // 四、遍历还款计划列表
        for (LendReturn lendReturn : lendReturnList) {
     
            // 计算总利息、总本金、总本息

            // 把回款计划中同一期所有的利息/本金/本息加起来
            // 计算出总利息、总本金、总本息
            BigDecimal principal = lendItemReturnAllList.stream()
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getPrincipal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal interest = lendItemReturnAllList.stream()
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getInterest)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal total = lendItemReturnAllList.stream()
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getTotal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            lendReturn.setPrincipal(principal);
            lendReturn.setInterest(interest);
            lendReturn.setTotal(total);
        }

        // 批量更新
        lendReturnService.updateBatchById(lendReturnList);
    }
    @Resource
    private LendItemReturnService lendItemReturnService;

    /**
     * 投资人还款计划
     *
     * @param lend
     * @param lendReturnMap
     * @param lendItem
     * @return
     */
    private List<LendItemReturn> returnInvest(LendItem lendItem, Map<Integer, Long> lendReturnMap, Lend lend) {
     
        // 投资金额
        BigDecimal investAmount = lendItem.getInvestAmount();

        // 年化利率
        BigDecimal lendYearRate = lendItem.getLendYearRate();

        // 投资期数
        Integer period = lend.getPeriod();

        // 创建还款期数、利息的map
        Map<Integer, BigDecimal> mapInterest;

        // 创建还款期数、本金的map
        Map<Integer, BigDecimal> mapPrincipal;

        // 二、根据还款方式计算本金和利息

        switch (ReturnMethodEnum.getByValue(lend.getReturnMethod().intValue())) {
     
            case ONE:
                mapInterest = Amount1Helper.getPerMonthInterest(investAmount, lendYearRate, period);
                mapPrincipal = Amount1Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
                break;
            case TWO:
                mapInterest = Amount2Helper.getPerMonthInterest(investAmount, lendYearRate, period);
                mapPrincipal = Amount2Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
                break;
            case THREE:
                mapInterest = Amount3Helper.getPerMonthInterest(investAmount, lendYearRate, period);
                mapPrincipal = Amount3Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
                break;
            default:
                mapInterest = Amount4Helper.getPerMonthInterest(investAmount, lendYearRate, period);
                mapPrincipal = Amount4Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
                break;
        }

        // 创建回款计划列表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();
        // 遍历还款期数、利息map
        for (Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
     
            Integer currentPeriod = entry.getKey();    // 当前期数


            // 通过当前期数获取到还款计划的id
            Long lendReturnId = lendReturnMap.get(currentPeriod);

            // 创建回款计划
            LendItemReturn lendItemReturn = new LendItemReturn();
            // 填充数据
            lendItemReturn.setLendReturnId(lendReturnId);
            lendItemReturn.setLendItemId(lendItem.getId());
            lendItemReturn.setLendId(lendItem.getLendId());
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
            lendItemReturn.setCurrentPeriod(currentPeriod);
            lendItemReturn.setLendYearRate(lend.getLendYearRate());
            lendItemReturn.setReturnMethod(lend.getReturnMethod());


            // filter做过滤 map取属性 reduce做处理
            if (currentPeriod.intValue() == lend.getPeriod().intValue()) {
     
                // 最后一期

                // 前几期要还的本金之和
                BigDecimal sumPrincipal = lendItemReturnList.stream()
                        .map(LendItemReturn::getPrincipal)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                // 最后一次还款本金 = 投资人的总投资 - 前几次还款金额之和
                BigDecimal lastPrincipal = lendItem.getInvestAmount().subtract(sumPrincipal);
                lendItemReturn.setPrincipal(lastPrincipal);

                // 前几期要还的利息之和
                BigDecimal sumInterest = lendItemReturnList.stream()
                        .map(LendItemReturn::getInterest)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                // 最后一次还款利息 = 投资人的预期收益 - 前几次还款利息之和
                BigDecimal lastInterest = lendItem.getExpectAmount().subtract(sumInterest);
                lendItemReturn.setInterest(lastInterest);

            } else {
     
                // 非最后一期,直接从map中获取对应当前期数的本金/利息
                lendItemReturn.setPrincipal(mapPrincipal.get(currentPeriod));
                lendItemReturn.setInterest(mapInterest.get(currentPeriod));
            }

            // 回款总金额
            lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest()));

            // 设置其他属性
            lendItemReturn.setFee(BigDecimal.ZERO);
            lendItemReturn.setReturnDate(lend.getLendStartDate().plusMonths(currentPeriod));
            lendItemReturn.setOverdue(false);
            lendItemReturn.setStatus(0);

            // 添加到回款计划列表中
            lendItemReturnList.add(lendItemReturn);
        }

        // 批量保存
        lendItemReturnService.saveBatch(lendItemReturnList);

        // 返回回款计划列表
        return lendItemReturnList;
    }

LendItemService

LendItemService.java

    // 根据标的id和状态获取投资人列表
	List<LendItem> listByIdAndStatus(Long lendId, Integer status)

LendItemServiceImpl.java

    @Override
    public List<LendItem> listByIdAndStatus(Long lendId, Integer status) {
     
        QueryWrapper<LendItem> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("lend_id",lendId).eq("status",status);
        return baseMapper.selectList(queryWrapper);
    }

前端

api

创建 src/api/core/lend.js

  makeLoan(id) {
     
    return request({
     
      url: `/admin/core/lend/makeLoan/${
       id}`,
      method: 'get'
    })
  }

script

src/views/core/lend/list.vue

methods:

    makeLoan(id) {
     
      this.$confirm('确定放款吗?', '提示', {
     
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
     
          return lendApi.makeLoan(id)
        })
        .then(response => {
     
          //放款成功则重新获取数据列表
          this.fetchData()
          this.$message({
     
            type: 'success',
            message: response.message
          })
        })
        .catch(error => {
     
          console.log('取消', error)
          if (error === 'cancel') {
     
            this.$message({
     
              type: 'info',
              message: '已取消放款'
            })
          }
        })
    }

测试各个表的变化

还款方式为等额本息,借3个月,标的金额为95000,实际募集2000,有两个人分别投资了1000元

测试时,注意多个表之间的数据关系,尤其是汇付宝的数据,一旦测试失败,需要重新投资的话,需要清空user_invest表。

尚融宝

lend

标的表
在这里插入图片描述

user_account

账户表
36. 尚融宝放款_第1张图片

trans_flow

流水表
36. 尚融宝放款_第2张图片
lend_return

还款计划表
在这里插入图片描述
在这里插入图片描述

lend_item_return

回款计划表
在这里插入图片描述
36. 尚融宝放款_第3张图片

汇付宝

user_account

账户表
36. 尚融宝放款_第4张图片

你可能感兴趣的:(#,尚融宝后台)