35. 尚融宝投标

简介

1、需求描述

平台发布标的,出借人充值就可以投资标的
在这里插入图片描述

2、相关数据库表

35. 尚融宝投标_第1张图片

3、参考文档

参考《汇付宝商户账户技术文档》3.10满标投资,投资过程与账户绑定、用户充值过程一致

35. 尚融宝投标_第2张图片

标的详情

需求

step1:点击标的,进入标的详情页面

35. 尚融宝投标_第3张图片

后端

controller

LendController.java

    @ApiOperation("标的详情")
    @GetMapping("/getLendDetail/{id}")
    public R getLendDetail(@PathVariable Long id) {
     
        Map<String, Object> lendDetail = lendService.getLendDetail(id);
        return R.ok().setData("lendDetail", lendDetail);
    }

service

LendService.java

    Map<String, Object> getLendDetail(Long id);

LendServiceImpl.java

    @Resource
    private BorrowerService borrowerService;
    
	@Override
    public Map<String, Object> getLendDetail(Long id) {
     
        // 获取表的信息
        Lend lend = baseMapper.selectById(id);
        setExtensionLend(lend);

        QueryWrapper<Borrower> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", lend.getUserId());
        Borrower borrower = borrowerService.getBaseMapper().selectOne(queryWrapper);

        // 获取借款人信息
        BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailVO(borrower.getId());

        Map<String, Object> map = new HashMap<>();
        map.put("lend", lend);
        map.put("borrower", borrowerDetailVO);
        return map;
    }

前端

template

pages/lend/_id.vue

以后也会上传到码云…

script

pages/lend/_id.vue

35. 尚融宝投标_第4张图片

  // 获取标的详情
  // 希望搜索引擎能收录,所以使用异步查询的方式
  async asyncData({
      $axios, params }) {
     
    let lendId = params.id
    let response = await $axios.$get('/api/core/lend/getLendDetail/' + lendId)
    return {
     
      lend: response.data.lendDetail.lend,
      borrower: response.data.lendDetail.borrower,
    }
  },
  //此时方法在客户端的浏览器中执行,可以获取到cookie
  mounted() {
     
    //查询账户余额
    this.fetchAmount()

    //判断登录人的用户类型
    this.fetchUserType()
  },

  methods: {
     
    //查询账户余额
    fetchAmount() {
     
      let userInfo = cookie.get('userInfo')
      if (userInfo) {
     
        this.$axios
          .$get('/api/core/userAccount/auth/getAmount')
          .then((response) => {
     
            this.amount = response.data.amount
          })
      }
    },

    //获取登录人的用户类型
    fetchUserType() {
     
      let userInfo = cookie.get('userInfo')
      if (userInfo) {
     
        userInfo = JSON.parse(userInfo)
        this.userType = userInfo.userType
      }
    },
  },

计算收益

需求

step2:输入投资金额,计算获得收益

还款方式

1、等额本息

等额本息法最重要的一个特点是每月的还款额相同,从本质上来说是本金所占比例逐月递增,利息所占比例逐月递减,月还款数不变。

即在月供“本金与利息”的分配比例中,前半段时期所还的利息比例大、本金比例小,还款期限过半后逐步转为本金比例大、利息比例小。

计算公式为

每月利息 = 剩余本金 x 贷款月利率

每月还本付息金额 = 还款总额 / 贷款月数

每月本金 = 每月还本付息金额 - 每月利息

注意:在等额本息法中,银行一般先收剩余本金利息,后收本金,所以利息在月供款中的比例会随本金的减少而降低,本金在月供款中的比例因而升高,但月供总额保持不变。

2、等额本金

等额本金法最大的特点是每月的还款额不同,呈现逐月递减的状态;它是将贷款本金按还款的总月数均分,再加上上期剩余本金的利息,这样就形成月还款额,所以等额本金法第一个月的还款额最多 ,然后逐月减少,越还越少。

计算公式为:

每月利息 = 剩余本金 x 贷款月利率

每月本金 = 贷款额 / 贷款月数

每月还本付息金额 = 每月本金 + 每月利息

注意:在等额本金法中,人们每月归还的本金额始终不变,利息随剩余本金的减少而减少,因而其每月还款额逐渐减少。

3、按期付息到期还本

按期付息到期还本是借款人在贷款到期日一次性归还贷款本金,利息按期归还

计算公式为

每月利息 = 贷款额 x 贷款月利率

总利息 = 每月利息 x 贷款月数

4、一次还本付息

一次还本付息是贷款到期后一次性归还本金和利息

计算公式为

还款金额 = 贷款额 + 贷款额 x 月利率 x 贷款月数

后端

controller

LendController.java

    @ApiOperation("计算投资收益")
    @GetMapping("/getInterestCount/{invest}/{yearRate}/{totalMonth}/{returnMethod}")
    public R getInterestCount(
            @ApiParam(value = "投资金额", required = true)
            @PathVariable("invest") BigDecimal invest,

            @ApiParam(value = "年化收益", required = true)
            @PathVariable("yearRate") BigDecimal yearRate,

            @ApiParam(value = "期数", required = true)
            @PathVariable("totalMonth") Integer totalMonth,

            @ApiParam(value = "还款方式", required = true)
            @PathVariable("returnMethod") Integer returnMethod){
     
        BigDecimal interestCount = lendService.getInterestCount(invest, yearRate,totalMonth,returnMethod);
        return R.ok().setData("interestCount",interestCount);
    }

enums

ReturnMethodEnum.java

    public static ReturnMethodEnum getByValue(Integer value){
     
        for(ReturnMethodEnum transactType : values()){
     
            if (transactType.getMethod() == value) {
     
                return transactType;
            }
        }
        return null;
    }

util

根据我们的表设计,出借人需要知道每月回款的本金与利息,借款人也一样,他也要知道每月的还款本金与利息,还有我们需要计算投资人的投资收益等数据。
因此我们将四种还款方式工具类设计如下:

在这里插入图片描述
说明:还款方式计算复杂,仅做了解,这里不做详细介绍
后续会上传码云,有急需的可以私信

service

LendService.java

    // 根据用户的还款方式,来计算不同的收益
	BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalMonth, Integer returnMethod);

LendServiceImpl.java

    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalMonth, Integer returnMethod) {
     
        BigDecimal interestCount;
        switch (ReturnMethodEnum.getByValue(returnMethod.intValue())) {
     
            case ONE:
                interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalMonth);
                break;
            case TWO:
                interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalMonth);
                break;
            case THREE:
                interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalMonth);
                break;
            default:
                interestCount = Amount4Helper.getInterestCount(invest, yearRate, totalMonth);
                break;
        }
        return interestCount;
    }

前端

script

pages/lend/_id.vue

    //计算收益
    getInterestCount() {
     
      this.$axios
        .$get(
          `/api/core/lend/getInterestCount/${
       this.invest.investAmount}/${
       this.lend.lendYearRate}/${
       this.lend.period}/${
       this.lend.returnMethod}`
        )
        .then((response) => {
     
          this.interestCount = response.data.interestCount
        })
    },

投标

需求

step3:同意协议,点击立即投资

35. 尚融宝投标_第5张图片
step4:跳转到汇付宝页面(资金托管接口调用)

step5:汇付宝验证用户交易密码

step6:汇付宝修改账号资金余额(更新user_account记录中的amount的值和freeze_amount的值)
汇付宝新增投资记录(新增user_invest记录)

后端

vo

InvestVO.java

@Data
public class InvestVO {
     
    private Long lendId;    // 标的id
    private Long investUserId;  // 投资人id
    private String investUserName;  // 投资人姓名
    private BigDecimal investAmount;    // 投资金额
}

controller

LendItemController.java

@Api("标的项")
@RestController
@RequestMapping("/api/core/lendItem")
@Slf4j
public class LendItemController {
     
    @Resource
    private LendItemService lendItemService;

    @ApiOperation("我要投资")
    @PostMapping("/auth/invest")
    public R invest(@RequestBody InvestVO investVO, HttpServletRequest request) {
     
        String token = request.getHeader("token");
        Long userId = JwtUtils.getUserId(token);
        String userName = JwtUtils.getUserName(token);

        investVO.setInvestUserId(userId);
        investVO.setInvestUserName(userName);
        String formStr = lendItemService.invest(investVO);
        return R.ok().setData("formStr", formStr);
    }
}

enums

UserTypeEnum.java

@AllArgsConstructor
@Getter
public enum UserTypeEnum {
     
    INVESTOR(1, "投资人"),
    BORROWER(2, "借款人");

    private Integer status;
    private String msg;
}

LendItemEnum.java

@Getter
@AllArgsConstructor
public enum LendItemEnum {
     
    NO_PAY(0, "未支付"),
    PAY_OK(1, "已支付");

    Integer status;
    String msg;
}

LendItemService

LendItemService.java

    String invest(InvestVO investVO);

LendItemServiceImpl.java

    @Resource
    private LendService lendService;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private LendMapper lendMapper;

    @Resource
    private UserAccountService userAccountService;

    @Resource
    private UserBindService userBindService;

	@Override
    public String invest(InvestVO investVO) {
     
        // 标的状态必须是募资中
        Lend lend = lendMapper.selectById(investVO.getLendId());
        Assert.isTrue(lend.getStatus() == LendStatusEnum.INVEST_RUN.getStatus(), ResponseEnum.LEND_INVEST_ERROR);

        // 用户必须是投资人
        UserInfo userInfo = userInfoMapper.selectById(investVO.getInvestUserId());
        Assert.isTrue(userInfo.getUserType() == UserTypeEnum.INVESTOR.getStatus(), ResponseEnum.LEND_USER_TYPE_ERROR);

        // 账户余额必须大于等于投资金额
        BigDecimal amount = userAccountService.getAmount(investVO.getInvestUserId());
        Assert.isTrue(amount.doubleValue() >= investVO.getInvestAmount().doubleValue(), ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);

        // 投资金额必须是100的正整数倍
        Assert.isTrue(
                investVO.getInvestAmount().remainder(new BigDecimal(100)).equals(new BigDecimal(0)) && investVO.getInvestAmount().doubleValue() > 0,
                ResponseEnum.LEND_INVEST_AMOUNT_ERROR
        );

        // 投资金额 + 已投金额 <= 募资金额
        BigDecimal sum = investVO.getInvestAmount().add(lend.getInvestAmount());
        Assert.isTrue(sum.doubleValue() <= lend.getAmount().doubleValue(), ResponseEnum.LEND_FULL_SCALE_ERROR);

        // 生成投资项对应的投资信息
        LendItem lendItem = new LendItem();
        lendItem.setLendItemNo(LendNoUtils.getLendItemNo());
        lendItem.setLendId(investVO.getLendId());
        lendItem.setInvestUserId(investVO.getInvestUserId());
        lendItem.setInvestName(investVO.getInvestUserName());
        lendItem.setInvestAmount(investVO.getInvestAmount());
        lendItem.setLendYearRate(lend.getLendYearRate());
        lendItem.setInvestTime(LocalDateTime.now());
        lendItem.setLendStartDate(lend.getLendStartDate());
        lendItem.setLendEndDate(lend.getLendEndDate());
        BigDecimal exceptAmount = lendService.getInterestCount(
                investVO.getInvestAmount(),
                lend.getLendYearRate(),
                lend.getPeriod(),
                lend.getReturnMethod()
        );
        lendItem.setExpectAmount(exceptAmount);

        BigDecimal realAmount = lendService.getInterestCount(
                investVO.getInvestAmount(),
                lend.getServiceRate(),
                lend.getPeriod(),
                lend.getReturnMethod()
        );
        lendItem.setRealAmount(realAmount);
        lendItem.setStatus(LendItemEnum.NO_PAY.getStatus());
        baseMapper.insert(lendItem);

        // 组装汇付宝参数
        Map<String, Object> map = new HashMap<>();
        String voteBindCode = userBindService.getBindCodeByUserId(lendItem.getInvestUserId());
        String benefitBindCode = userBindService.getBindCodeByUserId(lend.getUserId());
        map.put("agentId", HfbConst.AGENT_ID);
        map.put("voteBindCode", voteBindCode);
        map.put("benefitBindCode", benefitBindCode);
        map.put("agentProjectCode", lend.getLendNo());
        map.put("agentProjectName", lend.getTitle());
        map.put("agentBillNo", lendItem.getLendItemNo());
        map.put("voteAmt", lendItem.getInvestAmount());
        map.put("votePrizeAmt", "0");
        map.put("voteFeeAmt", "0");
        map.put("projectAmt", lend.getAmount());
        map.put("note", "");
        map.put("notifyUrl", HfbConst.INVEST_NOTIFY_URL);
        map.put("returnUrl", HfbConst.INVEST_RETURN_URL);
        map.put("timestamp", RequestHelper.getTimestamp());
        map.put("sign", RequestHelper.getSign(map));
        String formStr = FormHelper.buildForm(HfbConst.INVEST_URL, map);

        return formStr;
    }

UserBindService

创建一个通用的Service方法,以方便调用,根据userId获取用户绑定账号

UserBindService.java

    String getBindCodeByUserId(Long userId);

UserBindServiceImpl.java

    @Override
    public String getBindCodeByUserId(Long userId) {
     
        QueryWrapper<UserBind> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",userId);
        UserBind userBind = baseMapper.selectOne(queryWrapper);
        return userBind.getBindCode();
    }

前端

    //投资
    commitInvest() {
     
      //校验用户是否登录
      let userInfo = cookie.get('userInfo')
      // console.log(typeof userInfo)
      // console.log(!userInfo) //true
      if (!userInfo) {
     
        window.location.href = '/login'
        return
      }

      //校验当前用户是否是投资人
      let userInfoObj = JSON.parse(userInfo)
      if (userInfoObj.userType == 2) {
     
        //借款人
        this.$message.error('借款人无法投资')
        return
      }

      console.log(this.lend.investAmount)
      console.log(this.invest.investAmount)
      console.log(this.lend.amount)
      //判断标的是否超卖:标的已投金额 + 本次投资金额 > 标的总金额
      if (
        this.lend.investAmount + Number(this.invest.investAmount) >
        this.lend.amount
      ) {
     
        this.$message.error('标的可投资金额不足')
        return
      }

      //是否是100的整数倍
      // console.log(this.invest.investAmount)
      // console.log(Number(this.invest.investAmount))
      // console.log(typeof Number(this.invest.investAmount))
      // return
      if (
        Number(this.invest.investAmount) <= 0 ||
        this.invest.investAmount % this.lend.lowestAmount != 0
      ) {
     
        this.$message.error(`投资金额必须是${
       this.lend.lowestAmount}的整数倍`)
        return
      }

      //余额的判断
      if (this.invest.investAmount > this.amount) {
     
        this.$message.error('余额不足,请充值')
        return
      }

      //数据提交
      this.$alert(
        '
您即将前往汇付宝确认标的
'
, '前往汇付宝资金托管平台', { dangerouslyUseHTMLString: true, confirmButtonText: '立即前往', callback: (action) => { console.log('action', action) if (action === 'confirm') { this.invest.lendId = this.lend.id this.$axios .$post('/api/core/lendItem/auth/invest', this.invest) .then((response) => { // console.log(response.data.formStr) // debugger document.write(response.data.formStr) }) } }, } ) }, },

投资回调

需求

step7:异步回调

(1)账户金额更改(剩余金额和冻结金额)

(2)修改投资状态(lend_item表中的status)

(3)更新标的信息(lend表中的投资人数和已投金额)

(4)添加交易流水

step8:用户点击“返回平台”,返回尚融宝

后端

controller

LendItemController.java

    @ApiOperation("投资回调")
    @PostMapping("/notify")
    public String notify(HttpServletRequest request) {
     
        Map<String, Object> map = RequestHelper.switchMap(request.getParameterMap());
        // 校验签名
        if (RequestHelper.isSignEquals(map)) {
     
            if("0001".equals(map.get("resultCode"))){
     
                lendItemService.notify(map);
            }else{
     
                log.info("用户投资异步回调失败:" + JSON.toJSONString(map));
                return "fail";
            }
        } else {
     
            log.info("用户投资异步回调签名错误:" + JSON.toJSONString(map));
            return "fail";
        }
        return "success";
    }

service

LendItemService.java

    void notify(Map<String, Object> map);

LendItemServiceImpl.java

    @Resource
    private UserAccountMapper userAccountMapper;

    @Resource
    private TransFlowService transFlowService;

	@Override
    public void notify(Map<String, Object> map) {
     
        String agentBillNo = (String) map.get("agentBillNo");
        if (transFlowService.haveTransFlow(agentBillNo)) {
     
            log.warn("幂等性返回");
            return;
        }
        String bindCode = (String) map.get("voteBindCode");
        BigDecimal amount = new BigDecimal((String) map.get("voteAmt"));

        // 更新账户余额、冻结金额
        userAccountMapper.updateAccount(bindCode, new BigDecimal("-" + map.get("voteAmt")), amount);

        // 更新标的项状态
        LendItem lendItem = this.getLendItem(agentBillNo);
        lendItem.setStatus(LendItemEnum.PAY_OK.getStatus());
        baseMapper.updateById(lendItem);

        // 更新标的人数、已筹金额
        Lend lend = lendMapper.selectById(lendItem.getLendId());
        lend.setInvestAmount(lend.getInvestAmount().add(amount));
        lend.setInvestNum(lend.getInvestNum() + 1);
        lendMapper.updateById(lend);

        // 添加流水
        TransFlowBO transFlowBO = new TransFlowBO(
                agentBillNo,
                bindCode,
                TransTypeEnum.INVEST_LOCK,
                amount,
                "项目编号:"+lend.getLendNo()+",项目名称:"+lend.getTitle()
        );
        transFlowService.saveTransFlow(transFlowBO);
    }

    /**
     * 先提取出来,以后需要的话再公开
     * @param agentBillNo
     * @return
     */	
    private LendItem getLendItem(String agentBillNo) {
     
        QueryWrapper<LendItem> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("lend_item_no", agentBillNo);
        return baseMapper.selectOne(queryWrapper);
    }

你可能感兴趣的:(尚融宝,java,vue.js)